在本系列的上一篇文章中,我们注意到强类型ID的实体,序列化为 JSON 的时候报错了,就像这样:

{
"id": {
"value": 1
},
"name": "Apple",
"unitPrice": 0.8
}

不过想了一下,这样的意外也是在意料之中的,强类型ID是record类型,而不是原始类型,因此将其序列化为一个对象是有意义的,但这显然不是我们想要的……让我们看看如何解决这个问题。

System.Text.Json

在最新版本的ASP.NET Core(从3.0)中,默认的JSON序列化程序是System.Text.Json,因此让我首先介绍这种。

为了将强类型的id序列化为其值而不是对象,我们需要编写一个通用的 JsonConverter:

public class StronglyTypedIdJsonConverter<TStronglyTypedId, TValue> : JsonConverter<TStronglyTypedId>
where TStronglyTypedId : StronglyTypedId<TValue>
where TValue : notnull
{
public override TStronglyTypedId Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
{
if (reader.TokenType is JsonTokenType.Null)
return null; var value = JsonSerializer.Deserialize<TValue>(ref reader, options);
var factory = StronglyTypedIdHelper.GetFactory<TValue>(typeToConvert);
return (TStronglyTypedId)factory(value);
} public override void Write(Utf8JsonWriter writer, TStronglyTypedId value, JsonSerializerOptions options)
{
if (value is null)
writer.WriteNullValue();
else
JsonSerializer.Serialize(writer, value.Value, options);
}
}

逻辑很简单,对于直接读取 id.value, 对于反序列化,创建一个强类型id的实例,然后给它赋值。

然后在启动类中配置:

services.AddControllers()
.AddJsonOptions(options =>
{
options.JsonSerializerOptions.Converters.Add(
new StronglyTypedIdJsonConverter<ProductId, int>());
});

现在拿到了想要的结果:

{
"id": 1,
"name": "Apple",
"unitPrice": 0.8
}

真好!不过,还有有一个问题:我们只为添加了一个对于ProductId的转换器,但我不想为每种类型的强类型ID添加另一个转换器!我们想要一个适用于所有强类型id的转换器……,现在可以创建一个转换器工厂(ConverterFactory),就像下边这样:

public class StronglyTypedIdJsonConverterFactory : JsonConverterFactory
{
private static readonly ConcurrentDictionary<Type, JsonConverter> Cache = new(); public override bool CanConvert(Type typeToConvert)
{
return StronglyTypedIdHelper.IsStronglyTypedId(typeToConvert);
} public override JsonConverter CreateConverter(Type typeToConvert, JsonSerializerOptions options)
{
return Cache.GetOrAdd(typeToConvert, CreateConverter);
} private static JsonConverter CreateConverter(Type typeToConvert)
{
if (!StronglyTypedIdHelper.IsStronglyTypedId(typeToConvert, out var valueType))
throw new InvalidOperationException($"Cannot create converter for '{typeToConvert}'"); var type = typeof(StronglyTypedIdJsonConverter<,>).MakeGenericType(typeToConvert, valueType);
return (JsonConverter)Activator.CreateInstance(type);
}
}

首先我们查看需要转换的类型,检查它是否实际上是强类型的id,然后为该类型创建特定转换器的实例,我们添加了一些缓存,避免每次都进行反射工作。

现在,我们没有添加特定的JsonConvert,只是添加了一个Factory,然后在启动文件修改,现在,我们的转换器将应用于每个强类型ID

services.AddControllers()
.AddJsonOptions(options =>
{
options.JsonSerializerOptions.Converters.Add(
new StronglyTypedIdJsonConverterFactory());
});

Newtonsoft.Json

如果您的项目使用的是Newtonsoft.Json进行JSON序列化,那就很简单了。

当它序列化一个值时,Newtonsoft.Json 查找一个compatible JsonConverter,如果找不到,就查找一个TypeConverter, 如果TypeConverter存在,并且可以将值转换为string,那么它把值序列化为字符串, 因为我们之前定义了 TypeConverter,Newtonsoft.Json查找到了,我得到以下结果:

{
"id": "1",
"name": "Apple",
"unitPrice": 0.8
}

