天天看點

.NET 8 Preview 1 中 SystemTextJson 的改進

作者:opendotnet

.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, able`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, able`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 8 Preview 1 中 SystemTextJson 的改進

.NET 7 interface serialize output

在 .NET 8 Preview 1 輸出結果如下:

.NET 8 Preview 1 中 SystemTextJson 的改進

.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);
}
           

輸出結果如下:

.NET 8 Preview 1 中 SystemTextJson 的改進

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);
}
           

輸出結果如下:

.NET 8 Preview 1 中 SystemTextJson 的改進

KebabCaseNaming output

JsonSerializerOptions-ReadOnly

JsonSerializerOptions

中增加了

IsReadOnly

MakeReadOnly

兩個方法,我們可以在為某個類型的序列化指定了某些序列化選項之後調用

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 = ;
 }
catch (Exception e)
 {
 Console.WriteLine(e);
 }
}
           

輸出結果如下:

.NET 8 Preview 1 中 SystemTextJson 的改進

從上面的輸出可以看得出來,在我們調用

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