c# System.Text.Json 精讲
阅读原文时间:2021年11月29日阅读:2

本文内容来自我写的开源电子书《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解析

json是一种类似于通过键值对来储存数据的格式,在对数据库进行操作的时候,通常会把类数据转为json格式,然后储存在数据库里面,使用的时候再将json转为类的实例化对象。java的springboot框架的一整套解决方案里面可以通过mybatis和fastjson完成这个操作。在web的前后端数据传输中,一般也是用json作为数据的载体,JavaScript有着对json比较完备的支持。

  • 基础

    1. 概念: JavaScript Object Notation JavaScript对象表示法
    • json现在多用于存储和交换文本信息的语法

    • 进行数据的传输

    • JSON 比 XML 更小、更快,更易解析。

    1. 语法:

    2. 基本规则

      • 数据在名称/值对中:json数据是由键值对构成的

      • 键用引号(单双都行)引起来,也可以不使用引号

      • 值得取值类型:

        1. 数字(整数或浮点数)

        2. 字符串(在双引号中)

        3. 逻辑值(true 或 false)

        4. 数组(在方括号中) {"persons":[{},{}]}

        5. 对象(在花括号中) {"address":{"province":"陕西"….}}

        6. null

      • 数据由逗号分隔:多个键值对由逗号分隔

      • 花括号保存对象:使用{}定义json 格式

      • 方括号保存数组:[]

  1. json对象.键名

  2. json对象["键名"]

  3. 数组对象[索引]

  4. 遍历

使用 C# 对 JSON 进行序列化和反序列化 - .NET | Microsoft Docs

会用到两个名词,序列化和反序列化,其中序列化是指将实例对象转换成json格式的字符串,反序列化则是逆向前面序列化的过程。

在序列化的过程中,默认情况下会只序列化公共读写的属性,可以通过System.Text.Json.SerializationJsonInclude特性或者JsonSerializerOptionsIncludeFields属性来包含公有字段。通过System.Text.Json.SerializationJsonInclude特性可以来自定义可以序列化的非公共属性访问器(即属性的访问修饰符为public,但是set访问器和get访问器的任意一方为非public)。这可能对使用惯了java的人来说不适应,事实上这是一种很合理的序列化要求,默认状况下,序列化器会序列化对象中的所有可读属性,反序列化所有可写属性,这种方式尊重了访问修饰符的作用。也可用开源的Newtonsoft.Json来序列化非公有属性。现在很多编程语言(包括.NET)能通过反射来获取私有属性本身就是不合理的,从.NET core能明显的感觉到.NET团队出于安全的考虑在限制反射的使用。

需要用到的namespace

using System.Text.Encodings.Web;
using System.Text.Json;
using System.Text.Json.Serialization;
using System.Text.Unicode;
  • System.Text.JsonJsonSerializer.Serialize方法可以进行序列化
  • System.Text.JsonJsonSerializer.Deserialize方法可以进行反序列化
  • System.Text.Json.Serialization可以要序列化的类添加必要的特性,比如JsonPropertyName为属性序列化时重命名,再比如JsonInclude来定义序列化时要包含的字段。
  • System.Text.Encodings.WebSystem.Text.Unicode来让特定的字符集在序列化的时候能够正常序列化而不是被转义成为 \uxxxx,其中 xxxx 为字符的 Unicode 代码。事实上,默认情况下,序列化程序会转义所有非 ASCII 字符。

序列化

只将实例化对象转变成json字符串,假设有一个实例化对象weatherForecast,序列化方式如下:

string jsonString = JsonSerializer.Serialize(weatherForecast);

反序列化

指将json字符串序列化成实例化对象,书接前文,方式如下:

weatherForecast = JsonSerializer.Deserialize<WeatherForecastWithPOCOs>(jsonString);

JsonSerializerOptions

可以通过JsonSerializerOptions来指定诸如是否整齐打印和忽略Null值属性等信息。使用方式为将JsonSerializerOptions实例化之后再当作JsonSerializer.SerializeJsonSerializer.Deserialize的参数。

关于JsonSerializerOptions的属性可以查看如何使用 System.Text.Json 实例化 JsonSerializerOptions | Microsoft Docs

先实例化一个JsonSerializerOptions对象,在初始化器里面定义各种属性

