System.Text.Json API 中是否有类似于 IContractResolver 的东西?

11
在新的System.Text.Json命名空间中是否有类似于IContractResolver的东西,我正在尝试将我的项目从Newtonsoft迁移。这是我正在尝试移动的其中一个类:
public class SelectiveSerializer : DefaultContractResolver
{
private readonly string[] fields;

public SelectiveSerializer(string fields)
{
  var fieldColl = fields.Split(',');
  this.fields = fieldColl
      .Select(f => f.ToLower().Trim())
      .ToArray();
}

protected override JsonProperty CreateProperty(MemberInfo member, MemberSerialization memberSerialization)
{
  var property = base.CreateProperty(member, memberSerialization);
  property.ShouldSerialize = o => fields.Contains(member.Name.ToLower());

  return property;
}
}

你使用过 .net core 3.0 吗?能否分享一下为什么要从 Newtonsoft 迁移到 System.Text.Json?实际上,net core 3.0 可以支持 Newtonsoft。那你的代码是如何工作的呢?它用于将请求体数据转换为小写吗? - Rena
@Rena 这段代码用于构建我们自己的API选择函数。例如:select=FirstName,Email,Id,序列化程序只会序列化指定的字段。与OData类似,因为我们正在远离OData。 - Dživo Jelić
System.Text.Json 中的等效类型 -- JsonClassInfoJsonPropertyInfo -- 是 internal 的。有一个开放的增强请求 Equivalent of DefaultContractResolver in System.Text.Json #42001,要求提供一个公共的等效类型。 - dbc
@dbc 很好知道 :) - Dživo Jelić
2个回答

5

System.Text.Json中的等效类型-- JsonClassInfoJsonPropertyInfo -- 都是内部的。有一个开放性增强请求System.Text.Json中DefaultContractResolver的等效项#31257,要求提供公共等效项。 - dbc Nov 25 at 19:11

Github问题:

请尝试以下操作:
我编写了一个扩展程序,以提供缺失的功能:https://github.com/dahomey-technologies/Dahomey.Json

您将找到对编程对象映射的支持。

定义自己的 IObjectMappingConvention 实现:

public class SelectiveSerializer : IObjectMappingConvention
{
    private readonly IObjectMappingConvention defaultObjectMappingConvention = new DefaultObjectMappingConvention();
    private readonly string[] fields;

    public SelectiveSerializer(string fields)
    {
        var fieldColl = fields.Split(',');
        this.fields = fieldColl
            .Select(f => f.ToLower().Trim())
            .ToArray();
    }

    public void Apply<T>(JsonSerializerOptions options, ObjectMapping<T> objectMapping) where T : class
    {
        defaultObjectMappingConvention.Apply<T>(options, objectMapping);
        foreach (IMemberMapping memberMapping in objectMapping.MemberMappings)
        {
            if (memberMapping is MemberMapping<T> member)
            {
                member.SetShouldSerializeMethod(o => fields.Contains(member.MemberName.ToLower()));
            }
        }
    }
}

定义你的类:
public class Employee
{
    public int Id { get; set; }
    public string FirstName { get; set; }
    public string LastName { get; set; }
    public string Email { get; set; }
}

通过在 JsonSerializerOptions 上调用 Dahomey.Json 命名空间中定义的扩展方法 SetupExtensions,设置 json 扩展。

JsonSerializerOptions options = new JsonSerializerOptions();
options.SetupExtensions();

为该类注册新的对象映射约定:

options.GetObjectMappingConventionRegistry().RegisterConvention(
    typeof(Employee), new SelectiveSerializer("FirstName,Email,Id"));

然后使用常规的Sytem.Text.Json API对您的类进行序列化:

Employee employee = new Employee
{
    Id = 12,
    FirstName = "John",
    LastName = "Doe",
    Email = "john.doe@acme.com"
};
        
string json = JsonSerializer.Serialize(employee, options);
// {"Id":12,"FirstName":"John","Email":"john.doe@acme.com"};

4
“合同定制”将在.NET 7中实现,并且在Preview 6中可用。
来自Eirik Tsarpalis,Krzysztof Wicher和Layomi Akinrinade的文档页面.NET 7中System.Text.Json的新功能:合同定制

