.NET 5的System.Text.Json的JsonDocument类讲解
本文内容来自我写的开源电子书《WoW C#》,现在正在编写中,可以去WOW-Csharp/学习路径总结.md at master · sogeisetsu/WOW-Csharp (github.com)来查看编写进度。预计2021年年底会完成编写,2022年2月之前会完成所有的校对和转制电子书工作,争取能够在2022年将此书上架亚马逊。编写此书的目的是因为目前.NET市场相对低迷,很多优秀的书都是基于.NET framework框架编写的,与现在的.NET 6相差太大,正规的.NET 5学习教程现在几乎只有MSDN,可是MSDN虽然准确优美但是太过琐碎,没有过阅读开发文档的同学容易一头雾水,于是,我就编写了基于.NET 5的《WoW C#》。本人水平有限,欢迎大家去本书的开源仓库sogeisetsu/WOW-Csharp关注、批评、建议和指导。
解析JSON字符串
注意本文重点讲解的是解析,而非序列化,关于序列化请查看WOW-Csharp/数组和集合.md at master · sogeisetsu/WOW-Csharp (github.com)。
c#解析json字符串在.NET 5的首选是使用System.Text.Json的JsonDocument类。
格式化输出
想要格式化输出,需要先把字符串转变成一个JsonDocument实例化对象,然后在序列化这个对象的时候指定JsonSerializerOptions为整齐打印。
// 先定义一个json字符串
string jsonText = "{\"ClassName\":\"Science\",\"Final\":true,\"Semester\":\"2019-01-01\",\"Students\":[{\"Name\":\"John\",\"Grade\":94.3},{\"Name\":\"James\",\"Grade\":81.0},{\"Name\":\"Julia\",\"Grade\":91.9},{\"Name\":\"Jessica\",\"Grade\":72.4},{\"Name\":\"Johnathan\"}],\"Teacher'sName\":\"Jane\"}";
Console.WriteLine(jsonText);
// 将表示单个 JSON 字符串值的文本分析为 JsonDocument
JsonDocument jsonDocument = JsonDocument.Parse(jsonText);
// 序列化
string formatJson = JsonSerializer.Serialize(jsonDocument, new JsonSerializerOptions()
{
// 整齐打印
WriteIndented = true,
Encoder = JavaScriptEncoder.Create(UnicodeRanges.All)
});
// 格式化输出
Console.WriteLine(formatJson);
这个比较麻烦,我们可以将其制作成拓展方法。
internal static class JsonDocumentExtensions
{
internal static string JDFormatToString(this JsonDocument jsonDocument)
{
return JsonSerializer.Serialize(jsonDocument, new JsonSerializerOptions()
{
WriteIndented = true,
Encoder=JavaScriptEncoder.Create(UnicodeRanges.All)
});
}
internal static string TOJsonString(this string str)
{
JsonDocument jsonDocument = JsonDocument.Parse(str);
return JsonSerializer.Serialize(jsonDocument, new JsonSerializerOptions()
{
WriteIndented = true,
Encoder = JavaScriptEncoder.Create(UnicodeRanges.All)
});
}
}
这样就可以用类似于下面的方法直接调用了:
// jsondocument 格式化输出为json字符串
string a = jsonDocument.JDFormatToString();
// 格式化字符串
string b = jsonText.TOJsonString();
JSON DOM
对JSON进行DOM操作.NET提供了两种官方方法,分别是JsonDocumenth和JSonNode,其中JsonNode提供了创建可变 DOM 的能力,它更加强大和简单,但是JsonNode是.NET 6的内容,鉴于.NET 6的稳定版刚刚发布,所以本文还是讲解JsonDocumenth。.NET 6是一个LTS版本,它于2021年11月8日正式发布,会支持到2024年11月8日,详情可以查看.NET and .NET Core official support policy (microsoft.com),笔者会在.NET 5结束支持之前(2022 年 5 月 8 日)写一篇关于JSonNode的文章作为本文的Patch。
JsonDocument 提供了使用 Utf8JsonReader 构建只读 DOM 的能力。可以通过 JsonElement 类型访问组成有效负载的 JSON 元素。 JsonElement 类型提供数组和对象枚举器以及用于将 JSON 文本转换为常见 .NET 类型的 API。 JsonDocument 公开一个 RootElement 属性。
关于JsonDocument,MSDN上有一篇非常好的讲解文章How to use a JSON document, Utf8JsonReader, and Utf8JsonWriter in System.Text.Json | Microsoft Docs,这一部分笔者更多的是采用MSDN上面的例子,此部分在某种意义上可以看作对How to use a JSON document, Utf8JsonReader, and Utf8JsonWriter in System.Text.Json的翻译。
用作例子的Json数据如下:
{
"Class Name": "Science",
"Teacher's Name": "Jane",
"Semester": "2019-01-01",
"Students": [
{
"Name": "John",
"Grade": 94.3
},
{
"Name": "James",
"Grade": 81
},
{
"Name": "Julia",
"Grade": 91.9
},
{
"Name": "Jessica",
"Grade": 72.4
},
{
"Name": "Johnathan"
}
],
"Final": true
}
方法概述
先将其反序列化成JsonDocument对象:
Console.WriteLine("对json字符串进行dom操作");
string jsonText = "{\"ClassName\":\"Science\",\"Final\":true,\"Semester\":\"2019-01-01\",\"Students\":[{\"Name\":\"John\",\"Grade\":94.3},{\"Name\":\"James\",\"Grade\":81.0},{\"Name\":\"Julia\",\"Grade\":91.9},{\"Name\":\"Jessica\",\"Grade\":72.4},{\"Name\":\"Johnathan\"}],\"Teacher'sName\":\"Jane\"}";
JsonDocument jsonDocument = JsonDocument.Parse(jsonText);
获取当前JsonDocument的根元素(JsonElement类型):
JsonElement root = jsonDocument.RootElement;
RootElement是json数据的根,后续所有的操作都与其息息相关。
GetProperty根据键名,获取根元素下的元素(JsonElement类型):
JsonElement students = root.GetProperty("Students");
GetArrayLength获取数组属性的长度(如果将此方法用于非数组类型的json值会报错):
// 获取数组长度
Console.WriteLine(students.GetArrayLength());
可以对值类型为数组的JsonElement使用EnumerateArray ()方法来获取枚举器(IEnumerator),从而进行循环操作:
// EnumerateArray 一个枚举器,它用于枚举由该 JsonElement 表示的 JSON 数组中的值。
foreach (JsonElement student in students.EnumerateArray())
{
Console.WriteLine(student);
Console.WriteLine(student.ValueKind);// object
// 获取属性Name的string值
Console.WriteLine(student.GetProperty("Name").GetString());
}
获取值
对于JsonElement获取元素值的方式比较复杂,首先需要知道值的类型,然后根据值的类型来选择方法,方法列表可以从JsonElement 结构 (System.Text.Json) | Microsoft Docs查看,比如值的类型是double,就使用GetDouble()来获取json数字(double类型):
Console.WriteLine(student.GetProperty("Grade").GetDouble());
如果当前的值是string类型,就使用GetString()来获取json字符串:
Console.WriteLine(semester.GetString());
总之,为了获取准确的json值,必须提前知道json值的类型。这样才会最大限度保证不会出错。
获取和判读Json值的类型
可以使用JsonElement的ValueKind属性来获取值类型:
Console.WriteLine(students.ValueKind);
ValueKind属性的类型是名为JsonValueKind的枚举类型。JsonValueKind的字段如下:
| Array | 2 | JSON 数组。 |
|---|---|---|
| False | 6 | JSON 值 false。 |
| Null | 7 | JSON 值 null。 |
| Number | 4 | JSON 数字。 |
| Object | 1 | JSON 对象。 |
| String | 3 | JSON 字符串。 |
| True | 5 | JSON 值 true。 |
| Undefined | 0 | 没有值(不同于 Null)。 |
故可以使用像下面这种判断相等的方式来检测数据类型:
Console.WriteLine(students.ValueKind == JsonValueKind.Array); // true
检查属性是否存在
可以使用TryGetProperty方法来根据键来判断元素是否存在,demo如下:
root.TryGetProperty("Name", out JsonElement value)
存在就返回true,不存在就返回false,TryGetProperty的第一个参数是键名,第二个参数是用out关键字修饰的JsonElement类型,如果此属性存在,会将其值分配给 value 参数。
借助TryGetProperty既可以判断属性是否存在,也能在属性存在的情况下获取该属性对应的JsonElement,demo:
// 检查存在
Console.WriteLine(root.TryGetProperty("Semester", out JsonElement value));
// 使用被分配的JsonElement
Console.WriteLine(value.GetString());
MSDN的demo更具备实用性:
if (student.TryGetProperty("Grade", out JsonElement gradeElement))
{
sum += gradeElement.GetDouble();
}
else
{
sum += 70;
}
如何在 JsonDocument 和 JsonElement 中搜索子元素
对 JsonElement 的搜索需要对属性进行顺序搜索,因此速度相对较慢(例如在使用 TryGetProperty 时)。 System.Text.Json 旨在最小化初始解析时间而不是查找时间。因此,在搜索 JsonDocument 对象时使用以下方法来优化性能:
- 使用内置的枚举器(EnumerateArray 和 EnumerateObject)而不是自己做索引或循环。不要对数组形式的
JsonElement进行诸如students[1]的操作。 - 不要使用 RootElement 通过每个属性对整个 JsonDocument 进行顺序搜索。相反,根据 JSON 数据的已知结构搜索嵌套的 JSON 对象。也就是说不要进行不必要的搜索,要根据自己对所操作的JSON的最大了解程度来进行搜索,比如明知道某个json数组里面没有自己想要的数据,就别去对它进行一遍又一遍的搜索
JsonDocument 是非托管资源
因为是非托管资源,其不会在CLR中被托管。JsonDocument 类型实现了 IDisposable ,为了内存的整洁应该在using块中使用,使其在使用完之后立即被释放资源,就像下面这样:
using (JsonDocument jsonDocument = JsonDocument.Parse(jsonText))
{
// 对jsonDocument的各种操作
}
前面笔者没有将其放在using块里面纯粹是为了演示方便,请大家注意在using块中使用JsonDocument 才是推荐的用法。
函数调用JsonDocument
如果因为某种原因需要将JsonDocument转给方法调用者,则仅从您的 API 返回 JsonDocument,在大多数情况下,这不是必需的。返回返回 RootElement 的 Clone就好,它是一个 JsonElement。demo如下:
public JsonElement LookAndLoad(JsonElement source)
{
string json = File.ReadAllText(source.GetProperty("fileName").GetString());
using (JsonDocument doc = JsonDocument.Parse(json))
{
return doc.RootElement.Clone();
}
}
如果你所编写的方法收到一个 JsonElement 并返回一个子元素,则没有必要返回子元素的克隆。调用者负责使传入的 JsonElement 所属的 JsonDocument 保持活动状态即可。demo如下:
public JsonElement ReturnFileName(JsonElement source)
{
return source.GetProperty("fileName");
}
LICENSE
已将所有引用其他文章之内容清楚明白地标注,其他部分皆为作者劳动成果。对作者劳动成果做以下声明:
copyright 2021 苏月晟,版权所有。

