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

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

问题描述

我正在编写 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.

演示小提琴此处.

推荐答案

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())方法,您可以使用选项.text.json.jsonserializeroptions.-ctor?view = net-5.0#System_Text_Json_JsonSerializerOptions__ctor_System_Text_Json_JsonSerializerOptions_"rel =" nofollow noreferrer> JsonSerializerOptions 复制构造函数,从副本的中删除转换器https://docs.microsoft.com/zh-cn/dotnet/api/system.text.json.jsonserializeroptions.converters?view=net-5.0"rel =" nofollow noreferrer> 转换器 列表,然后将修改后的副本传递到 JsonSerializer.Serialize< T>(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.似乎没有一种生成默认序列化的方法./p>

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.

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

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< T> 应用于自定义值类型或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天全站免登陆