如何在自定义System.Text.Json JsonConverter中使用默认序列化?

k2fxgqgv  于 2023-05-19  发布在  其他
关注(0)|答案(1)|浏览(333)

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

public record Person(string Name);

我已经升级到

public record Person(string FirstName, string LastName);

我写了一个转换器如下:

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"},即串行化期间的CAMEL shell 丢失。但是如果我在写的时候通过递归调用

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

则序列化将失败并出现堆栈溢出。
如何获得忽略自定义转换器的精确默认序列化?没有与Json. NET的JsonConverter.CanWrite属性等效的属性。
演示小提琴here

cbjzeqam

cbjzeqam1#

如文档中所述,转换器的选择优先级如下:

  • [JsonConverter]应用于属性。
  • 添加到Converters集合的转换器。
  • [JsonConverter]应用于自定义值类型或POCO。

此外,还有另一种情况:

  • 当通过上述三种方法中的任何一种应用工厂时,一些JsonConverterFactory返回的JsonConverter<T>

每种情况都需要单独处理。
1.如果您将 [JsonConverter]应用于属性.,则只需调用JsonSerializer.Serialize(writer, person, options);即可生成默认序列化。
1.如果您有 A converter added to the Converters collection.,那么在Write()(或Read())方法中,您可以使用JsonSerializerOptions复制构造函数复制传入的options,从副本的Converters列表中删除转换器,并将修改后的副本传递到JsonSerializer.Serialize<T>(Utf8JsonWriter, T, JsonSerializerOptions);
这在.NET Core 3.x中无法轻松完成,因为该版本中不存在复制构造函数。临时修改传入选项的Converters集合以删除转换器不是线程安全的,因此不建议这样做。相反,需要创建新选项并手动复制每个属性以及Converters集合,跳过converterType类型的转换。
请注意,这将导致递归类型(如树)的序列化出现问题,因为相同类型的嵌套对象最初不会使用转换器进行序列化。
1.如果您将 [JsonConverter]应用于自定义值类型或POCO.,则似乎没有生成默认序列化的方法。
1.转换器列表中的JsonConverterFactory返回JsonConverter<T>的情况在此答案中没有解决,因为有必要禁用工厂,而不仅仅是转换器。在这种情况下,不清楚是完全禁用工厂,还是仅针对特定的具体类型T禁用工厂。
由于在问题中,转换器被添加到Converters列表中,因此以下修改版本正确地生成了默认序列化:

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)
    {
        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;

        public DefaultConverter(JsonSerializerOptions options, DefaultConverterFactory<T> factory)
        {
            this.factory = factory;
            this.modifiedOptions = options.CopyAndRemoveConverter(factory.GetType());
        }

        public override void Write(Utf8JsonWriter writer, T value, JsonSerializerOptions options) => factory.Write(writer, value, modifiedOptions);

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

    protected virtual T Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions modifiedOptions)
        => (T)JsonSerializer.Deserialize(ref reader, typeToConvert, modifiedOptions);

    protected virtual void Write(Utf8JsonWriter writer, T value, JsonSerializerOptions modifiedOptions) 
        => JsonSerializer.Serialize(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;
    }
}

注意事项:

  • 我使用转换器工厂而不是转换器作为PersonConverter的基类,因为它允许我方便地将复制的选项缓存在制造的转换器中。
  • 如果您尝试将DefaultConverterFactory<T>应用于自定义值类型或POCO,例如
[JsonConverter(typeof(PersonConverter))] public record Person(string FirstName, string LastName);

将发生严重的堆栈溢出。
演示小提琴here

相关问题