JsonSerializerOptions jsonSerializerOptions = new JsonSerializerOptions()
{
    // 整齐打印
    WriteIndented = true,
    // 忽略值为Null的属性
    IgnoreNullValues = true,
    // 设置Json字符串支持的编码,默认情况下,序列化程序会转义所有非 ASCII 字符。 即,会将它们替换为 \uxxxx,其中 xxxx 为字符的 Unicode
    // 代码。 可以通过设置Encoder来让生成的josn字符串不转义指定的字符集而进行序列化 下面指定了基础拉丁字母和中日韩统一表意文字的基础Unicode 块
    // (U+4E00-U+9FCC)。 基本涵盖了除使用西里尔字母以外所有西方国家的文字和亚洲中日韩越的文字
    Encoder = JavaScriptEncoder.Create(UnicodeRanges.BasicLatin, UnicodeRanges.CjkUnifiedIdeographs),
    // 反序列化不区分大小写
    PropertyNameCaseInsensitive = true,
    // 驼峰命名
    PropertyNamingPolicy = JsonNamingPolicy.CamelCase,

    // 对字典的键进行驼峰命名
    DictionaryKeyPolicy = JsonNamingPolicy.CamelCase,
    // 序列化的时候忽略null值属性
    DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull,
    // 忽略只读属性,因为只读属性只能序列化而不能反序列化,所以在以json为储存数据的介质的时候,序列化只读属性意义不大
    IgnoreReadOnlyFields = true,
    // 不允许结尾有逗号的不标准json
    AllowTrailingCommas = false,
    // 不允许有注释的不标准json
    ReadCommentHandling = JsonCommentHandling.Disallow,
    // 允许在反序列化的时候原本应为数字的字符串(带引号的数字)转为数字
    NumberHandling = JsonNumberHandling.AllowReadingFromString,
    // 处理循环引用类型,比如Book类里面有一个属性也是Book类
    ReferenceHandler = ReferenceHandler.Preserve
};

然后在序列化和反序列化的时候jsonSerializerOptions对象当作参数传给JsonSerializer.SerializeJsonSerializer.Deserialize

string jsonBookA = JsonSerializer.Serialize(bookA, jsonSerializerOptions);
// 反序列化
BookA bookA1 = JsonSerializer.Deserialize<BookA>(jsonBookA, jsonSerializerOptions);

JsonSerializerOptions 常用属性概述

作用

值类型

WriteIndented

整齐打印,将此值设置为true后序列化的json字符串在打印的时候会进行自动缩进和换行。默认为false。

bool

IgnoreNullValues

忽略值为Null的属性。默认为false。

bool

Encoder

设置Json字符串支持的编码,默认情况下,序列化程序会转义所有非 ASCII 字符。 即,会将它们替换为 \uxxxx,其中 xxxx 为字符的 Unicode代码。 可以通过设置Encoder来让生成的josn字符串不转义指定的字符集而进行序列化。可设置为Encoder = JavaScriptEncoder.Create(UnicodeRanges.BasicLatin, UnicodeRanges.CjkUnifiedIdeographs)来包含除使用西里尔字母以外所有西方国家的文字和亚洲中日韩越的文字

JavaScriptEncoder

PropertyNameCaseInsensitive

反序列化不区分键的大小写。默认为false。

bool

PropertyNamingPolicy

序列化时属性的命名方式,常用的为JsonNamingPolicy.CamelCase设置成小写字母开头的驼峰命名。

JsonNamingPolicy

DictionaryKeyPolicy

序列化时对字典的string键进行小写字母开头的驼峰驼峰命名。

JsonNamingPolicy

DefaultIgnoreCondition

指定一个条件,用于确定何时在序列化或反序列化过程中忽略具有默认值的属性。 默认值为 Never。常用值为JsonIgnoreCondition.WhenWritingDefault来忽略默认值属性。

JsonIgnoreCondition

IgnoreReadOnlyFields

序列化时忽略只读属性,因为只读属性只能序列化而不能反序列化,所以在以json为储存数据的介质的时候,序列化只读属性意义不大。默认为false。

bool

AllowTrailingCommas

反序列化时,允许结尾有逗号的不标准json,默认为false。

bool

ReadCommentHandling

反序列化时,允许有注释的不标准json,默认为false。

bool

NumberHandling

使用NumberHandling = JsonNumberHandling.AllowReadingFromString可允许在反序列化的时候原本应为数字的字符串(带引号的数字)转为数字

JsonNumberHandling

ReferenceHandler

配置在读取和写入 JSON 时如何处理对象引用。使用ReferenceHandler = ReferenceHandler.Preserve仍然会在序列化和反序列化的时候保留引用并处理循环引用。

