一.写在前面#

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. 从 Newtonsoft.Json 迁移到 System.Text.Json

    一.写在前面 System.Text.Json 是 .NET Core 3 及以上版本内置的 Json 序列化组件,刚推出的时候经常看到踩各种坑的吐槽,现在经过几个版本的迭代优化,提升了易用性,修复了 ...

  2. 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 ...

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

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

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

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

  5. 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 ...

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

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

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

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

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

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

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

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

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

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

随机推荐

  1. Elasticsearch与kibana的单机安装

    前言 本文为纯实操记录,以供需要时查阅. 对应版本为7.3,jdk版本1.8 Elasticsearch安装 Elasticsearch官网:https://www.elastic.co/cn/pro ...

  2. 微信小程序三种授权登录的方式

    经过一段时间对微信小程序的研发后 总结出以下三种授权登录的方式,我给他们命名为'一次性授权''永久授权''不授权' 1.一次性授权常规写法,需要获取用户公开信息(头像,昵称等)时,判断调取授权登录接口 ...

  3. kolla-ansible部署OpenStack Train版技术方案

    简单架构示意 项目目标 1. 实现容器化部署docker+ Ansible+openstack-tarin 2. 使用keeplived监控nova服务实现在单台服务器宕机的情况下能迅速切断连接减轻平 ...

  4. 力扣178(MySQL)-分数排名(中等)

    题目: 表: Scores 编写 SQL 查询对分数进行排序.排名按以下规则计算: 分数应按从高到低排列.如果两个分数相等,那么两个分数的排名应该相同.在排名相同的分数后,排名数应该是下一个连续的整数 ...

  5. anconda配置tensorflow环境

    一.anconda的安装 1.进入Anaconda官网并按照电脑配置选择合适的安装包 Anaconda官网:https://www.anaconda.com/ 点击进入 不同的三个版本,分别是wind ...

  6. 连续两年入选Gartner公共云容器,阿里云在边缘容器方面做了什么?

    最近,Gartner发布了2020年公共云容器报告,阿里云连续两年成为唯一入选的中国企业.报告显示,阿里云容器服务在中国市场表现强劲,产品形态丰富,在 Serverless 容器.服务网格.安全沙箱容 ...

  7. 你不知道的 HTTPS 压测

    ​简介:随着互联网安全规范的普及,使用 HTTPS 技术进行通信加密,实现网站和 APP 的可信访问,已经成为公认的安全标准.本文将介绍针对 HTTPS 协议做压力测试的关注点,以及使用 PTS 做 ...

  8. 为余势负天工背,云原生内存数据库Tair助力用户体验优化

    ​简介:作为双11大促承载流量洪峰的利器,Tair支撑了电商交易核心体验场景.不仅在数十亿QPS的峰值下保持着亚毫秒级别的顺滑延迟,同时在电商交易核心体验场景上也做出了技术创新. ​ 作者 | 漠冰 ...

  9. ACMMM2021|在多模态训练中融入“知识+图谱”:方法及电商应用实践

    ​简介: 随着人工智能技术的不断发展,知识图谱作为人工智能领域的知识支柱,以其强大的知识表示和推理能力受到学术界和产业界的广泛关注.近年来,知识图谱在语义搜索.问答.知识管理等领域得到了广泛的应用. ...

  10. SemanticFunction 自然语言函数

    本文将和大家介绍 LLM 的魔法,通过自然语言编程的方式开发 SemanticFunction 函数 大家都知道,编程里面的函数可以是一个完成某个功能的逻辑片段,绝大部分的函数都需要使用人类不友好的编 ...