一.写在前面

System.Text.Json 是 .NET Core 3 及以上版本内置的 Json 序列化组件,刚推出的时候经常看到踩各种坑的吐槽,现在经过几个版本的迭代优化,提升了易用性,修复了各种问题,是时候考虑使用 System.Text.Json 了。本文将从使用层面来进行对比。

System.Text.Json 在默认情况下十分严格,避免进行任何猜测或解释,强调确定性行为。比如:字符串默认转义,默认不允许尾随逗号,默认不允许带引号的数字等,不允许单引号或者不带引号的属性名称和字符串值。 该库是为了实现性能和安全性而特意这样设计的。Newtonsoft.Json 默认情况下十分灵活。

关于性能,参考 Incerry 的性能测试:.NET性能系列文章二:Newtonsoft.Json vs. System.Text.Json,如果打算使用 .NET 7 不妨考虑一下 System.Text.Json。

Newtonsoft.Json 使用 13.0.2 版本,基于 .NET 7。

二.序列化

1.序列化

定义 Class

public class Cat
{
public string? Name { get; set; }
public int Age { get; set; }
}

序列化

var cat = new Cat() { Name = "xiaoshi", Age = 18 };

Console.WriteLine(Newtonsoft.Json.JsonConvert.SerializeObject(cat));
// output: {"Name":"xiaoshi","Age":18}
Console.WriteLine(System.Text.Json.JsonSerializer.Serialize(cat));
// output: {"Name":"xiaoshi","Age":18}

变化:JsonConvert.SerializeObject()->JsonSerializer.Serialize()

2.忽略属性

2.1 通用

[Newtonsoft.Json.JsonIgnore]
[System.Text.Json.Serialization.JsonIgnore]
public int Age { get; set; }

输出:

var cat = new Cat() { Name = "xiaoshi", Age = 18 };

Console.WriteLine(Newtonsoft.Json.JsonConvert.SerializeObject(cat));
// output: {"Name":"xiaoshi"}
Console.WriteLine(System.Text.Json.JsonSerializer.Serialize(cat));
// output: {"Name":"xiaoshi"}

变化:无

2.2 忽略所有只读属性

代码:

public class Cat
{
public string? Name { get; set; } public int Age { get; } public Cat(int age)
{
Age = age;
}
} var cat = new Cat(18) { Name = "xiaoshi"};
var options = new System.Text.Json.JsonSerializerOptions
{
IgnoreReadOnlyProperties = true,
};
Console.WriteLine(System.Text.Json.JsonSerializer.Serialize(cat, options));
// output: {"Name":"xiaoshi"}

Newtonsoft.Json 需要自定义 ContractResolver 才能实现:https://stackoverflow.com/questions/45010583

2.3 忽略所有 null 属性

代码:

var cat = new Cat() { Name = null,Age = 18};

var op = new Newtonsoft.Json.JsonSerializerSettings()
{
NullValueHandling =NullValueHandling.Ignore
}; Console.WriteLine(Newtonsoft.Json.JsonConvert.SerializeObject(cat,op));
// output: {"Name":"xiaoshi"} var options = new System.Text.Json.JsonSerializerOptions
{
DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull
};
Console.WriteLine(System.Text.Json.JsonSerializer.Serialize(cat, options));
// output: {"Name":"xiaoshi"}

默认情况下两者都是不忽略的,需要自行设置

2.4 忽略所有默认值属性

代码:

var cat = new Cat() { Name = "xiaoshi",Age = 0};

var op = new Newtonsoft.Json.JsonSerializerSettings()
{
DefaultValueHandling = DefaultValueHandling.Ignore
}; Console.WriteLine(Newtonsoft.Json.JsonConvert.SerializeObject(cat,op));
// output: {"Name":"xiaoshi"} var options = new System.Text.Json.JsonSerializerOptions
{
DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingDefault
};
Console.WriteLine(System.Text.Json.JsonSerializer.Serialize(cat, options));
// output: {"Name":"xiaoshi"}

不管是引用类型还是值类型都具有默认值,引用类型为 null,int 类型为 0。

两者都支持此功能。

3.大小写

