我正在写一个 custom 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"} -- 即序列化期间的驼峰式 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属性(property)。
演示 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()) 方法,可以复制传入的 options使用 JsonSerializerOptions copy constructor , 从副本的 Converters中删除转换器列表,并将修改后的副本传递给 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, 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 的基类因为它让我可以方便地在制造的转换器中缓存复制的选项和默认转换器。
  • 如果您尝试申请 DefaultConverterFactory<T>到自定义值类型或 POCO,例如
    [JsonConverter(typeof(PersonConverter))] public record Person(string FirstName, string LastName);

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

演示 fiddle here .

关于c# - 如何在自定义 System.Text.Json JsonConverter 中使用默认序列化?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/65430420/

 
 

c# - 如何在自定义 System.Text.Json JsonConverter 中使用默认序列化?的更多相关文章

  1. 【译】System.Text.Json 的下一步是什么

    .NET 5.0 最近发布了,并带来了许多新特性和性能改进.System.Text.Json 也不例外.我们改进了性能和可靠性,并使熟悉 Newtonsoft.Json 的人更容易采用它.在这篇文章中 ...

  2. 在.Net Core 3.0中尝试新的System.Text.Json API

    .NET Core 3.0提供了一个名为System.Text.Json的全新命名空间,它支持reader/writer,文档对象模型(DOM)和序列化程序.在此博客文章中,我将介绍它如何工作以及如何 ...

  3. .NET性能系列文章二:Newtonsoft.Json vs. System.Text.Json

    微软终于追上了? 图片来自 Glenn Carstens-Peters Unsplash 欢迎来到.NET性能系列的另一章.这个系列的特点是对.NET世界中许多不同的主题进行研究.基准和比较.正如标题 ...

  4. System.Text.Json 自定义Converter实现时间转换

    Newtonsoft.Json与System.Text.Json区别 在 Newtonsoft.Json中可以使用例如 .AddJsonOptions(options => { options. ...

  5. 使用 System.Text.Json 时,如何处理 Dictionary 中 Key 为自定义类型的问题

    在使用 System.Text.Json 进行 JSON 序列化和反序列化操作时,我们会遇到一个问题:如何处理字典中的 Key 为自定义类型的问题. 背景说明 例如,我们有如下代码:   // 定义一 ...

  6. .NET Core 内置的 System.Text.Json 使用注意

    System.Text.Json 是 .NET Core 3.0 新引入的高性能 json 解析.序列化.反序列化类库,武功高强,但毕竟初入江湖,炉火还没纯青,使用时需要注意,以下是我们在实现使用中遇 ...

  7. .NET Core 3.0 System.Text.Json 和 Newtonsoft.Json 行为不一致问题及解决办法

    行为不一致 .NET Core 3.0 新出了个内置的 JSON 库, 全名叫做尼古拉斯 System.Text.Json - 性能更高占用内存更少这都不是事... 对我来说, 很多或大或小的项目能少 ...

  8. [.Net Core 3.0+/.Net 5] System.Text.Json中时间格式化

    简介 .Net Core 3.0开始全新推出了一个名为System.Text.Json的Json解析库,用于序列化和反序列化Json,此库的设计是为了取代Json.Net(Newtonsoft.Jso ...

  9. 如何使用 System.Text.Json 序列化 DateTimeOffset 为 Unix 时间戳

    在 .NET 中,日期和时间通常使用 DateTime 或 DateTimeOffset 来表示.这两种数据类型都可以表示日期和时间,但它们之间有一些明显的区别.DateTime 是不带时区信息的,而 ...

  10. 从 Newtonsoft.Json 迁移到 System.Text.Json

    一.写在前面 System.Text.Json 是 .NET Core 3 及以上版本内置的 Json 序列化组件,刚推出的时候经常看到踩各种坑的吐槽,现在经过几个版本的迭代优化,提升了易用性,修复了 ...

随机推荐

  1. Matplotlib Installing an official release from resources 源码安装Matplotlib官方版本

    Installation Installing an official release Matplotlib releases are available as wheel packages for ...

  2. CentOS 防火墙配置实战精要

    防火墙是服务器最重要的安全屏障,正确的操作对应用也是非常非常关键,本文参考了一些实战经验并经过实验验证,集众家之长做了简单的归纳整理,希望能帮助你更好地操作防火墙. 设置CentOS防火墙开放端口 在 ...

  3. docker 应用篇————具名挂载和匿名挂载[十三]

    前言 简单整理一下具名挂载和匿名挂载. 正文 来看一下匿名挂载. 这里-v指定了容器内部的路径,但是没有指定容器外部的路径,那么挂载到了什么地方. 用inspect 查看一下. 挂载到这个位置了. 然 ...

  4. Sarsa模型和Q_learning模型简记

    1. Sarsa模型 1.1 Sarsa类代码: class SarsaAgent(object): def __init__(self,state_n,action_n,learning_rate= ...

  5. leetcode第 181 场周赛

    5364. 按既定顺序创建目标数组 给你两个整数数组 nums 和 index.你需要按照以下规则创建目标数组: 目标数组 target 最初为空. 按从左到右的顺序依次读取 nums[i] 和 in ...

  6. 10个常用的JS工具库,80%的项目都在用

    高手区别于普通人的重要一点是,他们善于利用工具,把更多的时间留给了规划和思考.写代码也是同样的道理,工具用好了,你就有更多的时间来规划架构和攻克难点.今天就给大家分享一下当前最流行的 js 工具库,如 ...

  7. 轻松复现一张AI图片

    轻松复现一张AI图片 现在有一个非常漂亮的AI图片,你是不是想知道他是怎么生成的? 今天我会交给大家三种方法,学会了,什么图都可以手到擒来了. 需要的软件 在本教程中,我们将使用AUTOMATIC11 ...

  8. 力扣67(java)-二进制求和(简单)

    题目: 给你两个二进制字符串,返回它们的和(用二进制表示). 输入为 非空 字符串且只包含数字 1 和 0. 示例 1: 输入: a = "11", b = "1&quo ...

  9. Kruise Rollout:灵活可插拔的渐进式发布框架

    简介: Kruise Rollout 是 OpenKruise 社区开源的渐进式交付框架.Kruise Rollout 支持配合流量和实例灰度的金丝雀发布.蓝绿发布.A/B Testing 发布,以及 ...

  10. RocketMQ 消息集成:多类型业务消息——定时消息

    简介: 本篇将继续业务消息集成的场景,从使用场景.应用案例.功能原理以及最佳实践等角度介绍 RocketMQ 的定时消息功能. 作者:凯易.明锻   引言   Apache RocketMQ 诞生至今 ...