.NET Core 3.0 System.Text.Json 和 Newtonsoft.Json 行为不一致问题及解决办法
行为不一致
.NET Core 3.0 新出了个内置的 JSON 库, 全名叫做尼古拉斯
System.Text.Json- 性能更高占用内存更少这都不是事...
对我来说, 很多或大或小的项目能少个第三方依赖项, 还能规避多个依赖项的依赖 Newtonsoft.Json 版本不一致的问题, 是件极美的事情.
但是, 结果总不是不如预期那么简单和美好, 简单测试了下, 有一些跟
Newtonsoft.Json行为不一致的地方, 代码如下:
using Microsoft.VisualStudio.TestTools.UnitTesting; namespace UnitTestProject3 { [TestClass] public class TestJsonDiff { [TestMethod] [Description(description: "测试数字序列化")] public void TestNumber() { object jsonObject = new { number = 123.456 }; string aJsonString = Newtonsoft.Json.JsonConvert.SerializeObject(value: jsonObject); string bJsonString = System.Text.Json.JsonSerializer.Serialize(value: jsonObject); Assert.AreEqual(expected: aJsonString, actual: bJsonString, message: "测试数字序列化失败"); } [TestMethod] [Description(description: "测试英文序列化")] public void TestEnglish() { object jsonObject = new { english = "bla bla" }; string aJsonString = Newtonsoft.Json.JsonConvert.SerializeObject(value: jsonObject); string bJsonString = System.Text.Json.JsonSerializer.Serialize(value: jsonObject); Assert.AreEqual(expected: aJsonString, actual: bJsonString, message: "测试英文序列化失败"); } [TestMethod] [Description(description: "测试中文序列化")] public void TestChinese() { object jsonObject = new { chinese = "灰长标准的布咚发" }; string aJsonString = Newtonsoft.Json.JsonConvert.SerializeObject(value: jsonObject); string bJsonString = System.Text.Json.JsonSerializer.Serialize(value: jsonObject); Assert.AreEqual(expected: aJsonString, actual: bJsonString, message: "测试中文序列化失败"); } [TestMethod] [Description(description: "测试英文符号")] public void TestEnglishSymbol() { object jsonObject = new { symbol = @"~`!@#$%^&*()_-+={}[]:;'<>,.?/ " }; string aJsonString = Newtonsoft.Json.JsonConvert.SerializeObject(value: jsonObject); string bJsonString = System.Text.Json.JsonSerializer.Serialize(value: jsonObject); Assert.AreEqual(expected: aJsonString, actual: bJsonString, message: "测试英文符号失败"); } [TestMethod] [Description(description: "测试中文符号")] public void TestChineseSymbol() { object jsonObject = new { chinese_symbol = @"~·@#¥%……&*()—-+={}【】;:“”‘’《》,。?、" }; string aJsonString = Newtonsoft.Json.JsonConvert.SerializeObject(value: jsonObject); string bJsonString = System.Text.Json.JsonSerializer.Serialize(value: jsonObject); Assert.AreEqual(expected: aJsonString, actual: bJsonString, message: "测试中文符号失败"); } [TestMethod] [Description(description: "测试反序列化数值字符串隐式转换为数值类型")] public void TestDeserializeNumber() { string ajsonString = "{\"Number\":\"123\"}"; TestClass aJsonObject = Newtonsoft.Json.JsonConvert.DeserializeObject<TestClass>(ajsonString); // 报错,The JSON value could not be converted to System.Int32. Path: $.number | LineNumber: 0 | BytePositionInLine: 15 TestClass bJsonObject = System.Text.Json.JsonSerializer.Deserialize<TestClass>(json: ajsonString); Assert.AreEqual(expected: aJsonObject.Number, actual: bJsonObject.Number, message: "测试反序列化数值字符串隐式转换为数值类型失败"); } public class TestClass { public int Number { get; set; } } } }
先来看看总体的测试结果:
这是 VS 显示的结果
这是执行
dotnet test命令行显示的结果
这个时候需要配个图
那么问题来了, 国庆去哪玩比较好呢, 我是谁? 这是哪? 发生了什么?
可以罗列为以下行为不一致, 当然可能还有更多, 欢迎补充...让更多小伙伴看到
中文被编码
部分符号被转义
数值字符串不能隐式转换为数值类型
这里有个相关的 issue System.Text.Json: Deserialization support for quoted numbers #39473
隐式转换会出现精度缺失, 但依旧会转换成功最终导致数据计算或者数据落库等安全隐患, 是个潜在的问题, 而 Newtonsoft.Json 等默认支持隐式转换, 不一定是个合理的方式.
但是大家习惯用了, 先找找如何让二者行为一致的办法吧, 可以通过自定义类型转换器来实现.
// 自定义类型转换器 public class IntToStringConverter : JsonConverter<int> { public override int Read(ref Utf8JsonReader reader, Type type, JsonSerializerOptions options) { if (reader.TokenType == JsonTokenType.String) { ReadOnlySpan<byte> span = reader.HasValueSequence ? reader.ValueSequence.ToArray() : reader.ValueSpan; if (Utf8Parser.TryParse(span, out int number, out int bytesConsumed) && span.Length == bytesConsumed) { return number; } if (Int32.TryParse(reader.GetString(), out number)) { return number; } } return reader.GetInt32(); } public override void Write(Utf8JsonWriter writer, int value, JsonSerializerOptions options) { writer.WriteStringValue(value.ToString()); } }
使用的时候添加到配置即可, 依此类推可以自行添加更多其他类型转换器
JsonSerializerOptions options = new System.Text.Json.JsonSerializerOptions(); options.Converters.Add(item: new IntToStringConverter()); //options.Converters.Add(item: new OthersConverter()); System.Text.Json.JsonSerializer.Deserialize<TestClass>(json: ajsonString, options: options);
枚举类型的转换
System.Text.Json/tests/Serialization/EnumConverterTests.cs#L149 - 官方测试源码例子很全
[TestMethod] [Description(description: "测试枚举反序列化")] public void TestDeserializeEnum() { // 场景: 前端传过来字符串, 转成枚举 JsonSerializerOptions options = new System.Text.Json.JsonSerializerOptions(); options.Converters.Add(item: new JsonStringEnumConverter(namingPolicy: null, allowIntegerValues: false)); string jsonString = "{\"State\":\"2\"}"; Some aJsonObject = Newtonsoft.Json.JsonConvert.DeserializeObject<Some>(value: jsonString); Some bJsonObject = System.Text.Json.JsonSerializer.Deserialize<Some>(json: jsonString, options: options); Assert.AreEqual(expected: aJsonObject.State, actual: bJsonObject.State, message: "测试枚举反序列化失败"); } [TestMethod] [Description(description: "测试枚举序列化")] public void TestSerializeEnum() { // 场景: 后端枚举返回前端, 需要数值 Some some = new Some { State = State.Delete }; string aJsonString = Newtonsoft.Json.JsonConvert.SerializeObject(value: some); string bJsonString = System.Text.Json.JsonSerializer.Serialize(value: some); Assert.AreEqual(expected: aJsonString, actual: bJsonString, message: "测试枚举序列化失败"); } public enum State { Create = 1, Update = 2, Delete = 4, } public class Some { public State State { get; set; } }
不过这里延伸了一个问题, 在 ASP.NET Core 中的全局 JsonOptions 中怎么处理输入序列化和输出序列化设置不同的问题?
解决办法
解决中文会被 Unicode 编码的问题
这个问题是在博客园里找到的一种答案: .NET Core 3.0 中使用 System.Text.Json 序列化中文时的编码问题
[TestMethod] [Description(description: "测试中文序列化")] public void TestChinese() { object jsonObject = new { chinese = "灰长标准的布咚发" }; string aJsonString = Newtonsoft.Json.JsonConvert.SerializeObject(value: jsonObject); string bJsonString = System.Text.Json.JsonSerializer.Serialize( value: jsonObject, options: new System.Text.Json.JsonSerializerOptions { Encoder = System.Text.Encodings.Web.JavaScriptEncoder.Create(allowedRanges: UnicodeRanges.All) }); Assert.AreEqual(expected: aJsonString, actual: bJsonString, message: "测试中文序列化失败"); }
关键在于序列化配置加了一句
new System.Text.Json.JsonSerializerOptions { Encoder = System.Text.Encodings.Web.JavaScriptEncoder.Create(allowedRanges: UnicodeRanges.All) }
但是一些符号被转义的问题还是不管用, 寻思了一上午暂时没找到答案...
至于什么时候修复此类问题,
我去源码 corefx 溜个一圈, 暂时的发现是归到了 .NET Core 3.1 和 5.0 的开发时间线里...后面回来发现这不应该啊
但是...难道就这样了?
怀着受伤的核桃心, 中午又吃了3只大闸蟹...
诡异的是新建 ASP.NET Core API (.NET Core 3.0) 输出的 JSON 中文和转义字符都是正常, 如图:
说明一定是我们打开的方式不对...回娘家找源码, 寻寻匿匿最后发现这么一句
// If the user hasn't explicitly configured the encoder, use the less strict encoder that does not encode all non-ASCII characters. jsonSerializerOptions = jsonSerializerOptions.Copy(JavaScriptEncoder.UnsafeRelaxedJsonEscaping);
less strict ? 那对照的意思是 Newtonsoft.Json 一直使用的就是非严格模式咯, 而我们习惯使用的也是这种模式.
那么改下, 还报错的单元测试都加上配置
JavaScriptEncoder.UnsafeRelaxedJsonEscaping, 果然测试结果顺眼多了. 连上面的
UnicodeRanges.All都不需要配置了.
string bJsonString = System.Text.Json.JsonSerializer.Serialize( value: jsonObject, options: new System.Text.Json.JsonSerializerOptions { Encoder = System.Text.Encodings.Web.JavaScriptEncoder.UnsafeRelaxedJsonEscaping });
边上新开了家店, 晚上去吃吃看...
写在最后
划重点: 如果之前项目使用的是 Newtonsoft.Json, 升级之后建议还是继续使用 Newtonsoft.Json, 可以规避上诉N多可能的问题. 如果是新项目或者想少个三方依赖, 可以试试 System.Text.Json, 毕竟更轻量性能更好.
- [C#] .NET Core项目修改project.json来引用其他目录下的源码等文件的办法 & 解决多框架时 project.json 与 app.config冲突的问题
- ASP.NET MVC引用最新Newtonsoft.Json的异常问题解决办法
- 解决Spring 3.0 json返回中文乱码问题
- Node.js中AES加密和其它语言不一致问题解决办法
- 用ajax传递json到前台中文出现问号乱码问题的解决办法
- 解决MVC Json序列化的循环引用问题/EF Json序列化循引用问题---Newtonsoft.Json
- iOS开发小记:初次接入环信SDK3.0时遇到的问题及解决办法汇总
- CS0012: 类型“System.Data.Objects.DataClasses.EntityObject”在未被引用的程序集中定义----问题解决办法
- 微信小程序 ,text的margin-top失效的问题及解决办法
- EntityFramework中Json序列化的循环引用问题解决--Newtonsoft.Json
- 关于mybatis数据库表与类中属性不一致的问题终极解决办法
- cannot change version of project facet dynamic web module to 3.0问题解决办法
- [DNX]解决dnu restore时找不到Newtonsoft.Json的问题
- 关于AutoCompleteTextView 为什么必须输入两个字符才能出现补全提示的问题的解决办法
- 关于S60 3rd SDK+VC2005+Carbide.vs 3.0出现的两个问题的解决办法
- 关于AutoCompleteTextView 为什么必须输入两个字符才能出现补全提示的问题的解决办法
- 在mvc返回JSON时出错:序列化类型为“System.Data.Entity.DynamicProxies.Photos....这个会的对象时检测到循环引用 的解决办法
- 文件系统不同步问题resource is out of sync with the file system的解决办法
- jQuery.getJSON的缓存问题的解决办法
- android中画文字的换行 办法(对于遇到canvas.drawText(String s )无法实现换行问题的解决)