默认情况下两者序列化都是 Pascal 命名,及首字母大写,在 JavaScript 以及 Java 等语言中默认是使用驼峰命名,所以在实际业务中是离不开使用驼峰的。

代码:

var cat = new Cat() { Name = "xiaoshi",Age = 0};

var op = new Newtonsoft.Json.JsonSerializerSettings()
{
ContractResolver = new CamelCasePropertyNamesContractResolver()
}; Console.WriteLine(Newtonsoft.Json.JsonConvert.SerializeObject(cat,op));
// output: {"name":"xiaoshi","age":0} var options = new System.Text.Json.JsonSerializerOptions
{
PropertyNamingPolicy = JsonNamingPolicy.CamelCase,
};
Console.WriteLine(System.Text.Json.JsonSerializer.Serialize(cat, options));
// output: {"name":"xiaoshi","age":0}

4.字符串转义

System.Text.Json 默认会对非 ASCII 字符进行转义,会将它们替换为 \uxxxx,其中 xxxx 为字符的 Unicode 代码。这是为了安全而考虑(XSS 攻击等),会执行严格的字符转义。而 Newtonsoft.Json 默认则不会转义。

默认:

var cat = new Cat() { Name = "小时",Age = 0};

var op = new Newtonsoft.Json.JsonSerializerSettings()
{
ContractResolver = new CamelCasePropertyNamesContractResolver()
}; Console.WriteLine(Newtonsoft.Json.JsonConvert.SerializeObject(cat,op));
// output: {"name":"小时","age":0} var options = new System.Text.Json.JsonSerializerOptions
{
PropertyNamingPolicy = JsonNamingPolicy.CamelCase,
};
Console.WriteLine(System.Text.Json.JsonSerializer.Serialize(cat, options));
// output: {"name":"\u5C0F\u65F6","age":0}

System.Text.Json 关闭转义:

var options = new System.Text.Json.JsonSerializerOptions
{
PropertyNamingPolicy = JsonNamingPolicy.CamelCase,
Encoder = JavaScriptEncoder.UnsafeRelaxedJsonEscaping,
};
Console.WriteLine(System.Text.Json.JsonSerializer.Serialize(cat, options));
// output: {"name":"小时","age":0}

Newtonsoft.Json 开启转义:

var op = new Newtonsoft.Json.JsonSerializerSettings()
{
ContractResolver = new CamelCasePropertyNamesContractResolver(),
StringEscapeHandling = StringEscapeHandling.EscapeNonAscii
}; Console.WriteLine(Newtonsoft.Json.JsonConvert.SerializeObject(cat,op));
// output: {"name":"\u5c0f\u65f6","age":0}

详细说明:如何使用 System.Text.Json 自定义字符编码

5.自定义转换器

自定义转换器 Converter,是我们比较常用的功能,以自定义 Converter 来输出特定的日期格式为例。

Newtonsoft.Json:

public class CustomDateTimeConverter : IsoDateTimeConverter
{
public CustomDateTimeConverter()
{
DateTimeFormat = "yyyy-MM-dd";
} public CustomDateTimeConverter(string format)
{
DateTimeFormat = format;
}
} // test
var op = new Newtonsoft.Json.JsonSerializerSettings()
{
ContractResolver = new CamelCasePropertyNamesContractResolver(),
Converters = new List<JsonConverter>() { new CustomDateTimeConverter() }
}; Console.WriteLine(Newtonsoft.Json.JsonConvert.SerializeObject(cat,op));
// output: {"name":"xiaoshi","now":"2023-02-13","age":0}

System.Text.Json:

public class CustomDateTimeConverter : JsonConverter<DateTime>
{
public override DateTime Read(
ref Utf8JsonReader reader,
Type typeToConvert,
JsonSerializerOptions options) =>
DateTime.ParseExact(reader.GetString()!,
"yyyy-MM-dd", CultureInfo.InvariantCulture); public override void Write(
Utf8JsonWriter writer,
DateTime dateTimeValue,
JsonSerializerOptions options) =>
writer.WriteStringValue(dateTimeValue.ToString(
"yyyy-MM-dd", CultureInfo.InvariantCulture));
} // test
var options = new System.Text.Json.JsonSerializerOptions
{
PropertyNamingPolicy = JsonNamingPolicy.CamelCase,
Converters = { new CustomDateTimeConverter() }
};
Console.WriteLine(System.Text.Json.JsonSerializer.Serialize(cat, options));
// output: {"name":"xiaoshi","age":0,"now":"2023-02-13"}

