如何在自定义 System.Text.Json JsonConverter 中使用默认序列化? [英] How to use default serialization in a custom System.Text.Json JsonConverter?

查看:26
本文介绍了如何在自定义 System.Text.Json JsonConverter 中使用默认序列化?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我正在写一个 自定义 System.Text.Json.JsonConverter 将旧数据模型升级到新版本.我已经覆盖了 Read() 并实现了必要的后处理.但是,我根本不需要在 Write() 方法.如果我根本没有转换器,如何自动生成默认序列化?显然,我可以使用不同的 JsonSerializerOptions 进行反序列化和序列化,但是我的框架并没有直接为每个选项提供不同的选项.

I am writing a custom System.Text.Json.JsonConverter to upgrade an old data model to a new version. I have overridden Read() and implemented the necessary postprocessing. However, I don't need to do anything custom at all in the Write() method. How can I automatically generate the default serialization that I would get if I did not have a converter at all? Obviously I could just use different JsonSerializerOptions for deserialization and serialization, however my framework doesn't provide different options for each straightforwardly.

下面是一个简化的例子.假设我以前有以下数据模型:

A simplified example follows. Say I formerly had the following data model:

public record Person(string Name);

我已经升级到了

public record Person(string FirstName, string LastName);

我写了一个转换器如下:

I have written a converter as follows:

public sealed class PersonConverter : JsonConverter<Person>
{
    record PersonDTO(string FirstName, string LastName, string Name); // A DTO with both the old and new properties.

    public override Person Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
    {
        var dto = JsonSerializer.Deserialize<PersonDTO>(ref reader, options);
        var oldNames = dto?.Name?.Split(' ', StringSplitOptions.RemoveEmptyEntries) ?? Enumerable.Empty<string>();
        return new Person(dto.FirstName ?? oldNames.FirstOrDefault(), dto.LastName ?? oldNames.LastOrDefault());
    }

    public override void Write(Utf8JsonWriter writer, Person person, JsonSerializerOptions options)
        => // What do I do here? I want to preserve other options such as options.PropertyNamingPolicy, which are lost by the following call
        JsonSerializer.Serialize(writer, person);
}

和往返

var options = new JsonSerializerOptions
{
    PropertyNamingPolicy = JsonNamingPolicy.CamelCase,
    Converters = { new PersonConverter() },
};
var person = JsonSerializer.Deserialize<Person>(json, options);
var json2 = JsonSerializer.Serialize(person, options);

然后结果是{FirstName":FirstName",LastName":LastName"}——即序列化过程中的驼峰式外壳丢失.但是如果我在编写时通过递归调用传入选项

Then the result is {"FirstName":"FirstName","LastName":"LastName"} -- i.e. the camel casing during serialization is lost. But if I pass in options while writing by recursively calling

    public override void Write(Utf8JsonWriter writer, Person person, JsonSerializerOptions options)
        => // What do I do here? I want to preserve other options such as options.PropertyNamingPolicy, which are lost by the following call
        JsonSerializer.Serialize(writer, person, options);

然后序列化因堆栈溢出而失败.

Then serialization fails with a stack overflow.

如何获得忽略自定义转换器的精确默认序列化?没有等效于 Json.NET 的 JsonConverter.CanWrite 属性.

How can I get an exact default serialization that ignores the custom converter? There is no equivalent to Json.NET's JsonConverter.CanWrite property.

演示小提琴 此处.

推荐答案

docs,按以下优先级选择转换器:

As explained in the docs, converters are chosen with the following precedence:

  • [JsonConverter] 应用于属性.
  • 添加到 Converters 集合中的转换器.
  • [JsonConverter] 应用于自定义值类型或 POCO.
  • [JsonConverter] applied to a property.
  • A converter added to the Converters collection.
  • [JsonConverter] applied to a custom value type or POCO.

每个案例都需要单独处理.

Each case needs to be dealt with separately.

  1. 如果您已将 [JsonConverter] 应用于属性.,则只需调用 JsonSerializer.Serialize(writer, person, options); 将生成默认序列化.

  1. If you have [JsonConverter] applied to a property., then simply calling JsonSerializer.Serialize(writer, person, options); will generate a default serialization.

如果您将一个转换器添加到 Converters 集合中.,然后在 Write()(或 Read()) 方法,您可以使用 JsonSerializerOptions 复制构造函数,从复制的转换器 list,并将修改后的副本传入JsonSerializer.Serialize(Utf8JsonWriter, T, JsonSerializerOptions);

If you have A converter added to the Converters collection., then inside the Write() (or Read()) method, you can copy the incoming options using the JsonSerializerOptions copy constructor, remove the converter from the copy's Converters list, and pass the modified copy into JsonSerializer.Serialize<T>(Utf8JsonWriter, T, JsonSerializerOptions);

