我们给DNC3(.NET Core 3)上了一个新包,叫做System.Text.Json(点我下载),支持读写器,DOM(文档对象模型),和序列化,在这篇博文里,我会告诉大家为什么要做这个,这个包怎样工作,你们可以怎么去用它。
也可以看视频:
https://sec.ch9.ms/ch9/f427/704ea54a-d
获取新Json库
面向DNC开发:安装DNC3的最新预览版,可获得新json库还有ASP.DNC的集成。
面向.NET Standard或DNF开发:安装这个包(确定nuget允许预览版,版本需要4.6.0-preview6.19303.8或更高),没有ASP.DNC的集成。
JSON在DNC3中的前景
Json已经成为了所有现代.NET应用的重要部分,在许多情况下超过了XML的使用数量,但是.NET还没有一个很好的内建JSON处理方案,在此之前我们一直依靠Json.NET,来为.NET生态服务。
现在我们决定建立一个新的Json库:
高性能的JSON API。我们需要一批新的Json api,用Span<T>达成高性能,直接处理UTF-8编码而不用转码成UTF-16的字串。两方面对ASP.DNC都很重要,因为吞吐量非常关键。我们考虑过把代码提交给Json.NET,但是既要达成我们所要的性能,又不破坏Json.NET的客户使用体验几乎是不可能的。用了System.Text.Json,视方案不同可以得到1.3-5倍的性能加速(下面有更多细节),相信还能压榨出更多性能。
从ASP.DNC中移除Json.NET的依赖。 现在ASP.DNC依赖Json.NET,这样ASP.DNC和Json.NET的耦合不仅高,还使得Json.NET的版本被平台所限制。但是Json.NET经常更新,应用开发者经常想要——或者必须使用特定的版本,因此我们打算从ASP.DNC3移除Json.NET的依赖,这样客户便可以选择适用版本,不需要担心意外崩掉后台。
为Json.NET提供了一个ASP.DNC的集成包。Json.NET基本变成了.NET处理json的瑞士军刀。这玩意提供了很多选项和工具,允许客户便利地处理json需求,我们不想让客户体验打折(原文compromise直译折中),举个蛎子,调用AddJsonOptions扩展方法即可在ASP.DNC中配置Json序列化。因此,我们准备对ASP.DNC提供一个Json.NET集成包,开发者可以选择安装,这样他们就可以在新版本中继续使用Json.NET的好处。我们还需要确保有合适的扩展点,这样其他组织也可以为他们的Json库提供类似的集成包。
要查看更多细节和这一举措跟Json.NET的关系,可以查看我们在去年10月做的讨论。
直接使用System.Text.Json
所有的示例都导入了这两个包:
using System.Text.Json;
using System.Text.Json.Serialization;
使用序列化
System.Text.Json序列化器可以异步读写Json,为UTF-8编码优化过,使其完美地适应REST API和后台应用。
class WeatherForecast
{public DateTimeOffset Date { get; set; }public int TemperatureC { get; set; }public string Summary { get; set; }
}string Serialize(WeatherForecast value)
{return JsonSerializer.ToString<WeatherForecast>(value);
}
默认情况下,我们提供缩小的Json,如果你想提供些人类可读的东西,可以向序列化器传入一个JsonSerializerOptions实例,还能配置其他设置,例如处理评论,尾随逗号和命名策略。
string SerializePrettyPrint(WeatherForecast value)
{var options = new JsonSerializerOptions{WriteIndented = true};return JsonSerializer.ToString<WeatherForecast>(value, options);
}
反序列化与此类似:
// {
// "Date": "2013-01-20T00:00:00Z",
// "TemperatureC": 42,
// "Summary": "Typical summer in Seattle. Not.",
// }
WeatherForecast Deserialize(string json)
{var options = new JsonSerializerOptions{AllowTrailingCommas = true};return JsonSerializer.Parse<WeatherForecast>(json, options);
}
还支持异步序列化和反序列化:
async Task SerializeAsync(WeatherForecast value, Stream stream)
{await JsonSerializer.WriteAsync<WeatherForecast>(value, stream);
}
你也可以用自定义特性(虽然我更喜欢叫注解)来控制序列化行为,例如忽视Json中的属性并指定属性名:
class WeatherForecast
{public DateTimeOffset Date { get; set; }// Always in Celsius.[JsonPropertyName("temp")]public int TemperatureC { get; set; }public string Summary { get; set; }// Don't serialize this property.[JsonIgnore]public bool IsHot => TemperatureC >= 30;
}
目下还不支持F#的特殊行为(例如discriminated unions(可区分联合)和record types(记录类型)),以后会加。
使用DOM
有时候你不想反序列化json负载,但是还想将其内容结构化,比方说我们有个温度集合,打算平均一下星期一的温度:
[{"date": "2013-01-07T00:00:00Z","temp": 23,},{"date": "2013-01-08T00:00:00Z","temp": 28,},{"date": "2013-01-14T00:00:00Z","temp": 8,},
]
JsonDocument类允许你便捷地访问每个属性和对应值。
double ComputeAverageTemperatures(string json)
{var options = new JsonReaderOptions{AllowTrailingCommas = true //允许尾随逗号};using (JsonDocument document = JsonDocument.Parse(json, options)){int sumOfAllTemperatures = 0;int count = 0;foreach (JsonElement element in document.RootElement.EnumerateArray()){DateTimeOffset date = element.GetProperty("date").GetDateTimeOffset();if (date.DayOfWeek == DayOfWeek.Monday){int temp = element.GetProperty("temp").GetInt32();sumOfAllTemperatures += temp;count++;}}var averageTemp = (double)sumOfAllTemperatures / count;return averageTemp;}
}
使用写入器
直接可以使用:
var options = new JsonWriterOptions
{Indented = true
};using (var stream = new MemoryStream())
{using (var writer = new Utf8JsonWriter(stream, options)){writer.WriteStartObject();writer.WriteString("date", DateTimeOffset.UtcNow);writer.WriteNumber("temp", 42);writer.WriteEndObject();}string json = Encoding.UTF8.GetString(stream.ToArray());Console.WriteLine(json);
}
读取器需要切换下令牌类型:
byte[] data = Encoding.UTF8.GetBytes(json);
Utf8JsonReader reader = new Utf8JsonReader(data, isFinalBlock: true, state: default);while (reader.Read())
{Console.Write(reader.TokenType);switch (reader.TokenType){case JsonTokenType.PropertyName:case JsonTokenType.String:{string text = reader.GetString();Console.Write(" ");Console.Write(text);break;}case JsonTokenType.Number:{int value = reader.GetInt32();Console.Write(" ");Console.Write(value);break;}// Other token types elided for brevity}Console.WriteLine();
}
和ASP.DNC的集成
接受或返回对象负载时,ASP.DNC中的大部分Json使用都靠自动序列化,换句话说你的绝大多数应用代码不知道ASP.DNC用的是哪个Json库,这样切换很容易。
在这可以看到在MVC和SignalR中如何启用新Json库。
和ASP.DNC MVC的集成
在pre5版本中,ASP.DNC MVC添加了System.Text.Json读写json的支持,从pre6开始,新的json库将成为序列化和反序列化json的默认选项。
用MvcOptions就可以使用序列化器:
services.AddControllers().AddJsonOptions(options => options.JsonSerializerOptions.WriteIndented = true);
如果你想换回去用Newtonsoft.json,需要:
1.从nuget上安装它;
2.在ConfigureServices()
添加 AddNewtonsoftJson()
调用:
public void ConfigureServices(IServiceCollection services){...services.AddControllers().AddNewtonsoftJson()...}
已知问题
System.Text.Json对OpenAPI / Swagger的支持还在开展,不太可能作为DNC3正式版的一部分发布。
和SignalR的集成
从DNC3Pre5开始,System.Text.Json是SignalR客户端和服务器的默认核心协议了。如果你想换回Newtonsoft.Json,那么客户端和服务端都可以这么做:
安装
Microsoft.AspNetCore.SignalR.Protocols.NewtonsoftJson
包.在客户端,向HubConnectionBuilder添加
AddNewtonsoftJsonProtocol()
调用:
new HubConnectionBuilder().WithUrl("/chatHub").AddNewtonsoftJsonProtocol()
.Build();
3.在服务端,让AddSignalR()
调用AddNewtonsoftJsonProtocol();
services.AddSignalR().AddNewtonsoftJsonProtocol();
性能
既然性能推动我们改良特性,那么我们就需要说说新API带来的高性能。
记住这些测试是基于预览版的,正式版的数据很可能大不相同,我们仍然在修改会影响性能的默认行为(比如大小写敏感),注意这些都是微测试,你实际能得到的好处可能会大不一样,因此如果你很在意性能,确保你自己的检测能代表你的负载,如果你遇到希望我们进一步优化的方案,请提交bug。
对System.Text.Json和Json.NET进行微测试,生成如下结果:
场景 速度 内存消耗
反序列化 2倍 持平或更低
序列化 1.5倍 持平或更低
文档 (只读) 3-5倍 文件<1MB时几乎无分配
读取器 2-3倍 几乎无分配 (在你测试之前)
写入器 1.3-1.6倍 几乎无分配
对ASP.DNC MVC的System.Text.Json进行测试:
我们写了个生成数据的ASP.DNC应用,从MVC控制器序列化和反序列化,然后康康负载大小和测量结果(RPS越高越好):
对于最普遍的负载大小,MVC的System.Text.Json在输入和输出时以更小的内存占用达到了20%的吞吐量增加。
总结
DNC3正式版会带上System.Text.Json API,属于DNC内建的Json支持,包括读写器,只读DOM,序列化和反序列化。一开始的目标是性能,一般可以有超过Json.NET2倍的性能,但是这取决于你的方案和负载,因此需要确保你的重点。
ASP.DNC3添加了System.Text.Json
的支持, 默认启用。
试试System.Text.Json
并向我们反馈!
{"happy": "coding!"}