两者的使用方法都是差不多的,只是注册优先级有所不同。

Newtonsoft.Json:属性上的特性>类型上的特性>Converters 集合

System.Text.Json:属性上的特性>Converters 集合>类型上的特性

官方文档:如何编写用于 JSON 序列化的自定义转换器

6.循环引用

有如下定义:

public class Cat
{ public string? Name { get; set; } public int Age { get; set; } public Cat Child { get; set; } public Cat Parent { get; set; }
} var cat1 = new Cat() { Name = "xiaoshi",Age = 0};
var cat2 = new Cat() { Name = "xiaomao",Age = 0}; cat1.Child = cat2;
cat2.Parent = cat1;

序列化 cat1 默认两者都会抛出异常,如何解决?

Newtonsoft.Json:

var op = new Newtonsoft.Json.JsonSerializerSettings()
{
ContractResolver = new CamelCasePropertyNamesContractResolver(),
ReferenceLoopHandling = ReferenceLoopHandling.Ignore,
}; Console.WriteLine(Newtonsoft.Json.JsonConvert.SerializeObject(cat1,op));

设置 ReferenceLoopHandling.Ignore 即可。

System.Text.Json:

var options = new System.Text.Json.JsonSerializerOptions
{
PropertyNamingPolicy = JsonNamingPolicy.CamelCase,
ReferenceHandler = ReferenceHandler.IgnoreCycles
};
Console.WriteLine(System.Text.Json.JsonSerializer.Serialize(cat1, options));

等效设置

System.Text.Json Newtonsoft.Json
ReferenceHandler = ReferenceHandler.Preserve PreserveReferencesHandling=PreserveReferencesHandling.All
ReferenceHandler = ReferenceHandler.IgnoreCycles ReferenceLoopHandling = ReferenceLoopHandling.Ignore

详细说明:如何在 System.Text.Json 中保留引用

8.支持字段(Field)

在序列化和反序列时支持字段,字段不能定义为 private。

public class Cat
{ public string? Name { get; set; } public int _age; public Cat()
{
_age = 13;
}
} var op = new Newtonsoft.Json.JsonSerializerSettings()
{
ContractResolver = new Newtonsoft.Json.Serialization.CamelCasePropertyNamesContractResolver(),
}; Console.WriteLine(Newtonsoft.Json.JsonConvert.SerializeObject(cat,op));
// output: {"_age":13,"name":"xiaoshi"} var options = new System.Text.Json.JsonSerializerOptions
{
PropertyNamingPolicy = System.Text.Json.JsonNamingPolicy.CamelCase,
IncludeFields = true // 或者 JsonIncludeAttribute
}; Console.WriteLine(System.Text.Json.JsonSerializer.Serialize(cat, options));
// output: {"name":"xiaoshi","_age":13}

System.Text.Json 默认不支持直接序列化和反序列化字段,需要设置 IncludeFields = true或者 JsonIncludeAttribute 特性。

8.顺序

自定义属性在 Json 输出中的顺序:

public class Cat
{ public string? Name { get; set; } [System.Text.Json.Serialization.JsonPropertyOrder(0)]
[Newtonsoft.Json.JsonProperty(Order = 0)]
public int Age { get; set; }
}

System.Text.Json 使用 JsonPropertyOrder,Newtonsoft.Json 使用 JsonProperty(Order)

9.字节数组

Newtonsoft.Json 不支持直接序列化为字节数组,System.Text.Json 支持直接序列化为 UTF-8 字节数组。

System.Text.Json:

var bytes = JsonSerializer.SerializeToUtf8Bytes(cat)

序列化为 UTF-8 字节数组比使用基于字符串的方法大约快 5-10%。

10.重命名

public class Cat
{ public string? Name { get; set; } [System.Text.Json.Serialization.JsonPropertyName("catAge")]
[Newtonsoft.Json.JsonProperty("catAge")]
public int Age { get; set; }
}

