如何在自定义 System.Text.Json JsonConverter 中使用默认序列化?
How to use default serialization in a custom System.Text.Json JsonConverter?
我正在写一个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()
方法。如果根本没有转换器,我如何自动生成默认序列化?显然我可以使用不同的 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
属性.
演示 fiddle here.
如 docs 中所述,转换器的选择优先级如下:
[JsonConverter]
applied to a property.
- A converter added to the
Converters
collection.
[JsonConverter]
applied to a custom value type or POCO.
每个案例都需要单独处理。
如果您将 [JsonConverter]
应用于 属性.,则只需调用 JsonSerializer.Serialize(writer, person, options);
即可生成默认值序列化。
如果您将 A 转换器添加到 Converters
集合中。,则在 Write()
(或 Read()
) 方法,可以使用JsonSerializerOptions
copy constructor, remove the converter from the copy's Converters
列表复制传入的options
,将修改后的副本传入JsonSerializer.Serialize<T>(Utf8JsonWriter, T, JsonSerializerOptions);
这在 .NET Core 中无法轻松完成 3.x,因为该版本中不存在复制构造函数。临时修改传入选项的 Converters
集合以删除转换器不是线程安全的,因此不推荐。相反,需要创建新选项并手动复制每个 属性 以及 Converters
集合,跳过 converterType
.
类型的转换
如果您将 [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)
{
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
的基础 class 因为它允许我方便地在制造的转换器中缓存复制的选项。
如果您尝试将 DefaultConverterFactory<T>
应用于自定义值类型或 POCO,例如
[JsonConverter(typeof(PersonConverter))] public record Person(string FirstName, string LastName);
会发生严重的堆栈溢出。
演示 fiddle here.
我正在写一个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()
方法。如果根本没有转换器,我如何自动生成默认序列化?显然我可以使用不同的 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
属性.
演示 fiddle here.
如 docs 中所述,转换器的选择优先级如下:
[JsonConverter]
applied to a property.- A converter added to the
Converters
collection.[JsonConverter]
applied to a custom value type or POCO.
每个案例都需要单独处理。
如果您将
[JsonConverter]
应用于 属性.,则只需调用JsonSerializer.Serialize(writer, person, options);
即可生成默认值序列化。如果您将 A 转换器添加到
Converters
集合中。,则在Write()
(或Read()
) 方法,可以使用JsonSerializerOptions
copy constructor, remove the converter from the copy'sConverters
列表复制传入的options
,将修改后的副本传入JsonSerializer.Serialize<T>(Utf8JsonWriter, T, JsonSerializerOptions);
这在 .NET Core 中无法轻松完成 3.x,因为该版本中不存在复制构造函数。临时修改传入选项的
类型的转换Converters
集合以删除转换器不是线程安全的,因此不推荐。相反,需要创建新选项并手动复制每个 属性 以及Converters
集合,跳过converterType
.如果您将
[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)
{
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
的基础 class 因为它允许我方便地在制造的转换器中缓存复制的选项。如果您尝试将
DefaultConverterFactory<T>
应用于自定义值类型或 POCO,例如[JsonConverter(typeof(PersonConverter))] public record Person(string FirstName, string LastName);
会发生严重的堆栈溢出。
演示 fiddle here.