我正在写一个 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. 4天带你上手HarmonyOS ArkUI开发

    本次HarmonyOS ArkUI入门训练营课程--健康生活实战篇,手把手教大家如何制作一个合理膳食的APP前端Demo! 课程实战样例通过ArkUI声明式UI开发框架实现,只需用几行简单直观的声明式 ...

  2. 【5】Spring IoC介绍

    有部分 Java 开发者对 IoC(Inversion Of Control)和 DI(Dependency Injection)的概念有些混淆,认为二者是对等的. IoC 其实有两种方式,一种就是 ...

  3. wireshark 抓包整理———— 从一个小案例开始 [一]

    前言 前面已经有抓包系列了,简单写一下wireshark的抓包系列,共36节,18个理论小栗子,36个实战栗子. 正文 这个例子是<<wireshark 分析就这么简单>>的一 ...

  4. WPF开发随笔收录-操作注册表

    一.前言 在windows平台软件开发过程中,注册表的操作是经常会遇到的一个场景.今天记录一下在操作注册表时遇到的一些坑: 二.正文 1.操作注册表,于是直接从网上找了一段代码来用 /// <s ...

  5. vue中 如何实现一个单文件组件

    前端开发人员只要了解过vue.js框架可能都知道单文件组件.vue.js中的单文件组件允许在一个文件中定义一个组件的所有内容.这是一个非常有用的解决方案,在浏览器网页中已经开始提倡这种机制.但是不幸的 ...

  6. C#的窗体假关闭操作例子 - 开源研究系列文章

    晚上编码的时候,想到了以前编写的窗体关闭的事情,就是带托盘图标的应用,有一个主显示操作窗体,但是主窗体点击关闭按钮的时候,实际上是窗体隐藏而非真正关闭,这个在其它的一些应用程序里有这个效果.于是就想到 ...

  7. drf——基于apiview写过滤、排序和分页

    基于APIView带过滤和排序 from rest_framework.views import APIView from .models import Book from .serializer i ...

  8. 剑指offer56(Java)-数组中出现的次数Ⅰ(中等)

    题目: 一个整型数组 nums 里除两个数字之外,其他数字都出现了两次.请写程序找出这两个只出现一次的数字.要求时间复杂度是O(n),空间复杂度是O(1). 示例 1: 输入:nums = [4,1, ...

  9. Spring Boot 微服务性能下降九成!使用 Arthas 定位根因

    简介: 接收到公司业务部门的开发反馈,应用在升级公司内部框架后,UAT(预生产)环境接口性能压测不达标. 背景 接收到公司业务部门的开发反馈,应用在升级公司内部框架后,UAT(预生产)环境接口性能压测 ...

  10. 基于MaxCompute+开放搜索的电商、零售行业搜索开发实践

    ​简介: 搜索一直是电商行业流量来源的核心入口之一,如何搭建电商行业搜索并提升搜索效果,一直是电商行业开发者努力攻克的难题.基于传统数据库或开源引擎虽然能够搭建基础搜索服务,但随着商品数据的增多和业务 ...