重命名 Json 属性名称,System.Text.Json 使用 JsonPropertyName,Newtonsoft.Json 使用 JsonProperty

11.缩进

Newtonsoft.Json:

var op = new Newtonsoft.Json.JsonSerializerSettings()
{
ContractResolver = new Newtonsoft.Json.Serialization.CamelCasePropertyNamesContractResolver(),
// this option
Formatting = Newtonsoft.Json.Formatting.Indented,
}; Console.WriteLine(Newtonsoft.Json.JsonConvert.SerializeObject(cat,op));
// output:
// {
// "name": "xiaoshi",
// "catAge": 0
// }

System.Text.Json

var options = new System.Text.Json.JsonSerializerOptions
{
PropertyNamingPolicy = System.Text.Json.JsonNamingPolicy.CamelCase,
// this option
WriteIndented = true,
}; Console.WriteLine(System.Text.Json.JsonSerializer.Serialize(cat, options));
// output:
// {
// "name": "xiaoshi",
// "catAge": 0
// }

三.反序列化

1.反序列化

定义:

public class Cat
{
public string? Name { get; set; }
public int Age { get; set; }
} var json = """{"name":"xiaoshi","age":16} """;
Cat cat;

Newtonsoft.Json:

var op = new Newtonsoft.Json.JsonSerializerSettings()
{
ContractResolver = new Newtonsoft.Json.Serialization.CamelCasePropertyNamesContractResolver(),
}; cat=Newtonsoft.Json.JsonConvert.DeserializeObject<Cat>(json, op); Console.WriteLine($"CatName {cat.Name}, Age {cat.Age}");
// output: CatName xiaoshi, Age 16

System.Text.Json:

var options = new System.Text.Json.JsonSerializerOptions
{
PropertyNamingPolicy = System.Text.Json.JsonNamingPolicy.CamelCase,
}; cat=System.Text.Json.JsonSerializer.Deserialize<Cat>(json,options); Console.WriteLine($"CatName {cat.Name}, Age {cat.Age}");
// output: CatName xiaoshi, Age 16

变化 JsonConvert.DeserializeObject->JsonSerializer.Deserialize

2.允许注释

在反序列化过程中,Newtonsoft.Json 在默认情况下会忽略 JSON 中的注释。 System.Text.Json 默认是对注释引发异常,因为 System.Text.Json 规范不包含它们。

var json = """
{
"name": "xiaoshi", // cat name
"age": 16
}
""";
Cat cat; var options = new System.Text.Json.JsonSerializerOptions
{
PropertyNamingPolicy = System.Text.Json.JsonNamingPolicy.CamelCase,
// 不设置会引发异常
ReadCommentHandling = System.Text.Json.JsonCommentHandling.Skip,
}; cat=System.Text.Json.JsonSerializer.Deserialize<Cat>(json,options); Console.WriteLine($"CatName {cat.Name}, Age {cat.Age}");
// output: CatName xiaoshi, Age 16

设置 ReadCommentHandling=JsonCommentHandling.Skip即可忽略注释。

详细说明:如何使用 System.Text.Json 支持某种无效的 JSON

3.尾随逗号

尾随逗号即 Json 末尾为逗号:

无尾随逗号:

{
"name": "xiaoshi",
"age": 16
}

有尾随逗号:

{
"name": "xiaoshi",
"age": 16,
}

System.Text.Json 默认对尾随逗号引发异常,可以通过 AllowTrailingCommas = true 来设置

var json = """
{
"name": "xiaoshi",
"age": 16,
}
""";
Cat cat; var options = new System.Text.Json.JsonSerializerOptions
{
PropertyNamingPolicy = System.Text.Json.JsonNamingPolicy.CamelCase,
AllowTrailingCommas = true,
}; cat=System.Text.Json.JsonSerializer.Deserialize<Cat>(json,options); Console.WriteLine($"CatName {cat.Name}, Age {cat.Age}");
// output: CatName xiaoshi, Age 16

尾随逗号一般和允许注释一起使用,因为行注释必须写在引号以后。

4.带引号数字

