json.net - 如何在根对象上添加属性$ type ONLY

Tob*_* J. 9 json.net

我想修改我的json.NET序列化程序,只将$ type属性添加到实现给定接口但不对任何属性或嵌套对象的对象.

使用TypeNameHandling.Auto(默认)

{
  "PropertyA": 123,
  "PropertyB": "foo",
  "PropertyC": [1, 2, 3, 4]
}
Run Code Online (Sandbox Code Playgroud)

使用TypeNameHandling.All

{
  "$type": "JsonNetTypeNameHandling.TestEvent, jsonNetTypeNameHandling",
  "PropertyA": 123,
  "PropertyB": "foo",
  "PropertyC": {
    "$type": "System.Collections.Generic.List`1[[System.Int32, mscorlib]], mscorlib",
    "$values": [1, 2, 3, 4 ]
  }
}
Run Code Online (Sandbox Code Playgroud)

我想要的是

{
  "$type": "JsonNetTypeNameHandling.TestEvent, jsonNetTypeNameHandling",
  "PropertyA": 123,
  "PropertyB": "foo",
  "PropertyC": [1, 2, 3, 4]
}
Run Code Online (Sandbox Code Playgroud)

我正在尝试使用自定义的ContractResolver,但我没有让它工作:

class Program
{
    static void Main(string[] args)
    {
        var serializerSettings = new JsonSerializerSettings()
        {
            TypeNameHandling = TypeNameHandling.Auto,
            TypeNameAssemblyFormat = System.Runtime.Serialization.Formatters.FormatterAssemblyStyle.Simple,
            NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore,
            ContractResolver = new EnableTypeNameHandlingAllOnlyForEvents(),
            Formatting = Formatting.Indented
        };

        var event1 = new TestEvent() { PropertyA = 123, PropertyB = "foo", PropertyC = new List<int> { 1, 2, 3, 4 } };

        string event1Serialized = JsonConvert.SerializeObject(event1, serializerSettings);

        Console.WriteLine(event1Serialized);
        Console.ReadLine();
    }
}

public interface IEvent
{
}

public class TestEvent : IEvent
{
    public int PropertyA { get; set; }
    public string PropertyB { get; set; }
    public List<int> PropertyC { get; set; }
}

public class EnableTypeNameHandlingAllOnlyForEvents : DefaultContractResolver
{
    protected override JsonObjectContract CreateObjectContract(Type objectType)
    {
        var x = base.CreateObjectContract(objectType);

        if (typeof(IEvent).IsAssignableFrom(x.UnderlyingType))
        {
            // What to do to tell json.NET to add $type to instances of this (IEvent) type???
        }

        return x;
    }
}
Run Code Online (Sandbox Code Playgroud)

dbc*_*dbc 9

如果您需要"$type"根对象上的属性,并且如果需要,它可以出现在嵌套的多态对象和数组上,请使用以下重载TypeNameHandling.Auto:JsonConvert.SerializeObject(Object, Type, JsonSerializerSettings).

来自文档:

public static string SerializeObject(
    Object value,
    Type type,
    JsonSerializerSettings settings
)
Run Code Online (Sandbox Code Playgroud)

type 类型:System.Type要序列化的值的类型.当TypeNameHandling为Auto时,如果值的类型不匹配,则使用此参数写出类型名称.指定类型是可选的.

即,做:

var serializerSettings = new JsonSerializerSettings()
{
    TypeNameHandling = TypeNameHandling.Auto,
    TypeNameAssemblyFormat = System.Runtime.Serialization.Formatters.FormatterAssemblyStyle.Simple,
    NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore,
    Formatting = Formatting.Indented
};

var event1Serialized = JsonConvert.SerializeObject(event1, typeof(IEvent), serializerSettings);
Run Code Online (Sandbox Code Playgroud)

如果您需要"$type"根对象上,将不会接受对嵌套多态对象和数组即使另有要求,则需要使用TypeNameHandling.All带有沿自合同解析器,设置JsonContainerContract.ItemTypeNameHandling = TypeNameHandling.None:

public class SuppressItemTypeNameContractResolver : DefaultContractResolver
{
    // As of 7.0.1, Json.NET suggests using a static instance for "stateless" contract resolvers, for performance reasons.
    // http://www.newtonsoft.com/json/help/html/ContractResolver.htm
    // http://www.newtonsoft.com/json/help/html/M_Newtonsoft_Json_Serialization_DefaultContractResolver__ctor_1.htm
    // "Use the parameterless constructor and cache instances of the contract resolver within your application for optimal performance."
    static SuppressItemTypeNameContractResolver instance;

    // Using a static constructor enables fairly lazy initialization.  http://csharpindepth.com/Articles/General/Singleton.aspx
    static SuppressItemTypeNameContractResolver() { instance = new SuppressItemTypeNameContractResolver(); }

    public static SuppressItemTypeNameContractResolver Instance { get { return instance; } }

    protected SuppressItemTypeNameContractResolver() : base() { }

    protected override JsonContract CreateContract(Type objectType)
    {
        var contract = base.CreateContract(objectType);
        var containerContract = contract as JsonContainerContract;
        if (containerContract != null)
        {
            if (containerContract.ItemTypeNameHandling == null)
                containerContract.ItemTypeNameHandling = TypeNameHandling.None; 
        }
        return contract;
    }
}
Run Code Online (Sandbox Code Playgroud)

然后使用它像:

var serializerSettings = new JsonSerializerSettings()
{
    TypeNameHandling = TypeNameHandling.All,
    ContractResolver = SuppressItemTypeNameContractResolver.Instance,
    TypeNameAssemblyFormat = System.Runtime.Serialization.Formatters.FormatterAssemblyStyle.Simple,
    NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore,
    Formatting = Formatting.Indented
};

var event1Serialized = JsonConvert.SerializeObject(event1, serializerSettings);
Run Code Online (Sandbox Code Playgroud)

最后,请注意Newtonsoft文档中的这一注意事项:

当您的应用程序从外部源反序列化JSON时,应谨慎使用TypeNameHandling.使用非None以外的值进行反序列化时,应使用自定义SerializationBinder验证传入类型.

有关为什么需要这样做的讨论,请参阅Newtonsoft Json中的TypeNameHandling警告,如何配置Json.NET以创建易受攻击的Web API,以及AlvaroMuñoz和Oleksandr Mirosh的黑帽文章https://www.blackhat.com/docs/ US-17 /周四/ US-17-穆尼奥斯-周五最的13 JSON-攻击,wp.pdf