介绍 csharp 关于 json 的用法. 本文的所有源码均存放在 kentxxq/csharpDEMO (github.com).
为什么会有这篇文章?
因为 json 非常的流行, 而且存在有很多细节. 例如性能, 格式, 类库用法等等.
准备一个用于演示的示例对象 Person
. 同时涵盖了 List
, string
, int
, 枚举
, 日期
.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
| /// <summary>人</summary>
public class Person
{
[Display(Name = "人名", Description = "人的名字")]
public string Name { get; set; } = null!;
[Display(Name = "性别", Description = "人的性别")]
public Sex SexType { get; set; }
[Display(Name = "生日", Description = "人的生日")]
public DateTime Birthday { get; set; }
[Display(Name = "年纪", Description = "人的年纪")]
public int Age { get; set; }
[Display(Name = "头", Description = "人的头")]
public Head PersonHead { get; set; } = null!;
[Display(Name = "鞋子", Description = "人的鞋子")]
public List<Shoes>? PersonShoes { get; set; }
}
/// <summary>头</summary>
public class Head
{
[Display(Name = "宽", Description = "头的宽")]
public int Width { get; set; }
[Display(Name = "高", Description = "头的高")]
public int Height { get; set; }
}
/// <summary>性别</summary>
public enum Sex
{
[Display(Name = "男")] Man,
[Display(Name = "女")] Woman
}
/// <summary>鞋子</summary>
public class Shoes
{
[Display(Name = "鞋名", Description = "鞋子的名字")]
public string ShoesName { get; set; } = null!;
[Display(Name = "鞋颜色", Description = "鞋子的颜色")]
public Color ShoesColor { get; set; }
}
|
初始化一下:
1
2
3
4
5
6
7
8
| public static readonly Person DemoPerson = new()
{
Name = "ken", Age = 1, PersonHead = new Head { Height = 50, Width = 50 }, PersonShoes = new List<Shoes>
{
new() { ShoesColor = Color.Blue, ShoesName = "蓝色" },
new() { ShoesColor = Color.Red, ShoesName = "红色" }
}
};
|
1
2
3
4
5
6
7
8
9
10
11
| // 转json
var str = JsonSerializer.Serialize(StaticData.DemoPerson, new JsonSerializerOptions
{
// 空格
WriteIndented = true,
// 宽松转义规则,虽然不规范,但中文会正常打印出来. 否则中文会变成unicode字符,例如'蓝'-'\u84DD'
Encoder = JavaScriptEncoder.UnsafeRelaxedJsonEscaping
});
// 转对象
var d1 = JsonSerializer.Deserialize<Person>(str);
|
得到字符串
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
| {
"Name": "ken",
"SexType": 0,
"Birthday": "1234-05-06T00:00:00",
"Age": 1,
"PersonHead": {
"Width": 50,
"Height": 50
},
"PersonShoes": [
{
"ShoesName": "蓝色",
"ShoesColor": {
"R": 0,
"G": 0,
"B": 255,
"A": 255,
"IsKnownColor": true,
"IsEmpty": false,
"IsNamedColor": true,
"IsSystemColor": false,
"Name": "Blue"
}
},
{
"ShoesName": "红色",
"ShoesColor": {
"R": 255,
"G": 0,
"B": 0,
"A": 255,
"IsKnownColor": true,
"IsEmpty": false,
"IsNamedColor": true,
"IsSystemColor": false,
"Name": "Red"
}
}
]
}
|
#todo/笔记 net8新增UseStringEnumConverter
编写一个 Context 类
1
2
3
4
5
6
| [JsonSourceGenerationOptions(WriteIndented = true)] // 全局设置
[JsonSerializable(typeof(Person))] // 需要转换的类
[JsonSerializable(typeof(User))] // 可以多个
internal partial class JsonContext : JsonSerializerContext
{
}
|
使用:
1
2
3
4
5
| var s1 = JsonSerializer.Serialize(StaticData.DemoPerson, JsonContext.Default.Person);
var o1 = JsonSerializer.Deserialize(s1, JsonContext.Default.Person);
var s2 = JsonSerializer.Serialize(StaticData.DemoUser, JsonContext.Default.User);
Console.WriteLine(s1);
Console.WriteLine(s2);
|
- enum枚举 默认是枚举值 (数字), 使用名称替代
- 日期/timestamp 互转示例
1
2
3
4
5
6
7
8
9
10
11
12
13
14
| public class DateTimeJsonConverter2Timestamp : JsonConverter<DateTime>
{
public override DateTime Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
{
var tsDatetime = reader.GetInt64().MillisecondsToDateTime();
return tsDatetime;
}
public override void Write(Utf8JsonWriter writer, DateTime value, JsonSerializerOptions options)
{
var jsonDateTimeFormat = value.ToTimestampMilliseconds();
writer.WriteNumberValue(jsonDateTimeFormat);
}
}
|
推荐使用 JsonConverter
特性:
1
2
3
4
5
6
7
| public class WeatherForecastWithConverterAttribute
{
[JsonConverter(typeof(DateTimeJsonConverter2Timestamp))]
public DateTime Date { get; set; }
public int TemperatureCelsius { get; set; }
public string? Summary { get; set; }
}
|
或加入到 JsonSerializerOptions 中:
1
2
3
4
5
6
7
8
| var opt = new JsonSerializerOptions
{
// enum用名称,而不是数字表示
Converters =
{
new JsonStringEnumConverter(JsonNamingPolicy.CamelCase)
}
};
|
aspnetcore 使用
1
2
3
4
| services.AddControllers()
.AddJsonOptions(
opt => opt.JsonSerializerOptions.Converters.Add(new DateTimeJsonConverter2Timestamp())
);
|
什么情况使用? json 数据没有固定的字段, 或者数据类型.
使用选型:
在不规范的 json 中, 存在有重复的 key.
JsonDocument 取最后一个 key 的值. JsonNode 会报错!
1
2
3
4
5
6
7
8
| var jNode = JsonNode.Parse(str)!;
var name = jNode["Name"]!.GetValue<string>();
// 修改
jNode["Name"] = "kent";
name = jNode["Name"]!.GetValue<string>();
// 移除, JsonObject继承JsonNode
var jObject = jNode.AsObject();
jObject.Remove("Name");
|
1
2
| using var jDoc = JsonDocument.Parse(str);
name = jDoc.RootElement.GetProperty("Name").Deserialize<string>();
|
#todo/笔记 JsonSerializerOptions.UnmappedMemberHandling Property
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
| var opt = new JsonSerializerOptions
{
// 空格
WriteIndented = true,
// 宽松转义规则,虽然不规范,但中文会正常打印出来. 否则中文会变成unicode字符,例如'蓝'-'\u84DD'
Encoder = JavaScriptEncoder.UnsafeRelaxedJsonEscaping,
// 下面的不常用
// 默认不包含字段
IncludeFields = true,
// 允许存在注释,例如 "a":1, // a是id
ReadCommentHandling = JsonCommentHandling.Skip,
// 允许结尾的逗号
AllowTrailingCommas = true,
// 默认大小写不敏感
PropertyNameCaseInsensitive = true,
// 默认允许从string中读取数字
NumberHandling = JsonNumberHandling.AllowReadingFromString,
// 默认驼峰,可以 UpperCaseNamingPolicy:JsonNamingPolicy 然后重写 public override string ConvertName(string name) =>name.ToUpper();来修改
PropertyNamingPolicy = JsonNamingPolicy.CamelCase,
// 对象内部驼峰 "AB":{"aB":1,"bB":1}
DictionaryKeyPolicy = JsonNamingPolicy.CamelCase,
// enum用名称,而不是数字表示
Converters =
{
new JsonStringEnumConverter(JsonNamingPolicy.CamelCase)
}
// 从源生成中获取类型转换信息
TypeInfoResolver = JsonContext.Default
};
|
这些特性都是用在 POCO/实例类的.
1
2
| [JsonPropertyName("Wind")]
public int WindSpeed { get; set; }
|
How to customize property names and values with System.Text.Json - .NET | Microsoft Learn
1
2
| [JsonRequired]
public int WindSpeed { get; set; }
|
Require properties for deserialization - .NET | Microsoft Learn
常用
[JsonIgnore]
: 总是忽略[JsonIgnore(Condition = JsonIgnoreCondition.Never)]
: 永远显示, 是 JsonSerializerOptions
对象中, DefaultIgnoreCondition
,IgnoreReadOnlyProperties
, IgnoreReadOnlyFields
的默认值[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
: null 就忽略[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingDefault)]
: 忽略 null 或者值类型的默认值 (例如 int 默认值是 0 )
How to ignore properties with System.Text.Json - .NET | Microsoft Learn
1
2
| [JsonExtensionData]
public Dictionary<string, JsonElement>? ExtensionData { get; set; }
|
How to handle overflow JSON or use JsonElement or JsonNode in System.Text.Json - .NET | Microsoft Learn
下面是一些可能遇到的情况:
- 不传递值, 会使用实体类的默认值. 所以实体类如果不能为 null, 一定要配置默认值.
null
会破坏代码中不允许为 null
的问题. 需要 等待这个讨论完结 来最终加入一个新的 jsonserializerOptions
选项或者 特殊处理. 因此建议在 json 字符串中去掉值为 null
的数据!- 也可以不管这些, 通过用户提交的数据进行验证避免错误. 因为用户请求是一定要进行验证的.
1
2
3
4
5
6
7
8
9
10
11
12
| // 不标准的json
// 不完整的json,缺少的字段默认值
var j1 = """{"Name": "ken"}""";
var j11 = JsonSerializer.Deserialize(j1, JsonContext.Default.Person);
// 带null的json
// age为null报错.
// name为null则会传递到对象里,即使Name不允许为null值
var j2 = """{"Name": null,"Age":4}""";
var j22 = JsonSerializer.Deserialize(j2, JsonContext.Default.Person);
// 多余的字段不受影响.正常默认值
var j3 = """{"HHH":null}""";
var j33 = JsonSerializer.Deserialize(j3, JsonContext.Default.Person);
|