这在 .NET Core 3.x 中无法轻松完成,因为该版本中不存在复制构造函数.临时修改传入选项的 Converters 集合以移除转换器不是线程安全的,因此不建议这样做.相反,需要创建新选项并手动复制每个属性以及 Converters 集合,跳过 converterType 类型的转换.

This can't be done as easily in .NET Core 3.x because the copy constructor does not exist in that version. Temporarily modifying the Converters collection of the incoming options to remove the converter would not be not thread safe and so is not recommended. Instead one would need create new options and manually copy each property as well as the Converters collection, skipping converts of type converterType.

如果您将 [JsonConverter] 应用于自定义值类型或 POCO. 似乎没有生成默认序列化的方法.

If you have [JsonConverter] applied to a custom value type or POCO. there does not appear to be a way to generate a default serialization.

由于在问题中将转换器添加到了Converters 列表中,因此以下修改后的版本正确地生成了默认序列化:

Since, in the question, the converter is added to the Converters list, the following modified version correctly generates a default serialization:

public sealed class PersonConverter : DefaultConverterFactory<Person>
{
    record PersonDTO(string FirstName, string LastName, string Name); // A DTO with both the old and new properties.

    protected override Person Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions modifiedOptions, JsonConverter<Person> defaultConverter)
    {
        var dto = JsonSerializer.Deserialize<PersonDTO>(ref reader, modifiedOptions);
        var oldNames = dto?.Name?.Split(' ', StringSplitOptions.RemoveEmptyEntries) ?? Enumerable.Empty<string>();
        return new Person(dto.FirstName ?? oldNames.FirstOrDefault(), dto.LastName ?? oldNames.LastOrDefault());
    }
}

public abstract class DefaultConverterFactory<T> : JsonConverterFactory
{
    class DefaultConverter : JsonConverter<T>
    {
        readonly JsonSerializerOptions modifiedOptions;
        readonly DefaultConverterFactory<T> factory;
        readonly JsonConverter<T> defaultConverter;
        
        public DefaultConverter(JsonSerializerOptions options, DefaultConverterFactory<T> factory)
        {
            this.factory = factory;
            this.modifiedOptions = options.CopyAndRemoveConverter(factory.GetType());
            this.defaultConverter = (JsonConverter<T>)modifiedOptions.GetConverter(typeof(T));
        }
    
        public override void Write(Utf8JsonWriter writer, T value, JsonSerializerOptions options) => factory.Write(writer, value, modifiedOptions, defaultConverter);

        public override T Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) => factory.Read(ref reader, typeToConvert, modifiedOptions, defaultConverter);
    }

    protected virtual T Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions modifiedOptions, JsonConverter<T> defaultConverter)
        => defaultConverter.ReadOrSerialize<T>(ref reader, typeToConvert, modifiedOptions);

    protected virtual void Write(Utf8JsonWriter writer, T value, JsonSerializerOptions modifiedOptions, JsonConverter<T> defaultConverter) 
        => defaultConverter.WriteOrSerialize(writer, value, modifiedOptions);

    public override bool CanConvert(Type typeToConvert) => typeof(T) == typeToConvert;
    
    public override JsonConverter CreateConverter(Type typeToConvert, JsonSerializerOptions options) => new DefaultConverter(options, this);
}

public static class JsonSerializerExtensions
{
    public static JsonSerializerOptions CopyAndRemoveConverter(this JsonSerializerOptions options, Type converterType)
    {
        var copy = new JsonSerializerOptions(options);
        for (var i = copy.Converters.Count - 1; i >= 0; i--)
            if (copy.Converters[i].GetType() == converterType)
                copy.Converters.RemoveAt(i);
        return copy;
    }
    
    public static void WriteOrSerialize<T>(this JsonConverter<T> converter, Utf8JsonWriter writer, T value, JsonSerializerOptions options)
    {
        if (converter != null)
            converter.Write(writer, value, options);
        else
            JsonSerializer.Serialize(writer, value, options);
    }
    
    public static T ReadOrSerialize<T>(this JsonConverter<T> converter, ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
    {
        if (converter != null)
            return converter.Read(ref reader, typeToConvert, options);
        else
            return (T)JsonSerializer.Deserialize(ref reader, typeToConvert, options);
    }
}

注意事项:

  • 我使用转换器工厂而不是转换器作为 PersonConverter 的基类,因为它允许我方便地在制造的转换器中缓存复制的选项和默认转换器.

  • I used a converter factory rather than a converter as the base class for PersonConverter because it allowed me to conveniently cache the copied options and default converter inside the manufactured converter.

如果您尝试将 DefaultConverterFactory 应用于自定义值类型或 POCO,例如

If you try to apply a DefaultConverterFactory<T> to a custom value type or POCO, e.g.

[JsonConverter(typeof(PersonConverter))] public record Person(string FirstName, string LastName);

会发生令人讨厌的堆栈溢出.

A nasty stack overflow will occur.

演示小提琴 此处.

这篇关于如何在自定义 System.Text.Json JsonConverter 中使用默认序列化?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
相关文章
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