JSON序列化: System.Text.Json与Newtonsoft.Json

两者的比较

System.Text.Json: 官方, 高性能, 低内存, 默认行为严格, 相对不灵活, 支持源生成, AOT
Newtonsoft.Json: 第三方, 性能较高, 功能丰富, 默认行为相对宽松, 强大的灵活性, 不支持源生成, 不能AOT

System.Text.Json迁移至Newtonsoft.Json, 简单
Newtonsoft.Json迁移至System.Text.Json, 复杂, 要改很多东西(如果json严格,数据结构简单就相对好很多), 扩展阅读中查看

System.Text.Json 基本使用

序列化 JsonSerializer.Serialize

注意, 序列化的默认行为, 这是学习序列化的关键一步:

  • 默认情况下,所有公共(public)属性(Property)都会序列化, 会忽略字段
  • 默认无空格,不对齐,区分大小写
// 注意: 这里默认一定是 public 的 属性, 否则会失败
public class Weather
{
    public DateTimeOffset Date { get; set; }
    public int TemperatureCelsius { get; set; }
    public string? Summary { get; set; }
}
var weather = new Weather {Date = DateTime.Parse("2019-08-01"), TemperatureCelsius = 25, Summary = "Hot"};
string jsonString1 = JsonSerializer.Serialize(weather);
// 注意: 官方提供了很多种序列化/反序列化方法
// 包括: 泛型/非泛型(object)/源生成
public static string Serialize<TValue>(TValue value, JsonSerializerOptions? options = null); // 泛型
public static string Serialize(object? value, Type inputType, JsonSerializerOptions? options = null); // object
public static string Serialize<TValue>(TValue value, JsonTypeInfo<TValue> jsonTypeInfo); // 泛型,源生成
public static string Serialize(object? value, JsonTypeInfo jsonTypeInfo); // object,源生成
public static string Serialize(object? value, Type inputType, JsonSerializerContext context); // object,源生成
// 另外还包括 Stream流, Task异步, 很多个方法, 这里就不一一列举了
public static void Serialize<TValue>(Stream utf8Json, TValue value, JsonSerializerOptions? options = null)
public static Task SerializeAsync(Stream utf8Json, object? value, Type inputType, JsonSerializerOptions? options = null, CancellationToken cancellationToken = default)
......

反序列化

注意, 反序列化默认行为, 关键

  • 区分大小写
  • 枚举作为数字
  • 忽略字段⭐
  • JSON 中的注释或尾随逗号会引发异常
  • 最大深度为 64
var jsonString = """{"Date":"2019-08-01T00:00:00-07:00","TemperatureCelsius":25,"Summary":"Hot"}""";
Weather? weatherForecast = JsonSerializer.Deserialize<Weather>(jsonString);

// 当然, 反序列化也有很多种方法
// 包括: 泛型/非泛型(object)/源生成, 这里还支持 ReadOnlySpan<char>参数
public static TValue? Deserialize<TValue>(string json, JsonSerializerOptions? options = null);
public static TValue? Deserialize<TValue>(ReadOnlySpan<char> json, JsonSerializerOptions? options = null)
public static object? Deserialize(string json, Type returnType, JsonSerializerOptions? options = null)
public static object? Deserialize(ReadOnlySpan<char> json, Type returnType, JsonSerializerOptions? options = null)
public static TValue? Deserialize<TValue>(string json, JsonTypeInfo<TValue> jsonTypeInfo)
...... 
// 另外还包括 Stream流, Task异步, 很多个方法, 这里就不一一列举了
(略)

// required 修饰符 或 [JsonRequired] 表示json中必须包含 c#11
public required string Name { get; set; }

[JsonRequired]
public string Name { get; set; }

定制JSON序列化行为

// 常用选项
var options = new JsonSerializerOptions 
{
    WriteIndented = true,  // 缩进
    DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull, // 默认忽略null值
    PropertyNameCaseInsensitive = true, // 如果需要忽略大小写
    IncludeFields = true // 是否处理Fields
}