本作品由苏月晟采用知识共享署名-非商业性使用-相同方式共享 4.0 国际许可协议进行许可。
.NET 5的System.Text.Json的JsonDocument类讲解的更多相关文章
- [译]试用新的System.Text.Json API
译注 可能有的小伙伴已经知道了,在.NET Core 3.0中微软加入了对JSON的内置支持. 一直以来.NET开发者们已经习惯使用Json.NET这个强大的库来处理JSON. 那么.NET为什么要增 ...
- 使用System.Text.Json处理Json文档以及部分坑
System.Text.Json处理Json文档需要用到JsonDocument,JsonElement,JsonProperty. JsonDocument就是一个表示Json文档的东西,JsonE ...
- 在.Net Core 3.0中尝试新的System.Text.Json API
.NET Core 3.0提供了一个名为System.Text.Json的全新命名空间,它支持reader/writer,文档对象模型(DOM)和序列化程序.在此博客文章中,我将介绍它如何工作以及如何 ...
- 【译】System.Text.Json 的下一步是什么
.NET 5.0 最近发布了,并带来了许多新特性和性能改进.System.Text.Json 也不例外.我们改进了性能和可靠性,并使熟悉 Newtonsoft.Json 的人更容易采用它.在这篇文章中 ...
- .netcore3.0 System.Text.Json 日期格式化
.netcore3.0 的json格式化不再默认使用Newtonsoft.Json,而是使用自带的System.Text.Json来处理. 理由是System.Text.Json 依赖更少,效率更高. ...
- 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 ...
- 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 ...
- .NET Core 内置的 System.Text.Json 使用注意
System.Text.Json 是 .NET Core 3.0 新引入的高性能 json 解析.序列化.反序列化类库,武功高强,但毕竟初入江湖,炉火还没纯青,使用时需要注意,以下是我们在实现使用中遇 ...
- Net core 2.x 升级 3.0 使用自带 System.Text.Json 时区 踩坑经历
.Net Core 3.0 更新的东西很多,这里就不多做解释了,官方和博园大佬写得很详细 关于 Net Core 时区问题,在 2.1 版本的时候,因为用的是 Newtonsoft.Json,配置比较 ...
随机推荐
- SharkCTF2021 Babyhttp && get_or_lose
两道web. Babyhttp: 直接dirsearch,发现同时存在git和bak泄露:经验证,git的没用. 访问index.php.bak, 下载源码: 抓包,改包,发包即可. get_or_l ...
- 在Windows上使用Docker 创建MongoDB 副本集的极简方法(翻译)
这篇博客介绍下在Windows上使用Docker 创建MongoDB 三节点副本集的最简单的方法.以下命令需要Docker for Windows并使用Linux 容器. 1: 为每个节点创建数据卷 ...
- C/C++ 数据类型 表示最大 最小数值 探讨
C/C++中存储数字格式有整型和浮点型 字符型数据本质上也是以整型存储 整型 对于整型数据,最大值最小值很好计算 先确定对应数据型在本地所占用的字节数,同一数据型由于系统或者编译器的不同,所占字节不同 ...
- [对对子队]会议记录5.24(Scrum Meeting10)
今天已完成的工作 梁河览 工作内容:修改第一关的新手引导 相关issue:优化初步导出版本 相关签入:fix:改进第一关的新手引导 何瑞 工作内容:为加速按钮添加锚点 相关issu ...
- 【二食堂】Alpha - Scrum Meeting 4
Scrum Meeting 4 例会时间:4.14 12:30 - 12:50 进度情况 组员 昨日进度 今日任务 李健 1. 主页面的搭建工作issue 1. 完成主页搭建**issue2. 与后端 ...
- 基于docker-compose搭建sonarqube代码质量检测平台
一.需求 在我们开发的过程中,难免有时候代码写的不规范,或存在一些静态的bug问题,这个时候一个良好的代码检查工具就很有必要,而sonarqube正好可以满足整个要求. 二. docker-compo ...
- spring social实现百度登录
在早期我写过一篇spring social理解的文章,介绍了一些spring social的概念,但是没有提供一个例子.在这篇博客中,提供一个简单的spring social的例子,实现 百度登录,那 ...
- 冲刺noip2021模拟16
T1 树上的数 考场上比较脑瘫没有想到直接dfs就行了这样是O(n+m)的,傻不拉几地多添了个log, 不过因为accoder的评测机太弱了,绝大多数人的正解都是60分,所以没有什么差别: 直接dfs ...
- 2021.9.18考试总结[NOIP模拟56]
T1 爆零 贪心地想,肯定要先走完整个子树再走下一个,且要尽量晚地走深度大的叶子.所以对每个点的儿子以子树树高为关键字排序$DFS$即可. 也可$DP$. $code:$ T1 #include< ...
- IOC和DI之刨根问底之第一节
很多freshman上来就想搞清楚什么是IOC和DI,其实很多先进的理论和技术都在老的基础上升华出来的,最终目的是为了解放生产力. 所以先来说说下面两点基础知识: Direct Dependency( ...