ReferenceHandler

IncludeFields

确定是否在序列化和反序列化期间处理字段。 默认值为 false

bool

System.Text.Json.Serialization 特性

可以为将要序列化和被反序列化而生成的类的属性和字段添加特性。

JsonInclude 包含特定public字段和非公共属性访问器

在序列化或反序列化时,使用 JsonSerializerOptions.IncludeFields 全局设置或 [JsonInclude] 特性来包含字段(必须是public),当应用于某个属性时,指示非公共的 getter 和 setter 可用于序列化和反序列化。 不支持非公共属性。

demo:

/// <summary>
/// 时间戳
/// </summary>
[JsonInclude]
public long timestamp = DateTimeOffset.Now.ToUnixTimeMilliseconds();

/// <summary>
/// 书的名称
/// </summary>
[JsonInclude]
public string Name { private get; set; } = "《书名》";

JsonPropertyName 自定义属性名称

若要设置单个属性的名称,请使用 [JsonPropertyName] 特性。

此特性设置的属性名称:

  • 同时适用于两个方向(序列化和反序列化)。
  • 优先于属性命名策略。

demo:

/// <summary>
/// 作者
/// </summary>
[JsonPropertyName("作者")]
public string Author
{
    get { return _author; }
    set { _author = value; }
}

JsonIgnore 忽略单个属性

阻止对属性进行序列化或反序列化。

demo:

/// <summary>
/// 书的出版商
/// </summary>
[JsonIgnore]
public string OutCompany { get => _outCompany; set => _outCompany = value; }

JsonExtensionData 处理溢出 JSON

反序列化时,可能会在 JSON 中收到不是由目标类型的属性表示的数据。可以将这些无法由目标类型的属性表示的数据储存在一个Dictionary<string, JsonElement>字典里面,方式如下:

/// <summary>
/// 储存反序列化时候的溢出数据
/// </summary>
[JsonExtensionData]
public Dictionary<string, JsonElement> ExtensionData { get; set; }

笔者的选择

在笔者的开发经验当中,json用的最多的就是前后端数据传输和数据库储存数据。对jsonSerializerOptions往往会选择这几个选项:

JsonSerializerOptions jsonSerializerOptions = new JsonSerializerOptions()
{
    // 整齐打印
    WriteIndented = true,
    // 忽略值为Null的属性
    IgnoreNullValues = true,
    // 设置Json字符串支持的编码,默认情况下,序列化程序会转义所有非 ASCII 字符。 即,会将它们替换为 \uxxxx,其中 xxxx 为字符的 Unicode
    // 代码。 可以通过设置Encoder来让生成的josn字符串不转义指定的字符集而进行序列化 下面指定了基础拉丁字母和中日韩统一表意文字的基础Unicode 块
    // (U+4E00-U+9FCC)。 基本涵盖了除使用西里尔字母以外所有西方国家的文字和亚洲中日韩越的文字
    Encoder = JavaScriptEncoder.Create(UnicodeRanges.BasicLatin, UnicodeRanges.CjkUnifiedIdeographs, UnicodeRanges.CjkSymbolsandPunctuation),
    // 反序列化不区分大小写
    PropertyNameCaseInsensitive = true,
    // 驼峰命名
    PropertyNamingPolicy = JsonNamingPolicy.CamelCase,
    // 对字典的键进行驼峰命名
    DictionaryKeyPolicy = JsonNamingPolicy.CamelCase,
    // 忽略只读属性,因为只读属性只能序列化而不能反序列化,所以在以json为储存数据的介质的时候,序列化只读属性意义不大
    IgnoreReadOnlyFields = true,
    // 允许在反序列化的时候原本应为数字的字符串(带引号的数字)转为数字
    NumberHandling = JsonNumberHandling.AllowReadingFromString
};

尽量不使用JsonPropertyName特性,对有可能会用到json反序列化的类一定会用到JsonExtensionData特性来储存可能存在的溢出数据。JsonIgnoreJsonInclude会广泛的使用而不用JsonSerializerOptionsIncludeFields来序列化所有字段。

LICENSE

已将所有引用其他文章之内容清楚明白地标注,其他部分皆为作者劳动成果。对作者劳动成果做以下声明:

copyright 2021 苏月晟,版权所有。


本作品由苏月晟采用知识共享署名-非商业性使用-相同方式共享 4.0 国际许可协议进行许可。