• ASP.NET Core – System.Text.Json


    前言

    System.Text.Json 是 .NET 3.0 后推出的, 用来取代原本的 Newtonsoft.

    它的特点就是快. 一开始的时候很多东西不支持所以很少人用, .NET 6.0 后开始比较稳定了.

    这一篇就来系统的看一看它吧.

    主要参考:

    How to serialize and deserialize

    How to customize property names and values with System.Text.Json

    How to ignore properties with System.Text.Json

    How to handle overflow JSON or use JsonElement or JsonNode in System.Text.Json

    How to use a JSON document, Utf8JsonReader, and Utf8JsonWriter in System.Text.Json

    How to write custom converters for JSON serialization

    Serialize

    Serialize Whatever

    不管是匿名对象,字典,对象, dynamic 都可以直接 serialize to JSON.

    var person = new { Name = "Derrick" };
    Console.WriteLine(JsonSerializer.Serialize(person)); // {"Name":"Derrick"}
    
    var person1 = new Dictionary<string, object>
    {
        ["Name"] = "Derrick"
    };
    Console.WriteLine(JsonSerializer.Serialize(person1)); // {"Name":"Derrick"}
    
    var person2 = new Person { Name = "Derrick" };
    Console.WriteLine(JsonSerializer.Serialize(person2)); // {"Name":"Derrick"}
    
    dynamic person3 = new ExpandoObject();
    person3.Name = "Derrick";
    Console.WriteLine(JsonSerializer.Serialize(person3)); // {"Name":"Derrick"}

    Serialize to stream

    如果想 serialize 了写入 file 的话, 可以使用 SerializeAsync

    var rootPath = Path.GetDirectoryName(Assembly.GetEntryAssembly()!.Location[..Assembly.GetEntryAssembly()!.Location.IndexOf("bin\\")])!;
    using var fileStream = File.Open(Path.Combine(rootPath, "person.json"), FileMode.OpenOrCreate, FileAccess.ReadWrite);
    await JsonSerializer.SerializeAsync(fileStream, person3);

    Serialize Default Behavior

    all public properties are serialized, 所有公开属性都会被 serialize

    JSON is minified, 没有空格那些, 要美美的话要 set options

    casing of JSON names matches the .NET names, 比如 property "Name" serialize 出来也是 "Name" 而不是 javascript 喜欢的 camelcase "name"

    circular references are detected and exceptions thrown, 一旦循环引用就报错

    fields are ignored, 字段是不会被 serialize 出来的.

    enums are supported as numbers

    在 ASP.NET Core WebAPI 的环境下, default behavior 有一点不同

    JsonNamingPolicy = CamelCase

    也就是会 serialize 成 javascript 喜欢的 camelcase.

    Common Options

    WriteIndented = true, 变成有空格, human readable

    var person = new { Name = "Derrick" };
    Console.WriteLine(JsonSerializer.Serialize(person, new JsonSerializerOptions {
        WriteIndented = true
    }));
    //{
    //  "Name": "Derrick"
    //}

    PropertyNamingPolicy = JsonNamingPolicy.CamelCase, 变成 javascript 喜欢的 camelcase

    var person = new { Name = "Derrick" };
    Console.WriteLine(JsonSerializer.Serialize(person, new JsonSerializerOptions {
        PropertyNamingPolicy = JsonNamingPolicy.CamelCase
    })); // {"name":"Derrick"}

    注: 只有对象和匿名对象有效, 字典和 dynamic 则用 DictionaryKeyPolicy 哦.

    ReferenceHandler = ReferenceHandler.Preserve, 循环引用情况下用 $id 和 $ref 表达

    dynamic person = new ExpandoObject();
    person.Name = "Derrick";
    person.Person = person;
    Console.WriteLine(JsonSerializer.Serialize(person, new JsonSerializerOptions {
        ReferenceHandler = ReferenceHandler.Preserve
    })); // {"$id":"1","Name":"Derrick","Person":{"$ref":"1"}}

    ReferenceHandler = ReferenceHandler.IgnoreCycles, 循环引用的 property set to null

    dynamic person = new ExpandoObject();
    person.Name = "Derrick";
    person.Person = person;
    Console.WriteLine(JsonSerializer.Serialize(person, new JsonSerializerOptions {
        ReferenceHandler = ReferenceHandler.IgnoreCycles
    })); // {"Name":"Derrick","Person":null}

    Change Property Name and Order

    public class Person
    {
        [JsonPropertyName("NewName")]
        [JsonPropertyOrder(2)]
        public string Name { get; set; } = "";
    
        [JsonPropertyOrder(1)]
        public int Age { get; set; } 
    }

    输出

    var person = new Person {  Name = "Derrick", Age = 11 };
    Console.WriteLine(JsonSerializer.Serialize(person)); // {"Age":11,"NewName":"Derrick"}

    Custom JsonNamingPolicy

    能控制的地方不多, 只能获取到 property name 而不是 property info

    public class MyJsonNamingPolicy : JsonNamingPolicy
    {
        public override string ConvertName(string name) => name.ToUpper();
    }

    输出

    var person = new Person {  Name = "Derrick", Age = 11 };
    Console.WriteLine(
        JsonSerializer.Serialize(person, 
        new JsonSerializerOptions { PropertyNamingPolicy = new MyJsonNamingPolicy() })
    ); // {"AGE":11,"NewName":"Derrick"}

    “NewName” 不是 uppper 是因为被 JsonPropertyName override 了

    JsonIgnore

    [JsonIgnore(Condition = JsonIgnoreCondition.Always)]
    [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
    [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingDefault)]
    [JsonIgnore(Condition = JsonIgnoreCondition.Never)]
    [JsonIgnore]
    public string Name { get; set; } = "";

    Always 就是总是不要出

    Never 就是总是出 (可以 override 掉 global 的 options, 比如 IgnoreReadOnlyProperties)

    WhenWritingNull 是说, 如果 value 是 null 那么这个 property 就不要出

    WhenWritingDefault 是说, int = 0 这种 default value 也不要出.

    JsonInclude

    [JsonInclude]
    public string Name = "name";

    Filed by default 是不会被 serialize 的, 如果想要的话就加 JsonInclude, 只有 public 才可以 include 哦, non-public 会报错.

    通过 setting 配置也是可以

    var json = JsonSerializer.Serialize(person, new JsonSerializerOptions { IncludeFields = true });

    Deserialize

    Simple Deserialize

    Deserialize 比 Serialize 需要顾虑的东西比较多, 因为可能 type mismatch, underflow, overflow 都有可能发生.

    Deserialize 的时候要提供一个泛型类

    var json = JsonSerializer.Serialize(new Person { Name = "Derrick", Age = 11 });
    var person = JsonSerializer.Deserialize<Person>(json);

    如果泛型是一个 object 或者 dynamic, 那么出来的类型是 JsonElement, 这个后面会讲它是什么类型来的

    var person = JsonSerializer.Deserialize<object>(json)!;
    var typeName = person.GetType().FullName; // System.Text.Json.JsonElement

    Deserialize from Stream

    var rootPath = Path.GetDirectoryName(Assembly.GetEntryAssembly()!.Location[..Assembly.GetEntryAssembly()!.Location.IndexOf("bin\\")])!;
    using var fileStream = File.Open(Path.Combine(rootPath, "person.json"), FileMode.Open, FileAccess.Read);
    var person = await JsonSerializer.DeserializeAsync<Person>(fileStream);

    Deserialize Default Behavior

    property name matching is case-sensitive, 区分大小写

    read-only property is ignored 

    non-public constructors are ignored 

    enums are supported as numbers

    fields are ignored

    comments or trailing commas throw exceptions, 注释和结尾逗号是不允许的, 会报错

    The maximum depth is 64.

    underflow 会用 class default value

    overflow 会 ignore

    type mismatch 会报错.

    在 ASP.NET Core WebAPI 的环境下, default behavior 有一点不同

    PropertyNameCaseInsensitive = true, 不区分大小写

    NumberHandling = AllowReadingFromString, int = "100" 是 ok 的

    Type Mismatch

    Deserialize 时一旦发现 type mismatch 就会马上报错, error message 长这样

    Path 的写法和 SQL Server JSON 一样哦.

    Common Options

    PropertyNameCaseInsensitive = true

    NumberHandling = AllowReadingFromString

    AllowTrailingCommas = true

    ReadCommentHandling = JsonCommentHandling.Skip

    大部分就是为了 by pass, 不要求那么严格而已.

    JsonPropertyName, JsonNamingPolicy, JsonIgnore, JsonInclude

    上面在 Serialize 提过的在 Deserialize 也同样受

    JsonObject and JsonArray

    喜欢 javascript 一定喜欢这种写法

    var jObject = new JsonObject
    {
        ["Name"] = "Derrick",
        ["Children"] = new JsonArray
        { 
            new JsonObject { 
                ["Age"] = 11
            }
        }
    };
    var value = jObject["Children"]![0]!["Age"]!.GetValue<int>();
    var json = JsonSerializer.Serialize(jObject);

    JsonNode and JsonDocument

    JsonNode 是 muatable, Document 则是 immutable (如果只是想读就用这个, 它也比较快)

    上面说到, overflow, underflow 的问题, 要解决它就要 parse to JsonDocument 而不是一个固定了的类型

    大概长这样

    var person = new { Name = "Abc", Age = 11 };
    var json = JsonSerializer.Serialize(person);
    using var document = JsonDocument.Parse(json);
    var rootElement = document.RootElement;
    var isObject = document.RootElement.ValueKind == JsonValueKind.Object;
    foreach (var prop in rootElement.EnumerateObject())
    {
        var propertyName = prop.Name;
        var valueType = prop.Value.ValueKind;
        if (valueType == JsonValueKind.Number)
        {
            var value = prop.Value.GetDecimal();
        }
    }

    通过这个就可以遍历完整个 JSON 内容了, 然后想干嘛干嘛.

    JsonNode 的接口和 JsonDocument 差不多, 我就不写了

    Utf8JsonWriter

    图手写 JSON

    using var stream = new MemoryStream();
    using var writer = new Utf8JsonWriter(stream);
    writer.WriteStartObject();
    writer.WriteString("Name", DateTimeOffset.UtcNow);
    writer.WriteNumber("Age", 42);
    writer.WriteStartArray("Numbers");
    writer.WriteNumberValue(1);
    writer.WriteNumberValue(2);
    writer.WriteEndArray();
    writer.WriteEndObject();
    writer.Flush();
    string json = Encoding.UTF8.GetString(stream.ToArray());

    Custom Convertor

    Custom Convertor 是用来修改 value read/write 的, 类似 EF Core 的 value convertor.

    比如 Enum 默认是 serialize to number, 如果像 serialize to string 就需要自己写一个 convertor.

    有 2 种 convertor, 一种叫 basic, 一种叫 factory.

    Factory 就是就是给那种动态类型的.

    Basic Convertor

    private class MyIntConverter : JsonConverter<int>
    {
        public override int Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
        {
            return reader.GetInt32();
        }
    
        public override void Write(Utf8JsonWriter writer, int value, JsonSerializerOptions options)
        {
            writer.WriteNumberValue(value);
        }
    }

    它就是一个 read, 一个 write. 只要是 int 就会进来. 在 deserialize 的时候, typeToConvert 是来自 Deserialize<Person> 的泛型中的类型

    Register Convertor

    var options = new JsonSerializerOptions
    {
        Converters = {
            new MyIntConverter(),
            new MyEnumConverterFactory()
        }
    };

    Converters 是一个 IList, 全部放进去就可以了. 它自己会依据类型去调用.

    Factory Convertor

    顾名思义, 就是依据类型, 动态创建不同的 Convertor 来满足需求

    public class MyEnumConverterFactory : JsonConverterFactory
    {
        public override bool CanConvert(Type typeToConvert)
        {
            return typeToConvert.IsEnum;
        }
    
        public override JsonConverter? CreateConverter(Type typeToConvert, JsonSerializerOptions options)
        {
            var EnumConverterType = typeof(EnumConverter<>).MakeGenericType(typeToConvert);
            return (JsonConverter)Activator.CreateInstance(EnumConverterType)!;
        }
    }

    具体的 EnumConvertor

    private class EnumConverter<T> : JsonConverter<T> where T : Enum
    {
        public override T Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
        {
            var enumValue = reader.GetString();
            return (T)Enum.Parse(typeToConvert, enumValue!);
        }
    
        public override void Write(Utf8JsonWriter writer, T value, JsonSerializerOptions options)
        {
            writer.WriteStringValue(value.ToString());
        }
    }

    上面并没有处理 EnumMember 标签, 如果懒得自己写推荐库 : Macross.Json.Extensions 

    Serialize properties of derived classes

    参考: 

    How to serialize properties of derived classes with System.Text.Json

    Support polymorphic deserialization

    假设 Ali 继承 Person, serialize 的时候, 如果指定了 Person, 那么属于 Ali 的属性将不会出现.

    var json = JsonSerializer.Serialize<Person>(ali);

    如果属性是一个抽象, 值是一个具体, 也只是出现抽象而已.

    public class Person
    {
        public string Name { get; init; } = "";
    
        public Animal Animal { get; set; } = null!;
    }

    可以把 Animal 换成 object, 这样才会出现. (不过应该不会有人这样做吧...)

    我目前比较少遇到派生类的问题. 以后有更多研究在回来写吧.

    冷知识

    当遇上 JSON 遇上 Tuple

    参考: How does a Tuple serialize to and deserialize from JSON?

    public class Person
    {
        public List<(string A, string B)> Datas { get; set; } = new();
    }

    直接 serialize, 结果是空的...

    var json = JsonSerializer.Serialize(person); // {"Datas":[{}]}

    IncludeFileds 之后 serialize 的出来了, 但是它不是 string[], 而是 Item1, Item2 对象...

    var json = JsonSerializer.Serialize(person, new JsonSerializerOptions { IncludeFields = true }); // {"Datas":[{"Item1":"a","Item2":"b"}]}

    deserialize 也要 IncludeFileds 哦, 出来的结果是 ok 的.

    var json = JsonSerializer.Serialize(person, new JsonSerializerOptions { IncludeFields = true });
    var value = JsonSerializer.Deserialize<Person>(json, new JsonSerializerOptions { IncludeFields = true })!;
    var aValue = value.Datas.ElementAt(0).A; // collect value

    总结: 感觉勉强可以用, 但是不是很顺风水. 所以不推荐在要 serialize JSON 的地方使用 Tuple.

  • 相关阅读:
    利用锚点制作简单索引效果
    BOM之location对象
    引入CSS
    对象继承
    indexOf、instanceOf、typeOf、valueOf详解
    JSON详解
    浏览器兼容性-JS篇
    gcc堆栈排列的建议(译文)
    VLAN 学习
    DPDK KNI 接口2
  • 原文地址:https://www.cnblogs.com/keatkeat/p/15615359.html
Copyright © 2020-2023  润新知