我们给DNC3(.NET Core 3)上了一个新包,叫做System.Text.Json(点我下载),支持读写器,DOM(文档对象模型),和序列化,在这篇博文里,我会告诉大家为什么要做这个,这个包怎样工作,你们可以怎么去用它。
也可以看视频:
https://sec.ch9.ms/ch9/f427/704ea54a-d
面向DNC开发:安装DNC3的最新预览版,可获得新json库还有ASP.DNC的集成。
面向.NET Standard或DNF开发:安装这个包(确定nuget允许预览版,版本需要4.6.0-preview6.19303.8或更高),没有ASP.DNC的集成。
Json已经成为了所有现代.NET应用的重要部分,在许多情况下超过了XML的使用数量,但是.NET还没有一个很好的内建JSON处理方案,在此之前我们一直依靠Json.NET,来为.NET生态服务。
现在我们决定建立一个新的Json库:
高性能的JSON API。我们需要一批新的Json api,用Span
从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月做的讨论。
所有的示例都导入了这两个包:
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(value);
}
默认情况下,我们提供缩小的Json,如果你想提供些人类可读的东西,可以向序列化器传入一个JsonSerializerOptions实例,还能配置其他设置,例如处理评论,尾随逗号和命名策略。
string SerializePrettyPrint(WeatherForecast value)
{
var options = new JsonSerializerOptions
{
WriteIndented = true
};
return JsonSerializer.ToString(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(json, options);
}
还支持异步序列化和反序列化:
async Task SerializeAsync(WeatherForecast value, Stream stream)
{
await JsonSerializer.WriteAsync(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(记录类型)),以后会加。
有时候你不想反序列化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中的大部分Json使用都靠自动序列化,换句话说你的绝大多数应用代码不知道ASP.DNC用的是哪个Json库,这样切换很容易。
在这可以看到在MVC和SignalR中如何启用新Json库。
在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正式版的一部分发布。
从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!"}