ASP.NET Core – System.Text.Json
前言
System.Text.Json 是 .NET 3.0 后推出的, 用来取代原本的 Newtonsoft。
它的特点就是快,一开始的时候很多东西不支持所以很少人用,.NET 6.0 后开始比较稳定了。
这一篇就来系统的看一看它吧。
主要参考:
How to serialize and deserialize
How to customize property names and values with System.Text.Json
How to ignore properties with System.Text.Json
How to handle overflow JSON or use JsonElement or JsonNode in System.Text.Json
How to use a JSON document, Utf8JsonReader, and Utf8JsonWriter in System.Text.Json
How to write custom converters for JSON serialization
Serialize
Serialize Whatever
不管是匿名对象,字典,对象,dynamic 都可以直接 serialize to JSON。
var person = new { Name = "Derrick" };
Console.WriteLine(JsonSerializer.Serialize(person)); // {"Name":"Derrick"} var person1 = new Dictionary<string, object>
{
["Name"] = "Derrick"
};
Console.WriteLine(JsonSerializer.Serialize(person1)); // {"Name":"Derrick"} var person2 = new Person { Name = "Derrick" };
Console.WriteLine(JsonSerializer.Serialize(person2)); // {"Name":"Derrick"} dynamic person3 = new ExpandoObject();
person3.Name = "Derrick";
Console.WriteLine(JsonSerializer.Serialize(person3)); // {"Name":"Derrick"}
Serialize to stream
如果想 serialize 了写入 file 的话, 可以使用 SerializeAsync
var rootPath = Path.GetDirectoryName(Assembly.GetEntryAssembly()!.Location[..Assembly.GetEntryAssembly()!.Location.IndexOf("bin\\")])!;
using var fileStream = File.Open(Path.Combine(rootPath, "person.json"), FileMode.OpenOrCreate, FileAccess.ReadWrite);
await JsonSerializer.SerializeAsync(fileStream, person3);
Serialize Default Behavior
all public properties are serialized, 所有公开属性都会被 serialize
JSON is minified, 没有空格那些, 要美美的话要 set options
casing of JSON names matches the .NET names, 比如 property "Name" serialize 出来也是 "Name" 而不是 javascript 喜欢的 camelcase "name"
circular references are detected and exceptions thrown, 一旦循环引用就报错
fields are ignored, 字段是不会被 serialize 出来的.
enums are supported as numbers
在 ASP.NET Core WebAPI 的环境下, default behavior 有一点不同
JsonNamingPolicy = CamelCase
也就是会 serialize 成 javascript 喜欢的 camelcase.
Common Options
WriteIndented = true, 变成有空格, human readable
var person = new { Name = "Derrick" };
Console.WriteLine(JsonSerializer.Serialize(person, new JsonSerializerOptions {
WriteIndented = true
}));
//{
// "Name": "Derrick"
//}
PropertyNamingPolicy = JsonNamingPolicy.CamelCase, 变成 javascript 喜欢的 camelcase
var person = new { Name = "Derrick" };
Console.WriteLine(JsonSerializer.Serialize(person, new JsonSerializerOptions {
PropertyNamingPolicy = JsonNamingPolicy.CamelCase
})); // {"name":"Derrick"}
注: 只有对象和匿名对象有效, 字典和 dynamic 则用 DictionaryKeyPolicy 哦.
ReferenceHandler = ReferenceHandler.Preserve, 循环引用情况下用 $id 和 $ref 表达
dynamic person = new ExpandoObject();
person.Name = "Derrick";
person.Person = person;
Console.WriteLine(JsonSerializer.Serialize(person, new JsonSerializerOptions {
ReferenceHandler = ReferenceHandler.Preserve
})); // {"$id":"1","Name":"Derrick","Person":{"$ref":"1"}}
ReferenceHandler = ReferenceHandler.IgnoreCycles, 循环引用的 property set to null
dynamic person = new ExpandoObject();
person.Name = "Derrick";
person.Person = person;
Console.WriteLine(JsonSerializer.Serialize(person, new JsonSerializerOptions {
ReferenceHandler = ReferenceHandler.IgnoreCycles
})); // {"Name":"Derrick","Person":null}
Change Property Name and Order
public class Person
{
[JsonPropertyName("NewName")]
[JsonPropertyOrder(2)]
public string Name { get; set; } = ""; [JsonPropertyOrder(1)]
public int Age { get; set; }
}
输出
var person = new Person { Name = "Derrick", Age = 11 };
Console.WriteLine(JsonSerializer.Serialize(person)); // {"Age":11,"NewName":"Derrick"}
Custom JsonNamingPolicy
能控制的地方不多, 只能获取到 property name 而不是 property info
public class MyJsonNamingPolicy : JsonNamingPolicy
{
public override string ConvertName(string name) => name.ToUpper();
}
输出
var person = new Person { Name = "Derrick", Age = 11 };
Console.WriteLine(
JsonSerializer.Serialize(person,
new JsonSerializerOptions { PropertyNamingPolicy = new MyJsonNamingPolicy() })
); // {"AGE":11,"NewName":"Derrick"}
“NewName” 不是 uppper 是因为被 JsonPropertyName override 了
JsonIgnore
[JsonIgnore(Condition = JsonIgnoreCondition.Always)]
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingDefault)]
[JsonIgnore(Condition = JsonIgnoreCondition.Never)]
[JsonIgnore]
public string Name { get; set; } = "";
Always 就是总是不要出
Never 就是总是出 (可以 override 掉 global 的 options, 比如 IgnoreReadOnlyProperties)
WhenWritingNull 是说, 如果 value 是 null 那么这个 property 就不要出
WhenWritingDefault 是说, int = 0 这种 default value 也不要出.
JsonInclude
[JsonInclude]
public string Name = "name";
Field by default 是不会被 serialize 的, 如果想要的话就加 JsonInclude, 只有 public 才可以 include 哦, non-public 会报错.
通过 setting 配置也是可以
var json = JsonSerializer.Serialize(person, new JsonSerializerOptions { IncludeFields = true });
Deserialize
Simple Deserialize
Deserialize 比 Serialize 需要顾虑的东西比较多, 因为可能 type mismatch, underflow, overflow 都有可能发生.
Deserialize 的时候要提供一个泛型类
var json = JsonSerializer.Serialize(new Person { Name = "Derrick", Age = 11 });
var person = JsonSerializer.Deserialize<Person>(json);
如果泛型是一个 object 或者 dynamic, 那么出来的类型是 JsonElement, 这个后面会讲它是什么类型来的
var person = JsonSerializer.Deserialize<object>(json)!;
var typeName = person.GetType().FullName; // System.Text.Json.JsonElement
Deserialize from Stream
var rootPath = Path.GetDirectoryName(Assembly.GetEntryAssembly()!.Location[..Assembly.GetEntryAssembly()!.Location.IndexOf("bin\\")])!;
using var fileStream = File.Open(Path.Combine(rootPath, "person.json"), FileMode.Open, FileAccess.Read);
var person = await JsonSerializer.DeserializeAsync<Person>(fileStream);
Deserialize Default Behavior
property name matching is case-sensitive, 区分大小写
read-only property is ignored
non-public constructors are ignored
enums are supported as numbers
fields are ignored
comments or trailing commas throw exceptions, 注释和结尾逗号是不允许的, 会报错
The maximum depth is 64.
underflow 会用 class default value
overflow 会 ignore
type mismatch 会报错.
在 ASP.NET Core WebAPI 的环境下, default behavior 有一点不同
PropertyNameCaseInsensitive = true, 不区分大小写
NumberHandling = AllowReadingFromString, int = "100" 是 ok 的
Type Mismatch
Deserialize 时一旦发现 type mismatch 就会马上报错, error message 长这样
Path 的写法和 SQL Server JSON 一样哦.
Common Options
PropertyNameCaseInsensitive = true
NumberHandling = AllowReadingFromString
AllowTrailingCommas = true
ReadCommentHandling = JsonCommentHandling.Skip
大部分就是为了 by pass, 不要求那么严格而已.
JsonPropertyName, JsonNamingPolicy, JsonIgnore, JsonInclude
上面在 Serialize 提过的在 Deserialize 也同样受
JsonObject and JsonArray
喜欢 javascript 一定喜欢这种写法
var jObject = new JsonObject
{
["Name"] = "Derrick",
["Children"] = new JsonArray
{
new JsonObject {
["Age"] = 11
}
}
};
var value = jObject["Children"]![0]!["Age"]!.GetValue<int>();
var json = JsonSerializer.Serialize(jObject);
不仅仅可以用来生成 JSON,也可以用来 parse JSON 哦。
var json = """
{
"name": "Derrick",
"address": {
"state": "Johor",
"country": "Malaysia"
}
}
""";
var data = JsonSerializer.Deserialize<JsonObject>(json)!;
var name = data["name"]!.GetValue<string>(); // "Derrick"
提醒:Deserialize<JsonObject> 不支持配置 JsonNamingPolicy 哦。
另外,address 还可以 Deserialize to Class instance 哦。
public class Address
{
public string State { get; set; } = "";
public string Country { get; set; } = "";
}
var address = data["address"]!.Deserialize<Address>(new JsonSerializerOptions
{
PropertyNamingPolicy = JsonNamingPolicy.CamelCase
});
超级方便。
JsonNode and JsonDocument
JsonNode 是 JsonObject 更底层的操作.
JsonNode 是 muatable,Document 则是 immutable (如果只是想读就用 Document,比较快)
上面说到, overflow, underflow 的问题, 要解决它就要 parse to JsonDocument 而不是一个固定了的类型
大概长这样
var person = new { Name = "Abc", Age = 11 };
var json = JsonSerializer.Serialize(person);
using var document = JsonDocument.Parse(json);
var rootElement = document.RootElement;
var isObject = document.RootElement.ValueKind == JsonValueKind.Object;
foreach (var prop in rootElement.EnumerateObject())
{
var propertyName = prop.Name;
var valueType = prop.Value.ValueKind;
if (valueType == JsonValueKind.Number)
{
var value = prop.Value.GetDecimal();
}
}
通过这个就可以遍历完整个 JSON 内容了, 然后想干嘛干嘛.
JsonNode 的接口和 JsonDocument 差不多, 我就不写了
Utf8JsonWriter
图手写 JSON
using var stream = new MemoryStream();
using var writer = new Utf8JsonWriter(stream);
writer.WriteStartObject();
writer.WriteString("Name", DateTimeOffset.UtcNow);
writer.WriteNumber("Age", 42);
writer.WriteStartArray("Numbers");
writer.WriteNumberValue(1);
writer.WriteNumberValue(2);
writer.WriteEndArray();
writer.WriteEndObject();
writer.Flush();
string json = Encoding.UTF8.GetString(stream.ToArray());
Custom Convertor
Custom Convertor 是用来修改 value read/write 的, 类似 EF Core 的 value convertor.
比如 Enum 默认是 serialize to number, 如果像 serialize to string 就需要自己写一个 convertor.
有 2 种 convertor, 一种叫 basic, 一种叫 factory.
Factory 就是就是给那种动态类型的.
Basic Convertor
private class MyIntConverter : JsonConverter<int>
{
public override int Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
{
return reader.GetInt32();
} public override void Write(Utf8JsonWriter writer, int value, JsonSerializerOptions options)
{
writer.WriteNumberValue(value);
}
}
它就是一个 read, 一个 write. 只要是 int 就会进来. 在 deserialize 的时候, typeToConvert 是来自 Deserialize<Person> 的泛型中的类型
Register Convertor
var options = new JsonSerializerOptions
{
Converters = {
new MyIntConverter(),
new MyEnumConverterFactory()
}
};
Converters 是一个 IList, 全部放进去就可以了. 它自己会依据类型去调用.
Factory Convertor
顾名思义, 就是依据类型, 动态创建不同的 Convertor 来满足需求
public class MyEnumConverterFactory : JsonConverterFactory
{
public override bool CanConvert(Type typeToConvert)
{
return typeToConvert.IsEnum;
} public override JsonConverter? CreateConverter(Type typeToConvert, JsonSerializerOptions options)
{
var EnumConverterType = typeof(EnumConverter<>).MakeGenericType(typeToConvert);
return (JsonConverter)Activator.CreateInstance(EnumConverterType)!;
}
}
具体的 EnumConvertor
private class EnumConverter<T> : JsonConverter<T> where T : Enum
{
public override T Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
{
var enumValue = reader.GetString();
return (T)Enum.Parse(typeToConvert, enumValue!);
} public override void Write(Utf8JsonWriter writer, T value, JsonSerializerOptions options)
{
writer.WriteStringValue(value.ToString());
}
}
上面并没有处理 EnumMember 标签, 如果懒得自己写推荐库 : Macross.Json.Extensions
更新: 27-09-2022 微软视乎已经有 build-in 的 JsonStringEnumConverter 了, 这里补上一个例子 (它也不支持 EnumMember 哦)
更新: 28-10-2023 最新 Issue 追踪看这里 Github – Support customizing enum member names in System.Text.Json