几乎是正确的……除了id值不应序列化为字符串,而应序列化为数字,如果id值是GUID或字符串而不是int,那就很好,则需要编写一个自定义转换器。

它和 System.Text.Json 的转换器非常相似,不同之处在于Newtonsoft.Json没有转换器工厂(ConvertFactory)的概念,相反,我们将编写一个非泛型转换器:

public class StronglyTypedIdNewtonsoftJsonConverter : JsonConverter
{
private static readonly ConcurrentDictionary<Type, JsonConverter> Cache = new(); public override bool CanConvert(Type objectType)
{
return StronglyTypedIdHelper.IsStronglyTypedId(objectType);
} public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
var converter = GetConverter(objectType);
return converter.ReadJson(reader, objectType, existingValue, serializer);
} public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
if (value is null)
{
writer.WriteNull();
}
else
{
var converter = GetConverter(value.GetType());
converter.WriteJson(writer, value, serializer);
}
} private static JsonConverter GetConverter(Type objectType)
{
return Cache.GetOrAdd(objectType, CreateConverter);
} private static JsonConverter CreateConverter(Type objectType)
{
if (!StronglyTypedIdHelper.IsStronglyTypedId(objectType, out var valueType))
throw new InvalidOperationException($"Cannot create converter for '{objectType}'"); var type = typeof(StronglyTypedIdNewtonsoftJsonConverter<,>).MakeGenericType(objectType, valueType);
return (JsonConverter)Activator.CreateInstance(type);
}
} public class StronglyTypedIdNewtonsoftJsonConverter<TStronglyTypedId, TValue> : JsonConverter<TStronglyTypedId>
where TStronglyTypedId : StronglyTypedId<TValue>
where TValue : notnull
{
public override TStronglyTypedId ReadJson(JsonReader reader, Type objectType, TStronglyTypedId existingValue, bool hasExistingValue, JsonSerializer serializer)
{
if (reader.TokenType is JsonToken.Null)
return null; var value = serializer.Deserialize<TValue>(reader);
var factory = StronglyTypedIdHelper.GetFactory<TValue>(objectType);
return (TStronglyTypedId)factory(value);
} public override void WriteJson(JsonWriter writer, TStronglyTypedId value, JsonSerializer serializer)
{
if (value is null)
writer.WriteNull();
else
writer.WriteValue(value.Value);
}
}

然后在启动文件中这样设置:

services.AddControllers()
.AddNewtonsoftJson(options =>
{
options.SerializerSettings.Converters.Add(
new StronglyTypedIdNewtonsoftJsonConverter());
});

然后,我们得到了预期的结果,输出的结果是这样:

{
"id": 1,
"name": "Apple",
"unitPrice": 0.8
}

原文作者: thomas levesque

原文链接:https://thomaslevesque.com/2020/12/07/csharp-9-records-as-strongly-typed-ids-part-3-json-serialization/

最后

欢迎扫码关注我们的公众号 【全球技术精选】,专注国外优秀博客的翻译和开源项目分享,也可以添加QQ群 897216102

