Is it possible to use the .NET DataContractJSONSerializer to deserialize a JSON file formatted differently than it would usually serialize?

冷暖自知 提交于 2021-01-29 03:50:38

问题


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

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!