环境:
参考:
从 Newtonsoft.Json 迁移到 System.Text.Json
System.Text.Json 常规用法
直接看代码:
var str = System.Text.Json.JsonSerializer.Serialize(new Model { Id = 1, Name = "小明" });
Console.WriteLine(str);
//out: {"Id":1,"Name":"\u5C0F\u660E"}
public class Model
{
public int Id { get; set; }
public string Name { get; set; }
}
为了能输出 “小明” 而不是 “\u5C0F\u660E”,我们需要显示声明编码方法:
var str = System.Text.Json.JsonSerializer.Serialize(new Model { Id = 1, Name = "小明" }, new System.Text.Json.JsonSerializerOptions
{
Encoder = System.Text.Encodings.Web.JavaScriptEncoder.UnsafeRelaxedJsonEscaping
});
//out: {"Id":1,"Name":"小明"}
public class Model
{
public int Id { get; set; }
public string Name { get; set; }
}
比对:Newtonsoft.Json 默认就是 “小明”
默认类属性和dictionary的key都是原样输出,如:
var str = System.Text.Json.JsonSerializer.Serialize(new Model
{
Id = 1,
Name = "小明",
Properties = new Dictionary<string, object> { { "Age", 18 }, { "Other", new { Name = "小刚" } } }
}, new System.Text.Json.JsonSerializerOptions
{
Encoder = System.Text.Encodings.Web.JavaScriptEncoder.UnsafeRelaxedJsonEscaping
});
Console.WriteLine(str);
//out: {"Id":1,"Name":"小明","Properties":{"Age":18,"Other":{"Name":"小刚"}}}
public class Model
{
public int Id { get; set; }
public string Name { get; set; }
public Dictionary<string, object> Properties { get; set; }
}
如果想首字母小写,则:
var str = System.Text.Json.JsonSerializer.Serialize(new Model
{
Id = 1,
Name = "小明",
Properties = new Dictionary<string, object> { { "Age", 18 }, { "Other", new { Name = "小刚" } } }
}, new System.Text.Json.JsonSerializerOptions
{
Encoder = System.Text.Encodings.Web.JavaScriptEncoder.UnsafeRelaxedJsonEscaping,
//属性名首字母小写
PropertyNamingPolicy = System.Text.Json.JsonNamingPolicy.CamelCase,
//字典key首字母小写
DictionaryKeyPolicy = System.Text.Json.JsonNamingPolicy.CamelCase,
});
Console.WriteLine(str);
//out: {"id":1,"name":"小明","properties":{"age":18,"other":{"name":"小刚"}}}
public class Model
{
public int Id { get; set; }
public string Name { get; set; }
public Dictionary<string, object> Properties { get; set; }
}
比对:Newtonsoft.Json 默认也是原样,也可以控制属性首字母小写(但为找到控制字典key首字母小写的方法)
默认输出是一行(上面的效果),可以指定缩进输出,如下:
var str = System.Text.Json.JsonSerializer.Serialize(new Model
{
Id = 1,
Name = "小明",
Properties = new Dictionary<string, object> { { "Age", 18 }, { "Other", new { Name = "小刚" } } }
}, new System.Text.Json.JsonSerializerOptions
{
Encoder = System.Text.Encodings.Web.JavaScriptEncoder.UnsafeRelaxedJsonEscaping,
//缩进输出
WriteIndented = true,
});
Console.WriteLine(str);
/* out:
{
"Id": 1,
"Name": "小明",
"Properties": {
"Age": 18,
"Other": {
"Name": "小刚"
}
}
}
*/
public class Model
{
public int Id { get; set; }
public string Name { get; set; }
public Dictionary<string, object> Properties { get; set; }
}
比对:Newtonsoft.Json 默认也是一行,也可以控制缩进
默认枚举转为数字输出,如下:
var str = System.Text.Json.JsonSerializer.Serialize(new Model
{
Id = 1,
Name = "小明",
Sex = EnumSex.Male,
}, new System.Text.Json.JsonSerializerOptions
{
Encoder = System.Text.Encodings.Web.JavaScriptEncoder.UnsafeRelaxedJsonEscaping,
});
Console.WriteLine(str);
//out: {"Id":1,"Name":"小明","Sex":0}
public class Model
{
public int Id { get; set; }
public string Name { get; set; }
public EnumSex Sex { get; set; }
}
public enum EnumSex
{
Male,
FeMale
}
可以添加转换器来转换成字符串:
var options = new System.Text.Json.JsonSerializerOptions
{
Encoder = System.Text.Encodings.Web.JavaScriptEncoder.UnsafeRelaxedJsonEscaping,
};
options.Converters.Add(new System.Text.Json.Serialization.JsonStringEnumConverter());
var str = System.Text.Json.JsonSerializer.Serialize(new Model
{
Id = 1,
Name = "小明",
Sex = EnumSex.Male,
}, options);
Console.WriteLine(str);
//out: {"Id":1,"Name":"小明","Sex":"Male"}
public class Model
{
public int Id { get; set; }
public string Name { get; set; }
public EnumSex Sex { get; set; }
}
public enum EnumSex
{
Male,
FeMale
}
比对:Newtonsoft.Json 默认也是转为数字,也可以控制转为字符串
默认情况下,如果某个属性值为null,也会被输出,如下:
var options = new System.Text.Json.JsonSerializerOptions
{
Encoder = System.Text.Encodings.Web.JavaScriptEncoder.UnsafeRelaxedJsonEscaping
};
var str = System.Text.Json.JsonSerializer.Serialize(new Model
{
Id = 1,
Name = null
}, options);
Console.WriteLine(str);
//out: {"Id":1,"Name":null}
public class Model
{
public int Id { get; set; }
public string Name { get; set; }
}
如果不想输出null值属性,如下:
var options = new System.Text.Json.JsonSerializerOptions
{
Encoder = System.Text.Encodings.Web.JavaScriptEncoder.UnsafeRelaxedJsonEscaping,
DefaultIgnoreCondition = System.Text.Json.Serialization.JsonIgnoreCondition.WhenWritingNull,
};
var str = System.Text.Json.JsonSerializer.Serialize(new Model
{
Id = 1,
Name = null
}, options);
Console.WriteLine(str);
//out: {"Id":1}
public class Model
{
public int Id { get; set; }
public string Name { get; set; }
}
比对:Newtonsoft.Json 默认也是输出null值属性,也可以控制不输出
默认情况下,system.text.json会直接报错,如下:
var options = new System.Text.Json.JsonSerializerOptions
{
Encoder = System.Text.Encodings.Web.JavaScriptEncoder.UnsafeRelaxedJsonEscaping,
};
var fu = new Model
{
Id = 1,
Name = "小明",
Parent = null,
Children = new List<Model> { new Model { Id = 2, Name = "小明2", Parent = null } }
};
fu.Children[0].Parent = fu;
var str = System.Text.Json.JsonSerializer.Serialize(fu, options);
Console.WriteLine(str);
/*expception:
Unhandled exception. System.Text.Json.JsonException: A possible object cycle was detected. This can either be due to a cycle or if the object depth is larger than the maximum allowed depth of 64. Consider using ReferenceHandler.Preserve on JsonSerializerOptions to support cycles. Path: $.Children.Parent.Children.Parent
*/
public class Model
{
public int Id { get; set; }
public string Name { get; set; }
public Model Parent { get; set; }
public List<Model> Children { get; set; }
}
我们可以设置忽略循环引用,如下:
var options = new System.Text.Json.JsonSerializerOptions
{
Encoder = System.Text.Encodings.Web.JavaScriptEncoder.UnsafeRelaxedJsonEscaping,
//忽略循环引用
ReferenceHandler = System.Text.Json.Serialization.ReferenceHandler.IgnoreCycles,
};
var fu = new Model
{
Id = 1,
Name = "小明",
Parent = null,
Children = new List<Model> { new Model { Id = 2, Name = "小明2", Parent = null } }
};
fu.Children[0].Parent = fu;
var str = System.Text.Json.JsonSerializer.Serialize(fu, options);
Console.WriteLine(str);
//out: {"Id":1,"Name":"小明","Parent":null,"Children":[{"Id":2,"Name":"小明2","Parent":null,"Children":null}]}
public class Model
{
public int Id { get; set; }
public string Name { get; set; }
public Model Parent { get; set; }
public List<Model> Children { get; set; }
}
比对:Newtonsoft.Json 默认也是检测到循环报错,也可以控制忽略循环引用
默认情况下,Field不会被输出,如下:
var options = new System.Text.Json.JsonSerializerOptions
{
Encoder = System.Text.Encodings.Web.JavaScriptEncoder.UnsafeRelaxedJsonEscaping,
};
var str = System.Text.Json.JsonSerializer.Serialize(new Model
{
Id = 1,
Name = "小明",
FieldAge = 18,
}, options);
Console.WriteLine(str);
//out: {"Id":1,"Name":"小明"}
public class Model
{
public int Id { get; set; }
public string Name { get; set; }
public int FieldAge;
}
为了输出Field,如下:
var options = new System.Text.Json.JsonSerializerOptions
{
Encoder = System.Text.Encodings.Web.JavaScriptEncoder.UnsafeRelaxedJsonEscaping,
// 输出 Field
IncludeFields = true,
};
var str = System.Text.Json.JsonSerializer.Serialize(new Model
{
Id = 1,
Name = "小明",
FieldAge = 18,
}, options);
Console.WriteLine(str);
//out: {{"Id":1,"Name":"小明","FieldAge":18}
public class Model
{
public int Id { get; set; }
public string Name { get; set; }
public int FieldAge;
}
比对:Newtonsoft.Json 默认输出Field,并且只读Field也会输出,这点需要注意!
当我们允许输出Field的时候,只读的Field也会被输出,如下:
var options = new System.Text.Json.JsonSerializerOptions
{
Encoder = System.Text.Encodings.Web.JavaScriptEncoder.UnsafeRelaxedJsonEscaping,
//允许输出Field
IncludeFields = true,
};
var str = System.Text.Json.JsonSerializer.Serialize(new Model
{
Id = 1,
Name = "小明",
FieldAge = 18,
}, options);
Console.WriteLine(str);
//out: {"Id":1,"Name":"小明","FieldAge":18,"ReadOnlyFieldAge":15}
public class Model
{
public int Id { get; set; }
public string Name { get; set; }
public int FieldAge;
public readonly int ReadOnlyFieldAge = 15;
}
如果我们不想输出只读的Field,如下:
var options = new System.Text.Json.JsonSerializerOptions
{
Encoder = System.Text.Encodings.Web.JavaScriptEncoder.UnsafeRelaxedJsonEscaping,
//允许输出Field
IncludeFields = true,
//忽略只读Field
IgnoreReadOnlyFields = true,
};
var str = System.Text.Json.JsonSerializer.Serialize(new Model
{
Id = 1,
Name = "小明",
FieldAge = 18,
}, options);
Console.WriteLine(str);
//out: {"Id":1,"Name":"小明","FieldAge":18}
public class Model
{
public int Id { get; set; }
public string Name { get; set; }
public int FieldAge;
public readonly int ReadOnlyFieldAge = 15;
}
比对:Newtonsoft.Json 默认输出Field,并且只读Field也会输出,这点需要注意!
上面对只读的Field做了控制,也可以对只读属性做控制,默认情况下会输出只读属性:
var options = new System.Text.Json.JsonSerializerOptions
{
Encoder = System.Text.Encodings.Web.JavaScriptEncoder.UnsafeRelaxedJsonEscaping,
};
var str = System.Text.Json.JsonSerializer.Serialize(new Model
{
Id = 1,
Name = "小明",
}, options);
Console.WriteLine(str);
//out: {"Id":1,"Name":"小明","IsAdmin":true}
public class Model
{
public int Id { get; set; }
public string Name { get; set; }
public bool IsAdmin => Name == "小明";
}
在某些场景下,减少只读属性的输出会便于存储和数据传递(体积减少),但给前端输出数据慎用,如下:
var options = new System.Text.Json.JsonSerializerOptions
{
Encoder = System.Text.Encodings.Web.JavaScriptEncoder.UnsafeRelaxedJsonEscaping,
//忽略只读属性
IgnoreReadOnlyProperties = true,
};
var str = System.Text.Json.JsonSerializer.Serialize(new Model
{
Id = 1,
Name = "小明",
}, options);
Console.WriteLine(str);
//out: {"Id":1,"Name":"小明"}
public class Model
{
public int Id { get; set; }
public string Name { get; set; }
public bool IsAdmin => Name == "小明";
}
比对:Newtonsoft.Json 默认输出只读属性,没有找到禁止的方法。
默认情况下输出的格式为 ISO 8601-1:2019
参考百度百科 ,默认类似:
var options = new System.Text.Json.JsonSerializerOptions
{
Encoder = System.Text.Encodings.Web.JavaScriptEncoder.UnsafeRelaxedJsonEscaping,
WriteIndented = true,
};
var str = System.Text.Json.JsonSerializer.Serialize(new Model
{
Id = 1,
Name = "小明",
sDateTime = DateTime.Parse("2023-03-04 18:36:12.1234567+08:00"),
sDateTimeOffset = DateTime.Parse("2023-03-04 18:36:12.1234567+08:00"),
sTimeSpan = new TimeSpan(1, 2, 3, 4, 5),
}, options);
Console.WriteLine(str);
/*out:
{
"Id": 1,
"Name": "小明",
"sDateTime": "2023-03-04T18:36:12.1234567+08:00",
"sDateTimeOffset": "2023-03-04T18:36:12.1234567+08:00",
"sTimeSpan": "1.02:03:04.0050000"
}
*/
public class Model
{
public int Id { get; set; }
public string Name { get; set; }
public DateTime sDateTime { get; set; }
public DateTimeOffset sDateTimeOffset { get; set; }
public TimeSpan sTimeSpan { get; set; }
}
如果我们想支持自定义的格式,如下:
var options = new System.Text.Json.JsonSerializerOptions
{
Encoder = System.Text.Encodings.Web.JavaScriptEncoder.UnsafeRelaxedJsonEscaping,
WriteIndented = true,
};
options.Converters.Add(new JsonConverterDatetime("yyyy-MM-dd HH:mm:ss.fff zz"));
options.Converters.Add(new JsonConverterDateTimeOffset("yyyy-MM-dd HH:mm:ss.fff zz"));
var str = System.Text.Json.JsonSerializer.Serialize(new Model
{
Id = 1,
Name = "小明",
sDateTime = DateTime.Parse("2023-03-04 18:36:12.1234567+08:00"),
sDateTimeOffset = DateTime.Parse("2023-03-04 18:36:12.1234567+08:00"),
sTimeSpan = new TimeSpan(1, 2, 3, 4, 5),
}, options);
Console.WriteLine(str);
/*out:
{
"Id": 1,
"Name": "小明",
"sDateTime": "2023-03-04 18:36:12.123 +08",
"sDateTimeOffset": "2023-03-04 18:36:12.123 +08",
"sTimeSpan": "1.02:03:04.0050000"
}
*/
public class Model
{
public int Id { get; set; }
public string Name { get; set; }
public DateTime sDateTime { get; set; }
public DateTimeOffset sDateTimeOffset { get; set; }
//public DateOnly sDateOnly { get; set; }
//public TimeOnly sTimeOnly { get; set; }
public TimeSpan sTimeSpan { get; set; }
}
public class JsonConverterDatetime : System.Text.Json.Serialization.JsonConverter<DateTime>
{
public string Format { get; set; }
public JsonConverterDatetime(string format)
{
if (format == null) throw new ArgumentNullException("format");
Format = format;
}
public override DateTime Read(ref System.Text.Json.Utf8JsonReader reader, Type typeToConvert, System.Text.Json.JsonSerializerOptions options)
{
return DateTime.Parse(reader.GetString());
}
public override void Write(System.Text.Json.Utf8JsonWriter writer, DateTime value, System.Text.Json.JsonSerializerOptions options)
{
writer.WriteStringValue(value.ToString(Format));
}
}
public class JsonConverterDateTimeOffset : System.Text.Json.Serialization.JsonConverter<DateTimeOffset>
{
public string Format { get; set; }
public JsonConverterDateTimeOffset(string format)
{
if (format == null) throw new ArgumentNullException("format");
Format = format;
}
public override DateTimeOffset Read(ref System.Text.Json.Utf8JsonReader reader, Type typeToConvert, System.Text.Json.JsonSerializerOptions options)
{
return DateTimeOffset.Parse(reader.GetString());
}
public override void Write(System.Text.Json.Utf8JsonWriter writer, DateTimeOffset value, System.Text.Json.JsonSerializerOptions options)
{
writer.WriteStringValue(value.ToString(Format));
}
}
顺便说下:ISO的这种时间格式兼容性还是比较好的,比如:mysql中可以插入
'2023-03-04T18:36:12.1234567+08:00'
,但不能插入'2023-03-04 18:36:12.1234567 +08:00'
,原因是因为后面的 “+08:00” 前不能有空格。
比对:Newtonsoft.Json 默认也是ISO的格式,也支持自定义格式。
默认.net 6 (.net7可以)不支持 DateOnly、TimeOnly,报错如下:
- Unhandled exception. System.NotSupportedException: Serialization and deserialization of ‘System.DateOnly’ instances are not supported
- Unhandled exception. System.NotSupportedException: Serialization and deserialization of ‘System.TimeOnly’ instances are not supported
可以自定义转换器支持,如下:
var options = new System.Text.Json.JsonSerializerOptions
{
Encoder = System.Text.Encodings.Web.JavaScriptEncoder.UnsafeRelaxedJsonEscaping,
WriteIndented = true,
};
options.Converters.Add(new JsonConverterDateOnly("yyyy-MM-dd"));
options.Converters.Add(new JsonConverterTimeOnly("HH:mm:ss.fffffff"));
var str = System.Text.Json.JsonSerializer.Serialize(new Model
{
Id = 1,
Name = "小明",
sDateOnly = DateOnly.Parse("2023-03-04"),
sTimeOnly = TimeOnly.Parse("18:36:12.1234567"),
}, options);
Console.WriteLine(str);
/*out:
{
"Id": 1,
"Name": "小明",
"sDateOnly": "2023-03-04",
"sTimeOnly": "18:36:12.1234567"
}
*/
public class Model
{
public int Id { get; set; }
public string Name { get; set; }
public DateOnly sDateOnly { get; set; }
public TimeOnly sTimeOnly { get; set; }
}
public class JsonConverterDateOnly : System.Text.Json.Serialization.JsonConverter<DateOnly>
{
public string Format { get; set; }
public JsonConverterDateOnly(string format)
{
if (format == null) throw new ArgumentNullException("format");
Format = format;
}
public override DateOnly Read(ref System.Text.Json.Utf8JsonReader reader, Type typeToConvert, System.Text.Json.JsonSerializerOptions options)
{
return DateOnly.Parse(reader.GetString());
}
public override void Write(System.Text.Json.Utf8JsonWriter writer, DateOnly value, System.Text.Json.JsonSerializerOptions options)
{
writer.WriteStringValue(value.ToString(Format));
}
}
public class JsonConverterTimeOnly : System.Text.Json.Serialization.JsonConverter<TimeOnly>
{
public string Format { get; set; }
public JsonConverterTimeOnly(string format)
{
if (format == null) throw new ArgumentNullException("format");
Format = format;
}
public override TimeOnly Read(ref System.Text.Json.Utf8JsonReader reader, Type typeToConvert, System.Text.Json.JsonSerializerOptions options)
{
return TimeOnly.Parse(reader.GetString());
}
public override void Write(System.Text.Json.Utf8JsonWriter writer, TimeOnly value, System.Text.Json.JsonSerializerOptions options)
{
writer.WriteStringValue(value.ToString(Format));
}
}
比对:新版本的Newtonsoft.Json 支持DateOnly 和 TimeOnly
有的时候,我们需要将数字输出为字符串,如下:
var options = new System.Text.Json.JsonSerializerOptions
{
//输出是 number => string, 读取时允许 string => number
NumberHandling = System.Text.Json.Serialization.JsonNumberHandling.AllowReadingFromString | System.Text.Json.Serialization.JsonNumberHandling.WriteAsString,
};
var json = System.Text.Json.JsonSerializer.Serialize(new Model
{
Id = 1,
Score = 99.5f
}, options);
Console.WriteLine(json);
//out: {"Id":"1","Score":"99.5"}
public class Model
{
public int Id { get; set; }
public float Score { get; set; }
}
比对:Newtonsoft.Json 可以通过转换器将数字转为字符串。
默认情况下:属性有默认值也会被输出,我们可以控制当属性/字段有默认值的时候不再输出(传递给前端时慎用):
var options = new System.Text.Json.JsonSerializerOptions
{
DefaultIgnoreCondition = System.Text.Json.Serialization.JsonIgnoreCondition.WhenWritingDefault,
IncludeFields = true,
IgnoreReadOnlyFields = false,
IgnoreReadOnlyProperties = false,
};
var json = System.Text.Json.JsonSerializer.Serialize(new Model(), options);
Console.WriteLine(json);
//out: {"Id1":1,"IdR1":1,"IdF1":1,"IdFR1":1}
public class Model
{
public int Id0 { get; set; } = 0;
public int Id1 { get; set; } = 1;
public int IdR0 => Id0;
public int IdR1 => Id1;
public int IdF0 = 0;
public int IdF1 = 1;
public readonly int IdFR0 = 0;
public readonly int IdFR1 = 1;
}
比对:Newtonsoft.Json 默认也会输出默认值的属性,也可以设置禁止输出。
[System.Text.Json.Serialization.JsonIgnore]
public int Age { get; set; }
[Newtonsoft.Json.JsonProperty(Order = 0)]
public int Age { get; set; }
[System.Text.Json.Serialization.JsonPropertyName("catAge")]
public int Age { get; set; }
默认情况下,不允许json中存在多余的逗号,如:
{
"Id": 1,
"Name": "小明",
}
这本身并没有错,因为标准json格式是严格的,不允许这个。。。
如果我们读取上面字符串,就会报错如下:
Unhandled exception. System.Text.Json.JsonException: The JSON object contains a trailing comma at the end which is not supported in this mode. Change the reader options. Path: $ | LineNumber: 4 | BytePositionInLine: 0.
—> System.Text.Json.JsonReaderException: The JSON object contains …
为了能顺利读取到,我们可以使用如下方式:
var options = new System.Text.Json.JsonSerializerOptions
{
AllowTrailingCommas = true,
};
var json = @"
{
""Id"": 1,
""Name"": ""小明"",
}
";
var model = System.Text.Json.JsonSerializer.Deserialize<Model>(json, options);
Console.WriteLine($"model.Id={model.Id},model.Id={model.Name}");
//out: model.Id=1,model.Id=小明
public class Model
{
public int Id { get; set; }
public string Name { get; set; }
}
默认情况下,不允许json中存在注释,如:
//注释
/*
注释
*/
{
//注释
"Id": 1,
"Name": "小明",
}
这本身并没有错,因为标准json格式是严格的,不允许这个。。。
如果我们读取上面字符串,就会报错如下:
Unhandled exception. System.Text.Json.JsonException: ‘/’ is an invalid start of a value. Path: $ | LineNumber: 1 | BytePositionInLine: 0.
—> System.Text.Json.JsonReaderException: ‘/’ is an invalid start of a value. …
为了能顺利读取到,我们可以使用如下方式:
var options = new System.Text.Json.JsonSerializerOptions
{
//因为我们是直接反序列化为模型,所以这里 跳过注释
ReadCommentHandling = System.Text.Json.JsonCommentHandling.Skip,
};
var json = @"
//注释
/*
注释
*/
{
//注释
""Id"": 1,
""Name"": ""小明""
}
";
var model = System.Text.Json.JsonSerializer.Deserialize<Model>(json, options);
Console.WriteLine($"model.Id={model.Id},model.Name={model.Name}");
//out: model.Id=1,model.Name=小明
默认情况下,json中的字符串不能转为数字,如下,会报错:
var json = @"
{
""Id"": ""123"",
}
";
var model = System.Text.Json.JsonSerializer.Deserialize<Model>(json);
/*exception:
Unhandled exception. System.Text.Json.JsonException: The JSON value could not be converted to System.Int32. Path: $.Id | LineNumber: 2 | BytePositionInLine: 13.
*/
public class Model
{
public int Id { get; set; }
}
如果我们想实现自动转换,如下:
var options = new System.Text.Json.JsonSerializerOptions
{
//允许将 "123" 转为数字 123
NumberHandling = System.Text.Json.Serialization.JsonNumberHandling.AllowReadingFromString,
};
var json = @"
{
""Id"": ""123"",
""Score"":""NaN""
}
";
var model = System.Text.Json.JsonSerializer.Deserialize<Model>(json, options);
Console.WriteLine($"model.Id={model.Id},model.Score={model.Score}");
//model.Id=123,model.Score=NaN
public class Model
{
public int Id { get; set; }
public float Score { get; set; }
}
顺便说一下:float中的NaN,Infinity,-Infinity 都是有意义的。