.NET 8 Preview 1 中 SystemTextJson 的改进

Posted dotNET跨平台

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了.NET 8 Preview 1 中 SystemTextJson 的改进相关的知识,希望对你有一定的参考价值。

.NET 8 Preview 1 中 SystemTextJson 的改进

Intro

System.Text.Json 是从 .NET Core 3.0 开始的一个新的 JSON 处理库,在之后的版本中一直在完善和改善性能,在 .NET 8 Preview 1 中完善一些支持,具体更新如下

Improvements

Unmapped Json Property Handling

在之前的版本中,如果 json 里 property 是不希望的内容不会有任何处理,在新版本中增加了没有 mapping 的 json property 处理,可以在找不到 mapping 的时候报错,示例如下:

file record Person(int Id, string Name);

var personJsonWithoutId = JsonSerializer.Serialize(new  Id = 1, Name = "1234", Age = 10 );

try

    var p = JsonSerializer.Deserialize<Person>(personJsonWithoutId);
    Console.WriteLine(p?.ToString());

catch (Exception e)

    Console.WriteLine(e);

不指定没有 mapping 的 JSON property 的时候默认是允许的,以上就会正常输出,不会走到 exception,输出如下:

Person  Id = 1, Name = 1234 

当我们指定了要报错的时候就会抛异常

try

    var p = JsonSerializer.Deserialize<Person>(personJsonWithoutId,
        new JsonSerializerOptions()  
            UnmappedMemberHandling = JsonUnmappedMemberHandling.Disallow 
        );
    Console.WriteLine(p?.ToString());

catch (Exception e)

    Console.WriteLine(e);

输出结果如下:

System.Text.Json.JsonException: The JSON property 'Net8Sample.<>FE3B0C44298FC1C149AFBF4C8996FB92427AE41E4649B934CA495991B7852B855__Person' could not be mapped to any .NET member contained in type 'Age'.
   at System.Text.Json.ThrowHelper.ThrowJsonException_UnmappedJsonProperty(Type type, String unmappedPropertyName)
   at System.Text.Json.JsonSerializer.LookupProperty(Object obj, ReadOnlySpan`1 unescapedPropertyName, ReadStack& state, JsonSerializerOptions options, Boolean& useExtensionProperty, Boolean createExtensionProperty)
   at System.Text.Json.Serialization.Converters.ObjectWithParameterizedConstructorConverter`1.OnTryRead(Utf8JsonReader& reader, Type typeToConvert, JsonSerializerOptions options, ReadStack& state, T& value)
   at System.Text.Json.Serialization.JsonConverter`1.TryRead(Utf8JsonReader& reader, Type typeToConvert, JsonSerializerOptions options, ReadStack& state, T& value)
   at System.Text.Json.Serialization.JsonConverter`1.ReadCore(Utf8JsonReader& reader, JsonSerializerOptions options, ReadStack& state)
   at System.Text.Json.Serialization.Metadata.JsonTypeInfo`1.Deserialize(Utf8JsonReader& reader, ReadStack& state)
   at System.Text.Json.JsonSerializer.ReadFromSpan[TValue](ReadOnlySpan`1 utf8Json, JsonTypeInfo`1 jsonTypeInfo, Nullable`1 actualByteCount)
   at System.Text.Json.JsonSerializer.ReadFromSpan[TValue](ReadOnlySpan`1 json, JsonTypeInfo`1 jsonTypeInfo)
   at System.Text.Json.JsonSerializer.Deserialize[TValue](String json, JsonSerializerOptions options)
   at Net8Sample.JsonSample.MissingMemberHandlingTest()

除了指定 JsonSerializerOptions 我们也可以针对某一个类型添加 JsonUnmappedMemberHandling 标记,示例如下:

[JsonUnmappedMemberHandling(JsonUnmappedMemberHandling.Disallow)]
file record Person2

    public required int Id  get; init; 
    public required string Name  get; init; 
    public string? JobTitle  get; set; 


try

    var p = JsonSerializer.Deserialize<Person2>(personJsonWithoutId);
    Console.WriteLine(p?.ToString());

