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

数据库

我正在编写一个自定义,System.Text.Json.JsonConverter以将旧数据模型升级到新版本。我已覆盖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"}-即序列化过程中的骆驼套丢失了。但是如果我在写的时候通过递归调用传递选项

    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属性。

演示在这里摆弄

数据库

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

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

每种情况都需要单独处理。

  1. 如果您已[JsonConverter]申请财产。,然后只需调用即可JsonSerializer.Serialize(writer, person, options);生成默认的序列化。

  2. 如果您已将转换器添加到Converters集合中。,然后在Write()(或Read())方法中,您可以options使用JsonSerializerOptionscopy构造函数复制传入的内容,从副本Converters列表中删除转换器,然后将修改后的副本传递到JsonSerializer.Serialize<T>(Utf8JsonWriter, T, JsonSerializerOptions);

    在.NET Core 3.x中很难做到这一点,因为该版本中不存在复制构造函数。临时修改Converters传入选项集合以删除转换器并不是线程安全的,因此不建议这样做。

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

由于有问题,转换器已添加到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, 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因为它使我可以方便地将复制的选项和默认转换器缓存在制造的转换器内部。

  • 如果您尝试将aDefaultConverterFactory<T>应用于自定义值类型或POCO,例如

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

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

演示在这里摆弄

本文收集自互联网,转载请注明来源。

如有侵权,请联系 [email protected] 删除。

编辑于
0

我来说两句

0 条评论
登录 后参与评论

相关文章