Omit type for dynamic property without modifying class?

独自空忆成欢 提交于 2019-12-12 18:14:25

问题


I have a bunch of domain objects that I serialize, send to other applications and then deserialize using Json.Net. These objects may have properties that are

  • Defined as an abstract base class with multiple derived classes
  • Dynamic Properties

I've used TypeNameHandling.Auto, which adds the $type property to types that differ from the declared type. However, this setting has an unwanted side effect on my dynamic properties, namely that the types of them gets declared, too.

In the example below model is a dynamic property defined as public dynamic Model { get; set; } in my C# code.

"model":{"$type":"<>f__AnonymousType0`3[[System.String, mscorlib],[System.String, mscorlib],[System.String, mscorlib]], ExampleAssembly","link":"http://www.google.com","name":"John"}

When trying to deserialize this string in another assembly, Json.Net can't (of course) find the ExampleAssembly. Using the TypeNameHandling.None property gives the following property serialization

"model": {"link":"http://www.google.com","name":"John"}

Which can be successfully deserialized to a dynamic. However, this breaks deserialization of derived types.

Any ideas on how to get this to work without implementing custom IContractResolver and possibly other custom code?

I don't own the domain objects, so I can't decorate them or their properties with attributes or allow them to implement interfaces etc. What I'm looking for is some sort of setting in the serializer that omits types for dynamics.

IMHO this should be configurable through settings somehow, I just havn't found it.


回答1:


Json.Net does not provide a specific setting to turn off type name handling for dynamic types only. If you can't (or don't want to) mark the relevant dynamic properties with [JsonProperty(TypeNameHandling = TypeNameHandling.None)], then your only other option (short of modifying the Json.Net source code itself) is to implement a custom contract resolver to apply the behavior programmatically. But don't worry, this is not difficult to do if you derive your resolver from one of the Json.Net-provided resolvers such as DefaultContractResolver or CamelCasePropertyNamesContractResolver.

Here is all the code you would need:

using System;
using System.Reflection;
using Newtonsoft.Json;
using Newtonsoft.Json.Serialization;

public class OmitTypeNamesOnDynamicsResolver : DefaultContractResolver
{
    public static readonly OmitTypeNamesOnDynamicsResolver Instance = new OmitTypeNamesOnDynamicsResolver();

    protected override JsonProperty CreateProperty(MemberInfo member, MemberSerialization memberSerialization)
    {
        JsonProperty prop = base.CreateProperty(member, memberSerialization);
        if (member.GetCustomAttribute<System.Runtime.CompilerServices.DynamicAttribute>() != null)
        {
            prop.TypeNameHandling = TypeNameHandling.None;
        }
        return prop;
    }
}

Then, just add the resolver to the JsonSerializerSettings and you should be all set.

JsonSerializerSettings settings = new JsonSerializerSettings
{
    TypeNameHandling = TypeNameHandling.Auto,
    ContractResolver = OmitTypeNamesOnDynamicsResolver.Instance
};

string json = JsonConvert.SerializeObject(foo, settings);

Here is a round-trip demo to prove the concept:

public class Program
{
    public static void Main(string[] args)
    {
        Foo foo = new Foo
        {
            Model = new { link = "http://www.google.com", name = "John" },
            Widget1 = new Doodad { Name = "Sprocket", Size = 10 },
            Widget2 = new Thingy { Name = "Coil", Strength = 5 }
        };

        JsonSerializerSettings settings = new JsonSerializerSettings
        {
            TypeNameHandling = TypeNameHandling.Auto,
            ContractResolver = OmitTypeNamesOnDynamicsResolver.Instance,
            Formatting = Formatting.Indented
        };

        string json = JsonConvert.SerializeObject(foo, settings);
        Console.WriteLine(json);
        Console.WriteLine();

        Foo foo2 = JsonConvert.DeserializeObject<Foo>(json, settings);
        Console.WriteLine(foo2.Model.link);
        Console.WriteLine(foo2.Model.name);
        Console.WriteLine(foo2.Widget1.Name + " (" + foo2.Widget1.GetType().Name + ")");
        Console.WriteLine(foo2.Widget2.Name + " (" + foo2.Widget2.GetType().Name + ")");
    }
}

public class Foo
{
    public dynamic Model { get; set; }
    public AbstractWidget Widget1 { get; set; }
    public AbstractWidget Widget2 { get; set; }
}

public class AbstractWidget
{
    public string Name { get; set; }
}

public class Thingy : AbstractWidget
{
    public int Strength { get; set; }
}

public class Doodad : AbstractWidget
{
    public int Size { get; set; }
}

Output:

{
  "Model": {
    "link": "http://www.google.com",
    "name": "John"
  },
  "Widget1": {
    "$type": "Doodad, JsonTest",
    "Size": 10,
    "Name": "Sprocket"
  },
  "Widget2": {
    "$type": "Thingy, JsonTest",
    "Strength": 5,
    "Name": "Coil"
  }
}

http://www.google.com
John
Sprocket (Doodad)
Coil (Thingy)


来源:https://stackoverflow.com/questions/39386197/omit-type-for-dynamic-property-without-modifying-class

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