catch (Exception e)

    Console.WriteLine(e);

输出结果和前面的示例类似:

System.Text.Json.JsonException: The JSON property 'Net8Sample.<>FE3B0C44298FC1C149AFBF4C8996FB92427AE41E4649B934CA495991B7852B855__Person2' could not be mapped to any .NET member contained in type 'Age'.
   at System.Text.Json.ThrowHelper.ThrowJsonException_UnmappedJsonProperty(Type type, String unmappedPropertyName)
   at System.Text.Json.JsonSerializer.LookupProperty(Object obj, ReadOnlySpan`1 unescapedPropertyName, ReadStack& state, JsonSerializerOptions options, Boolean& useExtensionProperty, Boolean createExtensionProperty)
   at System.Text.Json.Serialization.Converters.ObjectDefaultConverter`1.OnTryRead(Utf8JsonReader& reader, Type typeToConvert, JsonSerializerOptions options, ReadStack& state, T& value)
   at System.Text.Json.Serialization.JsonConverter`1.TryRead(Utf8JsonReader& reader, Type typeToConvert, JsonSerializerOptions options, ReadStack& state, T& value)
   at System.Text.Json.Serialization.JsonConverter`1.ReadCore(Utf8JsonReader& reader, JsonSerializerOptions options, ReadStack& state)
   at System.Text.Json.Serialization.Metadata.JsonTypeInfo`1.Deserialize(Utf8JsonReader& reader, ReadStack& state)
   at System.Text.Json.JsonSerializer.ReadFromSpan[TValue](ReadOnlySpan`1 utf8Json, JsonTypeInfo`1 jsonTypeInfo, Nullable`1 actualByteCount)
   at System.Text.Json.JsonSerializer.ReadFromSpan[TValue](ReadOnlySpan`1 json, JsonTypeInfo`1 jsonTypeInfo)
   at System.Text.Json.JsonSerializer.Deserialize[TValue](String json, JsonSerializerOptions options)
   at Net8Sample.JsonSample.MissingMemberHandlingTest()

在 System.Text.Json 中有个特殊的特性,我们可以使用 JsonExtensionData 来匹配那些没有 mapping 的 JSON property,那两个一起使用会不会报错呢,我们也来试一下

file record PersonWithExtensionData

    public required int Id  get; init; 
    public required string Name  get; init; 
    [JsonExtensionData]
    public Dictionary<string,object>? Extensions  get; set; 


try

    var p = JsonSerializer.Deserialize<PersonWithExtensionData>(personJsonWithoutId,
        new JsonSerializerOptions()  UnmappedMemberHandling = JsonUnmappedMemberHandling.Disallow );
    Console.WriteLine(JsonSerializer.Serialize(p));

catch (Exception e)

    Console.WriteLine(e);

输出结果如下:

"Id":1,"Name":"1234","Age":10

是否和你猜测的一致呢

Interface Hierarchy

在之前的版本中如果我们使用接口进行序列化的话,接口继承的属性是不会被序列化的,比如下面的代码:

file interface IBase

    int Base  get; set; 

file interface IDerived : IBase

    int Derived  get; set; 

file class DerivedImplement : IDerived

    public int Base  get; set; 
    public int Derived  get; set; 


IDerived value = new DerivedImplement()  Base = 0, Derived =1 ;
var serializedValue = JsonSerializer.Serialize(value);
Console.WriteLine(serializedValue);

在 .NET 7 中输出结果如下:

.NET 7 interface serialize output

在 .NET 8 Preview 1 输出结果如下:

.NET 8 Preview interface serialize output

SnakeCaseNaming && KebabCaseNaming

在 .NET 8 Preview 1 中新增了两种属性名称序列化方式,SnakeCase 和 KebabCase,两种方式分别有 大写形式和小写形式,使用的时候在 JsonSerializerOptions 中指定 PropertyNamingPolicy 即可,我们直接看下示例吧