The contract metadata for a given type T is represented using JsonTypeInfo<T>, which in previous versions served as an opaque token used exclusively in source generator APIs. Starting in .NET 7, most facets of the JsonTypeInfo contract metadata have been exposed and made user-modifiable. Contract customization allows users to write their own JSON contract resolution logic using implementations of the IJsonTypeInfoResolver interface:

public interface IJsonTypeInfoResolver
{
    JsonTypeInfo? GetTypeInfo(Type type, JsonSerializerOptions options);
}

A contract resolver returns a configured JsonTypeInfo instance for the given Type and JsonSerializerOptions combination. It can return null if the resolver does not support metadata for the specified input type.

Contract resolution performed by the default, reflection-based serializer is now exposed via the DefaultJsonTypeInfoResolver class, which implements IJsonTypeInfoResolver.

Starting from .NET 7 the JsonSerializerContext class used in source generation also implements IJsonTypeInfoResolver.

您可以通过以下方法之一创建自己的IJsonTypeInfoResolver
您可以继承DefaultJsonTypeInfoResolver并覆盖GetTypeInfo(Type, JsonSerializerOptions)。这类似于覆盖Json.NET的DefaultContractResolver.CreateContract()
您可以向DefaultJsonTypeInfoResolver.Modifiers添加一个Action<JsonTypeInfo>,以修改默认情况下为选定类型生成的JsonTypeInfo。使用此方法组合多个自定义项比使用继承方法更容易。但是,由于修饰符操作按顺序应用,因此后续修饰符可能会与先前修饰符发生冲突。
您可以从头开始创建自己的IJsonTypeInfoResolver,仅为您感兴趣的那些类型创建契约,并通过JsonTypeInfoResolver.Combine(IJsonTypeInfoResolver[])将其与其他类型信息解析器组合。
当您只想为某些类型自定义序列化时,使用编译时生成的JsonSerializerContext实例和运行时契约解析器组合时,JsonTypeInfoResolver.Combine()也很有用。
一旦你有了自定义解析器,你可以通过 JsonSerializerOptions.TypeInfoResolver 来设置它。
因此,你的 SelectiveSerializer 可以通过使用修改器大致转换为 DefaultJsonTypeInfoResolver,具体步骤如下。首先定义以下流畅的扩展方法:
public static partial class JsonSerializerExtensions
{
    public static DefaultJsonTypeInfoResolver SerializeSelectedFields(this DefaultJsonTypeInfoResolver resolver, string fields) =>
        SerializeSelectedFields(resolver, fields?.Split(',', StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries) ?? throw new ArgumentNullException(nameof(fields)));

    public static DefaultJsonTypeInfoResolver SerializeSelectedFields(this DefaultJsonTypeInfoResolver resolver, IEnumerable<string> membersToSerialize)
    {
        if (resolver == null)
            throw new ArgumentNullException(nameof(resolver));
        if (membersToSerialize == null)
            throw new ArgumentNullException(nameof(membersToSerialize));
        var membersToSerializeSet =  membersToSerialize.ToHashSet(StringComparer.OrdinalIgnoreCase); // Possibly this should be changed to StringComparer.Ordinal
        resolver.Modifiers.Add(typeInfo => 
                               {
                                   if (typeInfo.Kind == JsonTypeInfoKind.Object)
                                   {
                                       foreach (var property in typeInfo.Properties)
                                       {
                                           if (property.GetMemberName() is {} name && !membersToSerializeSet.Contains(name))
                                               property.ShouldSerialize = static (obj, value) => false;
                                       }
                                   }
                               });
        return resolver;
    }
    
    public static string? GetMemberName(this JsonPropertyInfo property) => (property.AttributeProvider as MemberInfo)?.Name;
}

现在,您可以设置您的 JsonSerializerOptions,例如如下所示:
var options = new JsonSerializerOptions
{
    TypeInfoResolver = new DefaultJsonTypeInfoResolver()
        .SerializeSelectedFields("FirstName,Email,Id"),
    // Add other options as required
    PropertyNamingPolicy = JsonNamingPolicy.CamelCase, 
    WriteIndented = true,
};

注释:

注意 - 尚未测试,将在 .NET 7 发布时进行测试。 - dbc
.NET 7已经发布,该功能在此处描述:https://devblogs.microsoft.com/dotnet/system-text-json-in-dotnet-7/#example-conditional-serialization - sammy34

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接