在标准 Json 里,数字类型是不带引号的,如:{"Name":"xiaoshi","Age":18},但有时我们可能会遇到不标准的异类,Newtonsoft.Json 默认是支持直接反序列化为数字类型的,而 System.Text.Json 基于严格的标准出发,默认不支持,但是可配置。

var options = new System.Text.Json.JsonSerializerOptions
{
PropertyNamingPolicy = JsonNamingPolicy.CamelCase,
NumberHandling = JsonNumberHandling.AllowReadingFromString
}; // C# 11 原始字符串
var json="""{"name":"xiaoshi","age":"13"}"""; Console.WriteLine(System.Text.Json.JsonSerializer.Deserialize<Cat>(json, options).Age);
// output: 13

设置 NumberHandling = JsonNumberHandling.AllowReadingFromString 即可。

5.Json DOM

不直接反序列化为对象,比如 Newtonsoft.Json 里的 JObject.Parse。在 System.Text.Json 里可以使用 JsonNode、JsonDocument、JsonObject 等。

详细说明:如何在 System.Text.Json 中使用 JSON DOM、Utf8JsonReader 和 Utf8JsonWriter

6.JsonConstructor

通过 JsonConstructor 特性指定使用的反序列化构造方法,两者是一致的。

四.无法满足的场景

官方给出了对比 Newtonsoft.Json 没有直接支持的功能,但是可以通过自定义 Converter 来支持。如果需要依赖这部分功能,那么在迁移过程中需要进行代码更改。

Newtonsoft.Json System.Text.Json
支持范围广泛的类型
将推断类型反序列化为 object 属性
将 JSON null 文本反序列化为不可为 null 的值类型
DateTimeZoneHandlingDateFormatString 设置
JsonConvert.PopulateObject 方法
ObjectCreationHandling 全局设置
在不带 setter 的情况下添加到集合
对属性名称采用蛇形命名法

以下功能 System.Text.Json 不支持:

Newtonsoft.Json System.Text.Json
支持 System.Runtime.Serialization 特性
MissingMemberHandling 全局设置
允许不带引号的属性名称
字符串值前后允许单引号
对字符串属性允许非字符串 JSON 值
TypeNameHandling.All 全局设置
支持 JsonPath 查询
可配置的限制

五.结束

在 Ms Learn(Docs) 和 Google 之间频繁切换写完了这篇文章,希望对大家在从 Newtonsoft.Json 迁移到 System.Text.Json 有所帮助。就我个人而言我是打算使用 System.Text.Json 了。

参考资料

