覆盖 ASP.NET 核心中的 newtonsoft.json 序列化,使 typeNameHandling 仅对对象而非数组为 Auto

Override newtonsoft.json serialization in ASP.NET Core to make the typeNameHandling to be Auto only for objects and not arrays

我的问题是我的连载 json 需要一些 $type,我有一些子 class,这对我非常有用。

这是我的创业公司

services.AddTransient<Microsoft.Extensions.Options.IConfigureOptions<Microsoft.AspNetCore.Mvc.MvcNewtonsoftJsonOptions>, App_Start.MvcJsonOptionsSetup>();
services.AddControllers()
        .AddNewtonsoftJson()
        .SetCompatibilityVersion(CompatibilityVersion.Latest)
        .AddControllersAsServices();

internal class MvcJsonOptionsSetup : IConfigureOptions<MvcNewtonsoftJsonOptions>
{
   private readonly IHttpContextAccessor _provider;

   public MvcJsonOptionsSetup(IHttpContextAccessor provider)
   {
   }

   public MvcJsonOptionsSetup(IHttpContextAccessor provider)
   {
       _provider = provider;
   }

   public Func<IServiceProvider> GetProvider()
   {
       return () => _provider.HttpContext?.RequestServices;
   }

   public override void Configure(MvcNewtonsoftJsonOptions options)
   {
       options.SerializerSettings.ReferenceLoopHandling = ReferenceLoopHandling.Ignore;
       options.SerializerSettings.ContractResolver = new DefaultContractResolver();
       options.SerializerSettings.ConfigureRepositoryForJson<EntidadeBase>(GetProvider());
       options.SerializerSettings.TypeNameHandling = Newtonsoft.Json.TypeNameHandling.Auto;
    }
}

问题出在数组上;我不想放 $values,但有时 return 类型不一样。

有没有人做了一些 TypeNAmeHandling.Auto 除了数组的扩展?

比如我用的是kendo格子,return是这样的:

{
  "Data": {
    "$type": "System.Collections.Generic.List`1[[Model.Entity, Model]], System.Private.CoreLib",
    "$values": [
        ...
    ]
  },
  "Groups": null,
  "Aggregates": null,
  "Total": 4,
  "Errors": null
}

我想要这样

{
  "Data":  [
        ...
    ]
  ,
  "Groups": null,
  "Aggregates": null,
  "Total": 4,
  "Errors": null
} 

我没有使用

TypeNameHandling = Newtonsoft.Json.TypeNameHandling.Objects 

为了使结果更简单,我想 return 仅当类型是子class。

这是结果class

public class DataSourceResult
{
    public IEnumerable Data { get; set; }
    public IEnumerable Groups { get; set; }
    public object Aggregates { get; set; }
    public int Total { get; set; }
    public object Errors { get; set; }
}

最后,注意我不能把属性(比如[JsonProperty(TypeNameHandling=TypeNameHandling.None)])放在所有class中,我的意思是,有些库是我用的,不能改变,我是寻找我可以覆盖 newtonsoft 序列化程序并检查是否是数组并忽略 typeNamehandling 属性.

的东西

使用 Json.NET 无法启用或禁用纯粹基于被序列化项目的具体类型的类型信息的发射。相反,类型信息的发布由引用正在序列化的项目的成员或集合控制。*

只要这些成员(或集合项)被声明为可从 IEnumerable 分配的内容,您就可以使用自定义契约解析器来抑制类型信息,如下所示:

public class SuppressCollectionTypeNamesContractResolver : DefaultContractResolver
{
    internal static TypeNameHandling? RemoveCollectionTypenameHandling(TypeNameHandling? typeNameHandling, Type valueType)
        => typeNameHandling ?? (typeof(IEnumerable).IsAssignableFrom(valueType) && valueType != typeof(string) ? TypeNameHandling.None : typeNameHandling);

    protected override JsonProperty CreateProperty(MemberInfo member, MemberSerialization memberSerialization)
    {
        var property = base.CreateProperty(member, memberSerialization);
        property.TypeNameHandling = RemoveCollectionTypenameHandling(property.TypeNameHandling, property.PropertyType);
        return property;
    }

    protected override JsonArrayContract CreateArrayContract(Type objectType)
    {
        var contract = base.CreateArrayContract(objectType);
        contract.ItemTypeNameHandling = RemoveCollectionTypenameHandling(contract.ItemTypeNameHandling, contract.CollectionItemType);
        return contract;
    }
}

并用它代替 DefaultContractResolver,如下所示:

options.SerializerSettings.ContractResolver = new SuppressCollectionTypeNamesContractResolver();

请注意,Newtonsoft recommends caching custom resolvers statically for best performance

演示 fiddle #1 here.

如果您可能将成员声明为 objectdynamic,并且在其值是集合的情况下仍需要抑制类型信息,则您将必须使用如下自定义转换器:

public class SuppressCollectionTypeNamesConverter : JsonConverter
{
    static readonly IContractResolver globalDefaultResolver = new JsonSerializer().ContractResolver;
    IContractResolver resolver;
    
    public SuppressCollectionTypeNamesConverter() : this(globalDefaultResolver) { }
    public SuppressCollectionTypeNamesConverter(IContractResolver resolver) => this.resolver = resolver ?? globalDefaultResolver;
    
    public override bool CanConvert(Type objectType) => typeof(IEnumerable).IsAssignableFrom(objectType) && objectType != typeof(string) && resolver.ResolveContract(objectType) is JsonArrayContract;

    public override bool CanRead => false;      
    public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer) => throw new NotImplementedException();        

    public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
    {
        var contract = (JsonArrayContract)serializer.ContractResolver.ResolveContract(value.GetType());
        var itemType = contract.CollectionItemType;
        writer.WriteStartArray();
        foreach (object item in ((IEnumerable)value))
        {
            if (contract.ItemTypeNameHandling == TypeNameHandling.None)
                serializer.Serialize(writer, item);
            else
                serializer.Serialize(writer, item, itemType);
        }
        
        writer.WriteEndArray();
    }
}

并将其添加到选项中,如下所示:

options.SerializerSettings.Converters.Add(new SuppressCollectionTypeNamesConverter());

演示 fiddle #2 here.


* 这个可以从JsonSerializerInternalWriter.SerializeList() which calls JsonSerializerInternalWriter.ShouldWriteType()

的参考资料中得到证实