使用 C# 9 的records作为强类型ID - JSON序列化的更多相关文章

  1. 使用 C# 9 的records作为强类型ID - 初次使用

    强类型ID 实体通常是整数,GUID或者string类型,因为数据库直接支持这些类型,但是,如果实体的ID的类型是一样的,比如都是整数的ID,这有可能会出现ID值传错的问题,看下边的示例. publi ...

  2. 使用 C# 9 的records作为强类型ID - 路由和查询参数

    上一篇文章,我介绍了使用 C# 9 的record类型作为强类型id,非常简洁 public record ProductId(int Value); 但是在强类型id真正可用之前,还有一些问题需要解 ...

  3. Spring+Mybatis 复杂的分组查询

    1.需要的结果数据格式为 { "responseCode": "0000", "responseMsg": null, "data ...

  4. python---CRM用户关系管理

    Day1:项目分析 一:需求分析 二:CRM角色功能介绍 三:业务场景分析 销售: .销售A 从百度推广获取了一个客户,录入了CRM系统,咨询了Python课程,但是没有报名 .销售B 从qq群获取一 ...

  5. 使用强类型实体Id来避免原始类型困扰(一)

    原文地址:https://andrewlock.net/using-strongly-typed-entity-ids-to-avoid-primitive-obsession-part-1/ 作者: ...

  6. 通过ajax 后台传递的 区域id 选中ztree的节点 并展开节点

    代码如下: < script type = "text/javascript" >    var flag = "<%=request.getParam ...

  7. Elasticsearch由浅入深(三)document的核心元数据、Id、_source元数据、全量替换、强制创建以及删除机制

    document的核心元数据 document的核心元数据有三个:_index._type._id 初始化数据: PUT test_index/test_type/ { "test_cont ...

  8. 完美解决方案-雪花算法ID到前端之后精度丢失问题

    最近公司的一个项目组要把以前的单体应用进行为服务拆分,表的ID主键使用Mybatis plus默认 的雪花算法来生成. 快下班的时候,小伙伴跑过来找我,:"快给我看看这问题,卡这卡了小半天了 ...

  9. .NET缓存框架CacheManager在混合式开发框架中的应用(1)-CacheManager的介绍和使用

    在我们开发的很多分布式项目里面(如基于WCF服务.Web API服务方式),由于数据提供涉及到数据库的相关操作,如果客户端的并发数量超过一定的数量,那么数据库的请求处理则以爆发式增长,如果数据库服务器 ...

随机推荐

  1. js基本数据类型--null和undefined的区别

    1. null类型 只有一个值null,逻辑上表示一个空对象的指针,如果要定义一个变量来保存对象,最好将该变量初始化为null,比如let a="",而不要let b这样,其实nu ...

  2. 程序员必读的 99 本书籍 & 资源

    作为程序员,始终要保持学习,一直带着纸质书还是很不便的,因此电子书对于我们还是挺需要的.为了方便广大的小伙伴也能方便找到对应的电子书,我花费洪荒之力从各个搜索网站收集了几百本常用的电子书,找到了,我要 ...

  3. centos 7 配置 mysql 5.7 主从复制

    centos 7 配置 mysql 5.7 主从复制 主库:192.168.12.3 从库:192.168.12.2 1. 主库从库所在服务器关闭防火墙Systemctl stop firewalld ...

  4. .NET Core集成CorrelationId实现全链路日志输出

    .NET Core集成CorrelationId实现全链路日志输出 一,链路追踪 随着微服务架构的流行,一次请求会涉及多个服务的调用,并且服务本身也可能会依赖其他服务,整个请求路径会构成一个调用链,当 ...

  5. 2020-2021-1 20209307《Linux内核原理与分析》第四周作业

    一.Linux内核源代码简介 1.计算机三大法宝 存储程序计算机 函数调用堆栈 中断机制 2.操作系统两把宝剑 中断上下文的切换 进程上下文的切换 3.函数目录 Linux-3.18.6/arch/x ...

  6. CTF练习②

    参考的文章链接 :https://www.cnblogs.com/chrysanthemum/p/11657008.html 这个题是强网杯的一道SQL注入的题,网上有不少的在线靶场和writeup, ...

  7. js 点击按钮下载图片,另存为

    js: 1 $(document).on('click',"#xiazai",function(){ 2 imgurl = $(".img-box").find ...

  8. Python炫技操作:五种Python 转义表示法

    1. 为什么要有转义? ASCII 表中一共有 128 个字符.这里面有我们非常熟悉的字母.数字.标点符号,这些都可以从我们的键盘中输出.除此之外,还有一些非常特殊的字符,这些字符,我通常很难用键盘上 ...

  9. 【剑指offer】03 从尾到头打印链表

    题目地址:从尾到头打印链表 题目描述                                    输入一个链表,按链表从尾到头的顺序返回一个ArrayList. 时间限制:C/C++ 1秒, ...

  10. 利用COM组件实现对WORD书签各种操作大全,看这一篇就够了

    有个需求是,程序导出一份word报告,报告中有各种各样的表格,导出时还需要插入图片. 脑海中迅速闪过好几种组件,openxml组件,com组件,npoi.为了减少程序画复杂表格,我们选用了com组件+ ...