private static void SnakeCaseNamingTest()

    var p = new Person2() 
     
        Id = 1, 
        Name = "Alice",
        JobTitle = "Engineer" 
    ;
    var snakeCaseLowerJson = JsonSerializer.Serialize(p, new JsonSerializerOptions()
    
        PropertyNamingPolicy = JsonNamingPolicy.SnakeCaseLower
    );
    Console.WriteLine(snakeCaseLowerJson);

    var snakeCaseUpperJson = JsonSerializer.Serialize(p, new JsonSerializerOptions()
    
        PropertyNamingPolicy = JsonNamingPolicy.SnakeCaseUpper
    );
    Console.WriteLine(snakeCaseUpperJson);

输出结果如下:

SnakeCaseNaming output

再来看下 KebabCase 的示例:

private static void KebabCaseNamingTest()

    var p = new Person2() 
     
        Id = 1, 
        Name = "Alice",
        JobTitle = "Engineer" 
    ;
    var kebabCaseLowerJson = JsonSerializer.Serialize(p, new JsonSerializerOptions()
    
        PropertyNamingPolicy = JsonNamingPolicy.KebabCaseLower
    );
    Console.WriteLine(kebabCaseLowerJson);

    var kebabCaseUpperJson = JsonSerializer.Serialize(p, new JsonSerializerOptions()
    
        PropertyNamingPolicy = JsonNamingPolicy.KebabCaseUpper
    );
    Console.WriteLine(kebabCaseUpperJson);

输出结果如下:

KebabCaseNaming output

JsonSerializerOptions-ReadOnly

JsonSerializerOptions 中增加了 IsReadOnlyMakeReadOnly 两个方法,我们可以在为某个类型的序列化指定了某些序列化选项之后调用 MakeReadOnly 方法来保证序列化选项不会再被修改来保证序列化行为的一致性,下面是一个示例:

private static void JsonSerializerOptionsReadOnlyTest()

    var options = new JsonSerializerOptions(JsonSerializerDefaults.Web)
    
        TypeInfoResolver = new DefaultJsonTypeInfoResolver()
    ;
    Console.WriteLine($"IsReadOnly: options.IsReadOnly");
    options.PropertyNamingPolicy = JsonNamingPolicy.CamelCase;
    Console.WriteLine("PropertyNamingPolicy updated");

    options.MakeReadOnly();
    Console.WriteLine($"IsReadOnly: options.IsReadOnly");

    try
    
        options.PropertyNamingPolicy = null;
    
    catch (Exception e)
    
        Console.WriteLine(e);
    

输出结果如下:

从上面的输出可以看得出来,在我们调用 MakeReadOnly 方法之前 IsReadOnly 会是 false,是可以修改 options 的配置的,在调用之后 IsReadOnly 就变成 true 了,再修改 options 的配置就会抛异常

More

细心的小伙伴可能会发现第一个示例 Unmapped Json Property Handling 部分示例的异常信息是有点问题的,property 和 type 信息的位置反了,这是一个 BUG 。。,目前 bug 已经修复了,preview 2 应该就没这个问题了,修复 PR 可以参考:https://github.com/dotnet/runtime/pull/81718

References

  • https://devblogs.microsoft.com/dotnet/announcing-dotnet-8-preview-1/#json-improvements

  • https://github.com/dotnet/runtime/issues/37483

  • https://github.com/dotnet/runtime/pull/79945

  • https://github.com/dotnet/runtime/pull/78788

  • https://github.com/dotnet/runtime/pull/69613

  • https://github.com/dotnet/runtime/pull/74431

  • https://github.com/dotnet/runtime/pull/81718

  • https://github.com/WeihanLi/SamplesInPractice/blob/master/net8sample/Net8Sample/JsonSample.cs

以上是关于.NET 8 Preview 1 中 SystemTextJson 的改进的主要内容,如果未能解决你的问题,请参考以下文章

.NET 8 Preview 1 中 SystemTextJson 的改进

.NET 8 Preview 1 中 SDK 的更新

.NET 8 Preview 1 中新增的 Random 方法

.NET 7 发布的最后一个预览版Preview 7, 下个月发布RC

初尝 .NET 8 Preview 1

dotnet 8 preview 1 即将发布