🔍
📢

.NET Core 新的序列化API System.Text.Json

.NET Core 3.0提供了一个叫做System.Text.Json的全新Namespace,它支持reader/writer,文档对象模型(DOM)和序列化程序。在此博客文章中,我将介绍它如何工作以及如何使用。


官方文档

获取JSON库

  • 如果以.NET Core为目标,请安装.NET Core 3.0及以上版本,该版本提供了新的JSON库和ASP.NET Core集成。
  • 如果以.NET Standard或.NET Framework为目标。安装System.Text.Json NuGet软件包(确保安装.NET Framework 4.6.0或更高版本)。为了与ASP.NET Core集成,必须以.NET Core 3.0为目标。

NET Core 3.0中JSON特性

新的JSON API通过使用Span进行性能优化,并且可以直接处理UTF-8,而无需转码为UTF-16 string 实例。这两个方面对于ASP.NET Core都是至关重要的,在ASP.NET Core中,吞吐量是关键要求。使用System.Text.Json,可以将速度提高大约1.3倍至5倍。

使用System.Text.Json

 
using System.Text.Json;
using System.Text.Json.Serialization;

使用序列化器Serializer

  • 学习.Net Core最好的方式是查看源码,下面是JsonSerializer Serialize的部分源码:
 
namespace System.Text.Json
{
    public static partial class JsonSerializer
    {
        /// <summary>
        /// Convert the provided value into a <see cref="System.String"/>.
        /// </summary>
        /// <returns>A <see cref="System.String"/> representation of the value.</returns>
        /// <param name="value">The value to convert.</param>
        /// <param name="options">Options to control the conversion behavior.</param>
        /// <remarks>Using a <see cref="System.String"/> is not as efficient as using UTF-8
        /// encoding since the implementation internally uses UTF-8. See also <see cref="SerializeToUtf8Bytes"/>
        /// and <see cref="SerializeAsync"/>.
        /// </remarks>
        public static string Serialize<TValue>(TValue value, JsonSerializerOptions options = null)
        {
            return ToStringInternal(value, typeof(TValue), options);
        }

        /// <summary>
        /// Convert the provided value into a <see cref="System.String"/>.
        /// </summary>
        /// <returns>A <see cref="System.String"/> representation of the value.</returns>
        /// <param name="value">The value to convert.</param>
        /// <param name="inputType">The type of the <paramref name="value"/> to convert.</param>
        /// <param name="options">Options to control the conversion behavior.</param>
        /// <remarks>Using a <see cref="System.String"/> is not as efficient as using UTF-8
        /// encoding since the implementation internally uses UTF-8. See also <see cref="SerializeToUtf8Bytes"/>
        /// and <see cref="SerializeAsync"/>.
        /// </remarks>
        public static string Serialize(object value, Type inputType, JsonSerializerOptions options = null)
        {
            VerifyValueAndType(value, inputType);

            return ToStringInternal(value, inputType, options);
        }

        private static string ToStringInternal(object value, Type inputType, JsonSerializerOptions options)
        {
            return WriteCoreString(value, inputType, options);
        }
    }
}

可以看到序列化最终调用的是ToStringInternal这个方法。

  • 示例
 
class Sample
{
    public DateTimeOffset Date { get; set; }
    public string Summary { get; set; }
}

string Serialize(Sample value)
{
    return JsonSerializer.Serialize<Sample>(value);
}
//obj
string SerializeObj(object value)
{
    return JsonSerializer.Serialize(value);
}

JsonSerializerOptions用于在序列化时设置配置, 例如处理注释,null处理,尾随逗号和命名策略。

 
 public static string ToJson(this object obj)
 {
    var options = new JsonSerializerOptions() { IgnoreNullValues = true, WriteIndented = true, AllowTrailingCommas = true };
    return JsonSerializer.Serialize(obj, options);
 }

使用反序列化器Deserialize

 
 Sample Deserialize(string json)
{
    var options = new JsonSerializerOptions
    {
        AllowTrailingCommas = true
    };

    return JsonSerializer.Deserialize<Sample>(json, options);
}

自定义属性来控制序列化行为

可以使用自定义属性来控制序列化行为,例如,忽略属性或者指定属性的名称:

 
class Sample
{
    //忽略
    [JsonIgnore]
    public DateTimeOffset Date { get; set; }
    //指定名称
    [JsonPropertyName("test")]
    public string Summary { get; set; }
}

使用DOM

有时,不想反序列化JSON但仍然希望对其内容进行结构化访问,这时可以使用JsonDocument

 
var options = new JsonDocumentOptions
    {
        AllowTrailingCommas = true
    };