// 定制JSON序列化行为, 列出了主要的属性[详细见扩展阅读]
// JsonSerializerOptions 类
// JsonSourceGenerationOptionsAttribute 类
AllowTrailingCommas;    // ⭐JSON值列表末尾是否有额外的逗号
Converters;             // ⭐自定义转换器列表, 例如: 枚举类型转换, 日期时间转化等
DefaultBufferSize;     // 创建临时缓冲区时使用的默认缓冲区大小(以字节为单位)
DefaultIgnoreCondition; // ⭐何时忽略具有默认值的属性。 默认值为 Never, 可选: Always(总是),Never(从不),WhenWritingDefault(等于默认值时),WhenWritingNull(为null时)
DictionaryKeyPolicy;   // 用于设置将字典的键名称转化成另外一种格式的策略
IgnoreReadOnlyFields;  // 是否忽略只读(readonly)字段, 默认false
IgnoreReadOnlyProperties; // 是否忽略只读(readonly)属性, 默认false
IncludeFields;         // ⭐是否处理字段。 默认false, 注意默认是不处理字段的
MaxDepth;              // JSON 时允许的最大深度,默认值为 0,表示最大深度为 64。
NumberHandling;        // ⭐设置一个对象, 如何处理数字类型, 例如数字字符串
PreferredObjectCreationHandling; // 首选对象创建处理
PropertyNameCaseInsensitive; // ⭐反序列化时是否使用不区分大小写
PropertyNamingPolicy;   // 将对象上的属性名称转换为其他格式的策略,如大小写转化等
ReadCommentHandling;    // ⭐反序列化期间如何处理注释
UnknownTypeHandling;    // 反序列化期间如何处理声明为 Object 的类型。JsonElement或JsonNode
UnmappedMemberHandling; // 反序列化对象类型时如何处理无法映射到特定实例成员的 JSON 属性。默认忽略0,可以设置异常1
WriteIndented;          // ⭐JSON是否缩进,空格等, 默认是无缩进和空格的, 默认为false
GenerationMode; // 
UseStringEnumConverter; // ⭐字符串枚举, 默认false

System.Text.Json 中使用 JSON 文档对象模型

没有要反序列化到的类型收到的 JSON 没有固定架构时, 我们可以使用JSON 文档对象模型 (DOM)进行反序列化后可随机访问其内的数据。这时可以将josn反序列化为 JsonElement(只读/访问快)JsonNode(读写)。任何有效的 JSON 属性都可以反序列化为 JsonElement 或 JsonNode。

// JsonNode 基本操作
var jsonString = """......""";
// 反序列化
JsonNode forecastNode = JsonNode.Parse(jsonString)!; 
// 访问值
JsonNode temperatureNode = forecastNode!["Temperature"]!; 
int temperatureInt = forecastNode!["Temperature"]!.GetValue<int>();
// 修改值
forecastNode!["Temperature"] = 111;


// JsonElement 基本操作
double sum = 0;
int count = 0;
using (JsonDocument document = JsonDocument.Parse(jsonString))
{
    JsonElement root = document.RootElement;
    JsonElement studentsElement = root.GetProperty("Students");
    foreach (JsonElement student in studentsElement.EnumerateArray())
    {
        if (student.TryGetProperty("Grade", out JsonElement gradeElement))
        {
            sum += gradeElement.GetDouble();
        }
        else
        {
            sum += 70;
        }
        count++;
    }
}

序列化遇到的坑

运行原理
默认情况下,System.Text.Json 使用反射来收集所需的元数据,用于在运行时访问对象属性以进行序列化和反序列化。
System.Text.Json 可以使用 C# 源生成功能来提高性能、降低专用内存使用量以及推动程序集修整,从而缩小应用大小。

Byte[]序列化错误

System.Text.Json 不提供 byte[] 类型的数据直接处理 [1,2,3,4] 这种类型. 而是直接提供 Base64字符串 形式.

$type在JSON中读取类型信息

System.Text.Json 中排除了 TypeNameHandling.All 的等效功能。但其提供了多态反序列化, 无法直接处理$type动态类型.

访问修饰符问题

System.Text.Json 中默认的解析基本都是public, 属性. 需要进行默认行为设置. 但是我在AOT时, 发现初始化类型时, 某些无参数公共方法必须为public

System.Text.Json 源生成器

Newtonsoft.Json 基本使用

扩展阅读

微软官方: 内置转换器源代码
.NET性能系列文章二:Newtonsoft.Json vs. System.Text.Json
从Newtonsoft.Json迁移到 System.Text.Json不简单
微软官方: 从 Newtonsoft.Json 迁移到 System.Text.Json
如何在 System.Text.Json 中使用源生成
微软官方: JsonSerializerOptions 类
微软官方: JsonSourceGenerationOptionsAttribute 类
多态反序列化JsonConverter

此处评论已关闭