.NET Core3发布Json API

我们给DNC3(.NET Core 3)上了一个新包,叫做System.Text.Json(点我下载),支持读写器,DOM(文档对象模型),和序列化,在这篇博文里,我会告诉大家为什么要做这个,这个包怎样工作,你们可以怎么去用它。

也可以看视频:

https://sec.ch9.ms/ch9/f427/704ea54a-dsec.ch9.ms

获取新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,那么客户端和服务端都可以这么做:

  1. 安装Microsoft.AspNetCore.SignalR.Protocols.NewtonsoftJson包.

  2. 在客户端,向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!"}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值