问题
I have a JSON file formatted like so, which is generated by a tool that I cannot edit:
{
"thing_name1": {
"property1": 0,
"property2": "sure"
},
"thing_name2": {
"property1": 34,
"property2": "absolutely"
}
}
The class I'm trying to deserialize to is as such:
[DataContract]
public class Thing
{
[DataMember]
public String ThingName;
[DataMember(Name="property1")]
public int Property1;
[DataMember(Name="property2")]
public String Property2;
}
I need to put the values of "thing_name1" and "thing_name2" into the ThingName data member of their respective deserialized objects, but haven't been able to find an easy way to do this without writing a custom (de)serializer. Or writing a quick Python script to write another file, but that wouldn't be very space efficient.
回答1:
Yes, this is possible, but you do need some custom code to do it.
It's a little ugly, but you can create a custom IDataContractSurrogate class to deserialize the JSON into a Dictionary<string, Dictionary<string, object>>
, and then copy the values from the nested dictionary structure into a List<Thing>
. Here's the code you would need for the surrogate:
class MyDataContractSurrogate : IDataContractSurrogate
{
public Type GetDataContractType(Type type)
{
if (type == typeof(List<Thing>))
{
return typeof(Dictionary<string, Dictionary<string, object>>);
}
return type;
}
public object GetDeserializedObject(object obj, Type targetType)
{
if (obj.GetType() == typeof(Dictionary<string, Dictionary<string, object>>) &&
targetType == typeof(List<Thing>))
{
List<Thing> list = new List<Thing>();
foreach (var kvp in (Dictionary<string, Dictionary<string, object>>)obj)
{
Thing thing = new Thing { ThingName = kvp.Key };
Dictionary<string, object> propsDict = kvp.Value;
foreach (PropertyInfo prop in GetDataMemberProperties(typeof(Thing)))
{
DataMemberAttribute att = prop.GetCustomAttribute<DataMemberAttribute>();
object value;
if (propsDict.TryGetValue(att.Name, out value))
{
prop.SetValue(thing, value);
}
}
list.Add(thing);
}
return list;
}
return obj;
}
public object GetObjectToSerialize(object obj, Type targetType)
{
if (obj.GetType() == typeof(List<Thing>) &&
targetType == typeof(Dictionary<string, Dictionary<string, object>>))
{
var thingsDict = new Dictionary<string, Dictionary<string, object>>();
foreach (Thing thing in (List<Thing>)obj)
{
var propsDict = new Dictionary<string, object>();
foreach (PropertyInfo prop in GetDataMemberProperties(typeof(Thing)))
{
DataMemberAttribute att = prop.GetCustomAttribute<DataMemberAttribute>();
propsDict.Add(att.Name, prop.GetValue(thing));
}
thingsDict.Add(thing.ThingName, propsDict);
}
return thingsDict;
}
return obj;
}
private IEnumerable<PropertyInfo> GetDataMemberProperties(Type type)
{
return type.GetProperties().Where(p => p.CanRead && p.CanWrite && p.GetCustomAttribute<DataMemberAttribute>() != null);
}
// ------- The rest of these methods are not needed -------
public object GetCustomDataToExport(Type clrType, Type dataContractType)
{
throw new NotImplementedException();
}
public object GetCustomDataToExport(MemberInfo memberInfo, Type dataContractType)
{
throw new NotImplementedException();
}
public void GetKnownCustomDataTypes(System.Collections.ObjectModel.Collection<Type> customDataTypes)
{
throw new NotImplementedException();
}
public Type GetReferencedTypeOnImport(string typeName, string typeNamespace, object customData)
{
throw new NotImplementedException();
}
public System.CodeDom.CodeTypeDeclaration ProcessImportedType(System.CodeDom.CodeTypeDeclaration typeDeclaration, System.CodeDom.CodeCompileUnit compileUnit)
{
throw new NotImplementedException();
}
}
To use the surrogate, you'll need to create an instance of DataContractJsonSerializerSettings
and pass it to the DataContractJsonSerializer
with the following properties set. Note that since we require the UseSimpleDictionaryFormat
setting, this solution will only work with .Net 4.5 or later.
var settings = new DataContractJsonSerializerSettings();
settings.DataContractSurrogate = new MyDataContractSurrogate();
settings.KnownTypes = new List<Type> { typeof(Dictionary<string, Dictionary<string, object>>) };
settings.UseSimpleDictionaryFormat = true;
Note that in your Thing
class you should NOT mark the ThingName
member with a [DataMember]
attribute, since it is handled specially in the surrogate. Also, I made the assumption that your class members are actually properties (with { get; set; }
) and not fields like you wrote in your question. If that assumption is incorrect, you'll need to change all the references to PropertyInfo
in the surrogate code to use FieldInfo
instead; otherwise the surrogate will not work.
[DataContract]
public class Thing
{
// Don't mark this property with [DataMember]
public string ThingName { get; set; }
[DataMember(Name = "property1")]
public int Property1 { get; set; }
[DataMember(Name = "property2")]
public string Property2 { get; set; }
}
Here is a round-trip demo:
public class Program
{
public static void Main(string[] args)
{
string json = @"
{
""thing_name1"": {
""property1"": 0,
""property2"": ""sure""
},
""thing_name2"": {
""property1"": 34,
""property2"": ""absolutely""
}
}";
var settings = new DataContractJsonSerializerSettings();
settings.DataContractSurrogate = new MyDataContractSurrogate();
settings.KnownTypes = new List<Type> { typeof(Dictionary<string, Dictionary<string, object>>) };
settings.UseSimpleDictionaryFormat = true;
List<Thing> things = Deserialize<List<Thing>>(json, settings);
foreach (Thing thing in things)
{
Console.WriteLine("ThingName: " + thing.ThingName);
Console.WriteLine("Property1: " + thing.Property1);
Console.WriteLine("Property2: " + thing.Property2);
Console.WriteLine();
}
json = Serialize(things, settings);
Console.WriteLine(json);
}
public static T Deserialize<T>(string json, DataContractJsonSerializerSettings settings)
{
using (MemoryStream ms = new MemoryStream(Encoding.UTF8.GetBytes(json)))
{
var ser = new DataContractJsonSerializer(typeof(T), settings);
return (T)ser.ReadObject(ms);
}
}
public static string Serialize(object obj, DataContractJsonSerializerSettings settings)
{
using (MemoryStream ms = new MemoryStream())
{
var ser = new DataContractJsonSerializer(obj.GetType(), settings);
ser.WriteObject(ms, obj);
return Encoding.UTF8.GetString(ms.ToArray());
}
}
}
Output:
ThingName: thing_name1
Property1: 0
Property2: sure
ThingName: thing_name2
Property1: 34
Property2: absolutely
{"thing_name1":{"property1":0,"property2":"sure"},"thing_name2":{"property1":34,"property2":"absolutely"}}
来源:https://stackoverflow.com/questions/41233345/is-it-possible-to-use-the-net-datacontractjsonserializer-to-deserialize-a-json