本文内容来自我写的开源电子书《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.JsonJsonDocument类。

格式化输出

想要格式化输出,需要先把字符串转变成一个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值的类型

可以使用JsonElementValueKind属性来获取值类型:

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

如何在 JsonDocumentJsonElement 中搜索子元素

对 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类讲解的更多相关文章

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

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

  2. 使用System.Text.Json处理Json文档以及部分坑

    System.Text.Json处理Json文档需要用到JsonDocument,JsonElement,JsonProperty. JsonDocument就是一个表示Json文档的东西,JsonE ...

  3. 在.Net Core 3.0中尝试新的System.Text.Json API

    .NET Core 3.0提供了一个名为System.Text.Json的全新命名空间,它支持reader/writer,文档对象模型(DOM)和序列化程序.在此博客文章中,我将介绍它如何工作以及如何 ...

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

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

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

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

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

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

  8. .NET Core 内置的 System.Text.Json 使用注意

    System.Text.Json 是 .NET Core 3.0 新引入的高性能 json 解析.序列化.反序列化类库,武功高强,但毕竟初入江湖,炉火还没纯青,使用时需要注意,以下是我们在实现使用中遇 ...

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

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

随机推荐

  1. python os.walk处理树状目录结构的文件

    在项目工作中,时常需要用到处理文件的方法,尤其是在windows环境下的树状目录结构 os.walk恰好能完美的处理这种树状目录结构文件,能高效地帮助我们得到我们需要处理的文件 目录结构: Deskt ...

  2. jdbc简单学生管理系统

    这个是java连接mysql数据库的一个简单学生系统,通过jdbc连接数据库. 工具类 JDBCuntils. package Student; import java.io.IOException; ...

  3. Redis:学习笔记-04

    Redis:学习笔记-04 该部分内容,参考了 bilibili 上讲解 Redis 中,观看数最多的课程 Redis最新超详细版教程通俗易懂,来自 UP主 遇见狂神说 10. Redis主从复制 1 ...

  4. 微信小程序 scroll-view 完成上拉加载更多

    我们经常在软件客户端上看到这么一个功能,当我们阅读信息浏览到文章的末尾时,通常会加载出更多的信息.比如,我们在简书客户端上浏览推荐文章时,浏览到屏幕的末尾,此时又加载出了另一页的推荐文章,即实现了上拉 ...

  5. 像素设定 牛客网 程序员面试金典 C++ Python

    像素设定 牛客网 程序员面试金典 题目描述 有一个单色屏幕储存在一维数组中,其中数组的每个元素代表连续的8位的像素的值,请实现一个函数,将第x到第y个像素涂上颜色(像素标号从零开始),并尝试尽量使用最 ...

  6. Codeforces Round #741 (Div. 2)部分题题解

    我果然还是太菜了,就写了两道题....真是水死了.... A The Miracle and the Sleeper 简化题意:给定\(l,r\),求\(a\)%\(b\)的最大值,其中\(r> ...

  7. 挂载nfs存储

    查看nfs服务器上提供了哪些nfs目录 showmount -e 172.16.3.8 使用showmount前需要安装nfs-utils包 yum install nfs-utils -y 挂载nf ...

  8. LOTO虚拟示波器软件功能演示之——FIR数字滤波

    本文章介绍一下LOTO示波器新出的功能--FIR数字滤波的功能. 在此之前我们先来了解一下带通滤波和带阻滤波.我们都知道每个信号是不同频率不同幅值正弦波的线性叠加,为了方便直接得观察到这种现象,就有了 ...

  9. 五(一)、spring 声明式事务注解配置

    一.事务概述: 事务就是一系列的动作, 它们被当做一个单独的工作单元. 这些动作要么全部完成, 要么全部不起作用:比如 用户购买图书:购买动作之前需要确认 ①图书的数量是否足够:②用户账号余额是否足够 ...

  10. “TCP:三次握手”分析——以一个简单的“服务器”和“客户端”为例

    linux&C这两天学到了网络编程这一章,自己写了一个小的"服务器"和"客户端"程序,目的在于简单理解tcp/ip模型,以及要搭建一台简单服务器,服务器 ...