using (JsonDocument document = JsonDocument.Parse(json, options))
{
    //todo
     foreach (JsonElement element in document.RootElement.EnumerateArray())
     {
        DateTimeOffset date = element.GetProperty("date").GetDateTimeOffset();
         //todo
     }
}

使用Utf8JsonWriter

 
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.WriteEndObject();
    }

    string json = Encoding.UTF8.GetString(stream.ToArray());
    Console.WriteLine(json);
}

使用Utf8JsonReader

 
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;
        }
    }

    Console.WriteLine();
}

System.Text.Json中的DateTime和DateTimeOffset支持

The System.Text.Json library parses and writes DateTime and DateTimeOffset values according to the ISO 8601:-2019 extended profile. Converters provide custom support for serializing and deserializing with JsonSerializer. Custom support can also be implemented when using Utf8JsonReader and Utf8JsonWriter.

具体请参考官方文档
示例:

 
 public class DateTimeConverterUsingDateTimeParse : JsonConverter<DateTime>
    {
        /// <summary>
        /// 日期格式
        /// </summary>
        public string dateTimeFormat { get; }
        /// <summary>
        /// ctor
        /// </summary>      
        /// <param name="dateTimeFormat"></param>
        public DateTimeConverterUsingDateTimeParse(string dateTimeFormat)
        {
            this.dateTimeFormat = dateTimeFormat;
        }
        public override DateTime Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
        {
            Debug.Assert(typeToConvert == typeof(DateTime));
            return DateTime.Parse(reader.GetString());
        }

        public override void Write(Utf8JsonWriter writer, DateTime value, JsonSerializerOptions options)
        {
            writer.WriteStringValue(value.ToString(dateTimeFormat));
        }
    }
 
 public static class JsonHelper
    {
        /// <summary>Converts to json.</summary>
        /// <param name="obj">The object.</param>
        /// <param name="dateTimeFormat">The date time format.</param>
        /// <returns>System.String.</returns>
        public static string ToJson(this object obj, string dateTimeFormat = "yyyy-MM-dd")
        {
            var options = new JsonSerializerOptions() { IgnoreNullValues = true, WriteIndented = true };
            options.Converters.Add(new DateTimeConverterUsingDateTimeParse(dateTimeFormat));
            return JsonSerializer.Serialize(obj, options);
        }
    }

总结

  • 在.NET Core 3.0中,System.Text.Json API提供对JSON的内置支持,包括reader/writer,只读DOM和序列化器/反序列化器。
  • 主要目标是实现高性能和低内存分配。
  • ASP.NET Core 3.0包括对System.Text.Json的支持,默认情况下启用。

补充

当前API版本反序列化不会将JSON字符串中的数字强制转换;但是JsonConverter转换器功能可以逐个属性有效地选择转换:

 
public class NumberToStringConverter : JsonConverter<int>
    {
        public override int Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
        {
            var number = 0;
            if (reader.TokenType == JsonTokenType.String)
            {
                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());
        }
    }

class Sample
    {
        public string Date { get; set; }
        public int Summary { get; set; }
    }
   var json = "{\"Date\":\"2019-10-31\",\"Summary\":\"1\"}";
            var options = new JsonSerializerOptions() { IgnoreNullValues = true, WriteIndented = true };
            options.Converters.Add(new NumberToStringConverter());
            var deserialize = JsonSerializer.Deserialize<Sample>(json, options);

当前API版本支持System.Text.Json 支持Dictionary<key,value>序列化。

 
Dictionary<string, object> dictionary = new Dictionary<string, object>
            {
                {"1", 2}
            };
 var serialize = JsonSerializer.Serialize(dictionary);
            var temjson = "{\"1\":2}";
            var deserializeDictionary = JsonSerializer.Deserialize<Dictionary<string, object>>(temjson);


1)默认设置下中文会被编码

解决方法:

public void ConfigureServices(IServiceCollection services)
{
    services.AddControllers().AddJsonOptions(options =>
    {
        options.JsonSerializerOptions.Encoder = JavaScriptEncoder.Create(UnicodeRanges.All);
    });
}


2)不能序列化公有字段(成员变量),只能序列化公有属性。
比如

public class FollowBloggerRequest
{
    public Guid blogUserGuid;
}
要改为

public class FollowBloggerRequest
{
    public Guid blogUserGuid { get; set; }
}


3)全是数字的字符串不能隐式反序列化为 int 类型
比如 { "postId": "12345" } 不能反序列化为 int PostId ,github 上的相关 issue : System.Text.Json: Deserialization support for quoted numbers


4)对于枚举类型属性值(非数字型)的序列化,需要加 [JsonStringEnumConverter] 特性
比如

[JsonConverter(typeof(JsonStringEnumConverter))]
public SiteCategoryType CategoryType { get; set; }
Json.NET 对应的是 StringEnumConverter