从 Newtonsoft.Json 迁移到 System.Text.Json的更多相关文章

  1. C# Serialization performance in System.Runtime.Serialization.Formatters.Binary.BinaryFormatter,Newtonsoft.Json.JsonConvert and System.Text.Json.JsonSerializer.Serialize

    In .net core 3.0 using System;using System.Collections.Generic;using System.Collections;using System ...

  2. 【译】System.Text.Json 的下一步是什么

    .NET 5.0 最近发布了,并带来了许多新特性和性能改进.System.Text.Json 也不例外.我们改进了性能和可靠性,并使熟悉 Newtonsoft.Json 的人更容易采用它.在这篇文章中 ...

  3. Net core 2.x 升级 3.0 使用自带 System.Text.Json 时区 踩坑经历

    .Net Core 3.0 更新的东西很多,这里就不多做解释了,官方和博园大佬写得很详细 关于 Net Core 时区问题,在 2.1 版本的时候,因为用的是 Newtonsoft.Json,配置比较 ...

  4. In .net 4.8,calculate the time cost of serialization in BinaryFormatter,NewtonSoft.json,and System.Text.Json.JsonSerializer.Serialize

    using ConsoleApp390.Model; using Newtonsoft.Json; using System; using System.Collections.Generic; us ...

  5. .NET Core 3.0 System.Text.Json 和 Newtonsoft.Json 行为不一致问题及解决办法

    行为不一致 .NET Core 3.0 新出了个内置的 JSON 库, 全名叫做尼古拉斯 System.Text.Json - 性能更高占用内存更少这都不是事... 对我来说, 很多或大或小的项目能少 ...

  6. .NET性能系列文章二:Newtonsoft.Json vs. System.Text.Json

    微软终于追上了? 图片来自 Glenn Carstens-Peters Unsplash 欢迎来到.NET性能系列的另一章.这个系列的特点是对.NET世界中许多不同的主题进行研究.基准和比较.正如标题 ...

  7. [译]试用新的System.Text.Json API

    译注 可能有的小伙伴已经知道了,在.NET Core 3.0中微软加入了对JSON的内置支持. 一直以来.NET开发者们已经习惯使用Json.NET这个强大的库来处理JSON. 那么.NET为什么要增 ...

  8. .netcore3.0 System.Text.Json 日期格式化

    .netcore3.0 的json格式化不再默认使用Newtonsoft.Json,而是使用自带的System.Text.Json来处理. 理由是System.Text.Json 依赖更少,效率更高. ...

  9. .net core中关于System.Text.Json的使用

    在.Net Framework的时候序列化经常使用Newtonsoft.Json插件来使用,而在.Net Core中自带了System.Text.Json,号称性能更好,今天抽空就来捣鼓一下. 使用起 ...

  10. System.Text.Json 自定义Converter实现时间转换

    Newtonsoft.Json与System.Text.Json区别 在 Newtonsoft.Json中可以使用例如 .AddJsonOptions(options => { options. ...

随机推荐

  1. Go语言核心36讲08

    在上一篇文章,我们一直都在围绕着可重名变量,也就是不同代码块中的重名变量,进行了讨论. 还记得吗?最后我强调,如果可重名变量的类型不同,那么就需要引起我们的特别关注了,它们之间可能会存在"屏 ...

  2. centos7 uwsgi 加入系统服务

    生产环境中采用nginx + uwsgi + django 来部署web服务,这里需要实现uwsgi的启动和停止,简单的处理方式可以直接在命令行中启动和kill掉uwsgi服务,但为了更安全.方便的管 ...

  3. C温故补缺(十四):内存管理

    内存管理 stdlib库中有几个内存管理相关的函数 序号 函数和描述 1 void *calloc(int num, int size);在内存中动态地分配 num 个长度为size 个字节 的连续空 ...

  4. ElasticSearch7.6.1学习笔记-狂神

    ElasticSearch:7.6.1 https://gitee.com/yujie.louis/elastic-search 笔记,代码,安装包等 什么是ElasticSearch? Elasti ...

  5. 协程Part1-boost.Coroutine.md

    首先,在计算机科学中 routine 被定义为一系列的操作,多个 routine 的执行形成一个父子关系,并且子 routine 一定会在父 routine 结束前结束,也就是一个个的函数执行和嵌套执 ...

  6. 【Hadoop学习】上:组成介绍、生态体系、环境准备、不同运行模式测试

    目录: 入门(上) HDFS(中) MapReduce&Yarn(下) 优化(补充) 一.大数据概述 概念 特点:VVVV 应用场景 二.Hadoop到大数据生态 1.Hadoop生态圈.发展 ...

  7. MYSQL-INNODB索引构成详解

    作者:郑啟龙 摘要: 对于MYSQL的INNODB存储引擎的索引,大家是不陌生的,都能想到是 B+树结构,可以加速SQL查询.但对于B+树索引,它到底"长"得什么样子,它具体如何由 ...

  8. 记开源项目:DotNetCore.CAP.MySql问题分析:only mysqlparameter objects may be stored

    1.  简介 最近在学习分布式事务及解决方案,最终找到了开源项目DotNetCore.CAP ,因为自己用的MySql数据库比较多.于是也使用MySQL+EFCore+RabbitMQ+CAP实现事务 ...

  9. uni-app 动态修改主题色

    老是碰到初版制作完成没多久,就整一出说什么要更改整个项目的色彩体系.真的是宝宝心里苦啊! 起初都是通过uni项目自带的uni.scss中定义,在替换页面上对应的css.以便于达到一次性修改整体布局的样 ...

  10. mysql数据库(字段约束条件)

    什么是字段约束 字段约束就是将字段的内容定一个规则,我们要按照规则办事 约束 描述 关键字 非空约束 限制该字段的数据不能为null not null 唯一约束 保证该字段的所有数据都是唯一.不重复的 ...