using Stooges.Core.NamingConversion;
using System.Text.Json.Serialization; namespace TestFacebookGraphApiReview; public class SnackCaseJsonNamingPolicy : JsonNamingPolicy
{
public override string ConvertName(string name) => name.ToSnakeCase();
} public enum RecommendationType
{
Negative,
Positive
} public class Review
{
public RecommendationType RecommendationType { get; set; }
}
public class Program
{
public static void Main(string[] args)
{
var json = "{\"recommendation_type\":\"positive\"}";
var review = JsonSerializer.Deserialize<Review>(json, new JsonSerializerOptions
{
PropertyNamingPolicy = new SnackCaseJsonNamingPolicy(),
Converters = {
new JsonStringEnumConverter()
}
})!;
var value = review.RecommendationType;
}
}
JsonConverterAttribute
上面 JsonConverter 只能针对类型做转化,如果我的需求是,某个 property name 我就要转换,它就做不到了。
这时就需要利用 JsonConverterAttribute。参考: Stack Overflow – System.Text.Json: Get the property name in a custom converter
用法很简单,在指定的 property 上添加 attribute 就可以了。
public class Person
{
[JsonConverter(typeof(MyConverter))]
public string FirstName { get; set; } = "";
}
serialize 的时候就不需要做任何配置了。
var person = new Person { FirstName = "Derrick" };
var json = JsonSerializer.Serialize(person);
Console.WriteLine(json);
我个人觉得这个方法虽然能用,但绝对不友好。
Serialize properties of derived classes
参考:
How to serialize properties of derived classes with System.Text.Json
Support polymorphic deserialization
要 .NET 7.0 才支持哦.
首先我们有三个 class, 一个抽象 Person, 一个 Ali 派生和一个 Derrick 派生.
public abstract class Person
{
public string Name { get; set; } = "";
}
public class Ali : Person
{
public int Age { get; set; }
}
public class Derrick : Person
{
public int Salary { get; set; }
}
在抽象 class 添加 Attribute 声明它的派生类
[JsonDerivedType(typeof(Ali), typeDiscriminator: "Ali")]
[JsonDerivedType(typeof(Derrick), typeDiscriminator: "Derrick")]
public abstract class Person
{
public string Name { get; set; } = "";
}
Serialize
var people = new List<Person> {
new Ali { Name = "Ali", Age = 10 },
new Derrick { Name = "Derrick", Salary = 100 }
};
var json = JsonSerializer.Serialize(people, new JsonSerializerOptions { WriteIndented = true });
效果
[
{
"$type": "Ali",
"Age": 10,
"Name": "Ali"
},
{
"$type": "Derrick",
"Salary": 100,
"Name": "Derrick"
}
]
$type 是一个特殊属性, 用来表达对象的具体类型, 它的值来自 Attribute 的 typeDiscriminator (p.s. $type 是 lowercase 哦, $id, $ref 也都是 lowercase 呢)
Deserialize
var json = """
[
{
"$type": "Ali",
"Age": 10,
"Name": "Ali"
},
{
"$type": "Derrick",
"Salary": 100,
"Name": "Derrick"
}
]
""";
var people = JsonSerializer.Deserialize<List<Person>>(json);
Deserialize 也很简单, 确保 JSON value 有 $type 就行了.
冷知识
当 JSON 遇上 Tuple
参考: How does a Tuple serialize to and deserialize from JSON?
public class Person
{
public List<(string A, string B)> Datas { get; set; } = new();
}
直接 serialize, 结果是空的...
var json = JsonSerializer.Serialize(person); // {"Datas":[{}]}
IncludeFields 之后 serialize 的出来了, 但是它不是 string[], 而是 Item1, Item2 对象...
var json = JsonSerializer.Serialize(person, new JsonSerializerOptions { IncludeFields = true }); // {"Datas":[{"Item1":"a","Item2":"b"}]}
deserialize 也要 IncludeFields 哦, 出来的结果是 ok 的.
var json = JsonSerializer.Serialize(person, new JsonSerializerOptions { IncludeFields = true });
var value = JsonSerializer.Deserialize<Person>(json, new JsonSerializerOptions { IncludeFields = true })!;
var aValue = value.Datas.ElementAt(0).A; // collect value
总结: 感觉勉强可以用, 但是不是很顺风水. 所以不推荐在要 serialize JSON 的地方使用 Tuple.
+ 变成 \u002B
参考
Github – + sign is changing into \u002B using JsonSerializer.Serialize
Docs – How to customize character encoding with System.Text.Json
var person = new Person { Phone = "+65 1122 3344" };
var json = JsonSerializer.Serialize(person);
Console.Write(json); // {"Phone":"\u002B65 1122 3344"}
+ 变成勒 \u002B, 这是因为 by default serialize 的时候会做 HTML escape, 因为它怕我们不小心把 JSON 直接丢进 <script> 里头.
我是在做 JSON-LD 发现这个问题的. 解决方法是 bypass
var json = JsonSerializer.Serialize(person, new JsonSerializerOptions { Encoder = JavaScriptEncoder.UnsafeRelaxedJsonEscaping });
这样就可以了. 但是要注意哦, 我是因为 <script type="application/ld+json"> 它不会执行 JS 所以才可以放胆的 bypass, 如果是放进普通的 <script> 可不要乱 bypass 哦, 不然中 XSS 就糟糕了.
ASP.NET Core – System.Text.Json的更多相关文章
- 在.Net Core 3.0中尝试新的System.Text.Json API
.NET Core 3.0提供了一个名为System.Text.Json的全新命名空间,它支持reader/writer,文档对象模型(DOM)和序列化程序.在此博客文章中,我将介绍它如何工作以及如何 ...
- .NET Core 3.0 System.Text.Json 和 Newtonsoft.Json 行为不一致问题及解决办法
行为不一致 .NET Core 3.0 新出了个内置的 JSON 库, 全名叫做尼古拉斯 System.Text.Json - 性能更高占用内存更少这都不是事... 对我来说, 很多或大或小的项目能少 ...
- .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,配置比较 ...
- .net core中关于System.Text.Json的使用
在.Net Framework的时候序列化经常使用Newtonsoft.Json插件来使用,而在.Net Core中自带了System.Text.Json,号称性能更好,今天抽空就来捣鼓一下. 使用起 ...
- [.Net Core 3.0+/.Net 5] System.Text.Json中时间格式化
简介 .Net Core 3.0开始全新推出了一个名为System.Text.Json的Json解析库,用于序列化和反序列化Json,此库的设计是为了取代Json.Net(Newtonsoft.Jso ...
- [译]试用新的System.Text.Json API
译注 可能有的小伙伴已经知道了,在.NET Core 3.0中微软加入了对JSON的内置支持. 一直以来.NET开发者们已经习惯使用Json.NET这个强大的库来处理JSON. 那么.NET为什么要增 ...
- 【译】System.Text.Json 的下一步是什么
.NET 5.0 最近发布了,并带来了许多新特性和性能改进.System.Text.Json 也不例外.我们改进了性能和可靠性,并使熟悉 Newtonsoft.Json 的人更容易采用它.在这篇文章中 ...
- 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 ...
- System.Text.Json 自定义Converter实现时间转换
Newtonsoft.Json与System.Text.Json区别 在 Newtonsoft.Json中可以使用例如 .AddJsonOptions(options => { options. ...
随机推荐
- 解决方案 | Chrome/Edge 总是自动修改我的pdf默认打开方式
1.问题描述 最近我的pdf文件总是被chrome打开(如图1),而且点击属性,更改别的pdf阅读器也不管用(如图2),此时的chrome就像个流氓软件一样. 图1 被chrome劫持 图2 点击属性 ...
- [oeasy]python0082_VT100_演化_颜色设置_VT选项_基础色_高亮色_索引色_RGB总结
更多颜色 回忆上次内容 上次 了解了控制序列 背后的故事 一切标准 都是 从无到有 的 就连 负责标准的组织 也是 从无到有 的 VT-05 奠定了 基础颜色 黑底 绿字 隔行 扫描 但 多颜色设置 ...
- CF1363A 题解
洛谷链接&CF 链接 题目简述 共有 \(T\) 组数据. 对于每组数据,给定 \(n,x\) 和 \(n\) 个数,问是否可以从 \(n\) 个数中选 \(x\) 个使其和为奇数,可以输出 ...
- java面试一日一题:讲下mysql中的锁
问题:请讲下在mysql中的锁 分析:该问题主要考察对中锁的掌握,主要考察的是读.写锁.行锁.间隙锁.next-key,其他还有表锁.意向锁 回答要点: 主要从以下几点去考虑, 1.mysql中的锁有 ...
- 【Spring】使用SpringTest报错 java.lang.NoSuchMethodError
完整报错信息: "C:\Program Files\Java\jdk1.8.0_301\bin\java.exe" -ea -Didea.test.cyclic.buffer.si ...
- 【Java】Maven模块化工程SSM整合
创建数据库一个演示表User CREATE TABLE `user` ( `id` int NOT NULL AUTO_INCREMENT, `name` varchar(64) DEFAULT NU ...
- 何时使用交叉熵,何时使用KL散度:计算分布差距为啥使用KL散度而不用交叉熵,计算预测差距时为啥使用交叉熵而不用KL散度
问题: 何时使用交叉熵,何时使用KL散度? 计算分布差距为啥使用KL散度而不用交叉熵,计算预测差距时为啥使用交叉熵而不用KL散度 问题很大,答案却很简单. 答案: 熵是一种量度,是信息不确定性的量度: ...
- 【转载】 Tensorflow Guide: Batch Normalization (tensorflow中的Batch Normalization)
原文地址: http://ruishu.io/2016/12/27/batchnorm/ ------------------------------------------------------- ...
- Ubuntu 18.04.4 导入docker镜像,启动镜像,保存容器为镜像,导出镜像
1. 查看 docker 版本 sudo docker version 2. 查看本地库中的镜像 sudo docker images 3. 查看 正在运行的 容器 sudo docker ...
- 强化学习游戏仿真环境:torcs的安装——自动驾驶、赛车游戏环境
Ubuntu系统下可以有两种安装方式: 1. 通过系统软件库进行安装,命令: sudo apt install torcs torcs-data 该种安装方式比较简单,容易成功,缺点就是必须要有sud ...