首页 文章详情

System.Text.Json 自定义 Conveter

DotNet NB | 332 2021-10-30 15:24 0 0 0
UniSMS (合一短信)

System.Text.Json 自定义 Conveter

Intro

System.Text.Json 作为现在 .NET 默认提供的高性能 JSON 序列化器,对于一些比较特殊类型支持的并不太好,业务需求中总是有各种各样的需要,很多时候就需要用到自定义 Converter ,对于微软新出的 DateOnly/TimeOnly 也是需要自定义 Converter 来支持的

Sample

遇到一个(伪)需求,一个 Id 属性可能是字符串也可能是整型数字,举个栗子,

{"Id"1"Name""Test"}
{"Id""这是一个 Id""Name""Test"}

上面这是两个 JSON,想实现用同一个 Model 来保存结果,应该怎么做呢?

如果 Id 只会是整数或者整数的字符串,那么我们就可以用 int 来表示,System.Text.Json 从 5.0 开始支持解析带引号的数字,也就是数字的字符串形式可以参考:https://github.com/dotnet/runtime/issues/30255,只需要配置 JsonNumberHandling, 在 ASP.NET Core 中默认是启用的,是可以把 "1" 反序列化成一个 int 类型的

但是我们的示例中的 Id 是可能不是数字的,转成数字可能会失败的,所以想要把它当作 string 来处理,最后 model 是这样的

public record TestModel
{
    public string Id { get; init; } = default!;
    public string? Name { getset; }
}

但是如果是上面第一种形式的 JSON 反序列化时会发生错误,异常如下:

所以还需要自定义一个 Converter 来支持将数字转换成一个字符串,Converter 实现如下,  属性类型是什么,泛型类型就应该是什么

public class StringOrIntConverter : JsonConverter<string>
{
    public override string Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
    {
        if (reader.TokenType == JsonTokenType.Number)
        {
            return reader.GetInt32().ToString();
        }
        return reader.GetString();
    }

    public override void Write(Utf8JsonWriter writer, string value, JsonSerializerOptions options)
    {
        writer.WriteStringValue(value);
    }
}

使用 Converter 的方式有两种,一种是在某个属性上添加 JsonConverter 来使用,另一种是作为全局 Converter 来使用,直接配置 JsonSerializerOptions 中的 Converter

属性使用 Converter 示例:

public record TestModel
{
    [JsonConverter(typeof(StringOrIntConverter))]
    public string Id { get; init; } = default!;
    public string? Name { getset; }
}

配置 JsonSerializerOptions示例:

JsonSerializer.Deserialize<TestModel>(node.ToJsonString(), new JsonSerializerOptions
        {
            Converters =
            {
                new StringOrIntConverter()
            }
        });

这样我们就可以支持从一个 intstring 的转换了,完整示例如下:

var model = new TestModel
{
    Id = "123",
    Name = "456"
};
var jsonString = JsonSerializer.Serialize(model);
WriteLine(jsonString);
var node = JsonNode.Parse(jsonString);
ArgumentNullException.ThrowIfNull(node, nameof(node));
node["Id"] = 123;
var newJsonString = node.ToJsonString();
WriteLine(newJsonString);
var newModel = JsonSerializer.Deserialize<TestModel>(newJsonString);
WriteLine(model == newModel);

node["Name"] = 345;
WriteLine(JsonSerializer.Deserialize<TestModel>(node.ToJsonString(), new JsonSerializerOptions
{
    Converters =
    {
        new StringOrIntConverter()
    }
})?.Name);

输出结果如下:

output

More

可能你会问为什么不直接用 object,如果使用 object 的话,上面的 Equals 判断就要改写了,需要自己重新实现比较逻辑,而用 string 就不需要了 希望上面自定义 Converter 的代码对你有所帮助~

References

  • https://docs.microsoft.com/en-us/dotnet/standard/serialization/system-text-json-converters-how-to?WT.mc_id=DT-MVP-5004222
  • https://github.com/dotnet/runtime/issues/30255
  • https://github.com/dotnet/runtime/pull/39685
  • https://github.com/WeihanLi/SamplesInPractice/blob/master/JsonSample/SystemTextJsonSample/CustomConvertSample.cs

推荐阅读:
Kubernetes全栈架构师(Kubeadm高可用安装k8s集群)--学习笔记
.NET 云原生架构师训练营(模块一 架构师与云原生)--学习笔记
.NET Core开发实战(第1课:课程介绍)--学习笔记

点击下方卡片关注DotNet NB

一起交流学习

▲ 点击上方卡片关注DotNet NB,一起交流学习

请在公众号后台


回复 【路线图】获取.NET 2021开发者路线图
回复 【原创内容】获取公众号原创内容
回复 【峰会视频】获取.NET Conf开发者大会视频
回复 【个人简介】获取作者个人简介
回复 【年终总结】获取作者年终总结
回复 加群加入DotNet NB 交流学习群

长按识别下方二维码,或点击阅读原文。和我一起,交流学习,分享心得。


good-icon 0
favorite-icon 0
收藏
回复数量: 0
    暂无评论~~
    Ctrl+Enter