在本系列的上一篇文章中,我们注意到强类型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. JavaScript:正则表达式匹配规则

    正则表达式的语法规则: <!DOCTYPE html> <html lang="en"> <head> <meta charset=&qu ...

  2. uni-app开发中的各种问题处理

    特别注意: ※:在components下的组件,图片路径用 /static/img/back.png  这样的根路径形式,不要用../static  或者 ../../static 的形式,不然很坑, ...

  3. eclipse提示JVM版本太低

    解决方案:去eclipse的安装路径找到eclipse.ini文件,然后在头部指定JVM的版本(第一第二行) -vm C:/Program Files/Java/jdk-11.0.9/bin -sta ...

  4. 一文入门Redis

    一文入门Redis 目录 一文入门Redis 一.Redis简介 二.常用数据类型 1.String(字符串) 2.Hash(哈希) 3.List(列表) 4.Set(集合) 5.Zset(有序集合) ...

  5. Springboot之登录模块探索(含Token,验证码,网络安全等知识)

    简介 登录模块很简单,前端发送账号密码的表单,后端接收验证后即可~ 淦!可是我想多了,于是有了以下几个问题(里面还包含网络安全问题): 1.登录时的验证码 2.自动登录的实现 3.怎么维护前后端登录状 ...

  6. windows10 64位下安装oracle 11g和PL/SQL Developer

    一.材料准备: oracle11g安装包(64位) oracle11g客户端(32位) PL\SQL Developer安装包(32位) 1.下载Oracle 11g链接:http://www.ora ...

  7. DRF对Django请求响应做了技术升级

    Django视图是用来处理请求和响应的,Django默认是按Form和Template来设计的,如果要处理以JSON格式为主的RESTful API,那么就需要对Django请求和响应的处理代码进行优 ...

  8. react第八单元(什么是纯函数-组件的性能优化-pureComponent-组件性能优化的原理)

    课程目标 理解纯函数 熟练掌握组件性能优化的几种技巧 pureComponent和Component的区别 #知识点 一个函数的返回结果只依赖于它的参数,并且在执行过程里面没有副作用,我们就把这个函数 ...

  9. Java字节码中对应的JDK版本

    Java class vs. JDK version mapping Java SE 9 = 53,Java SE 8 = 52,Java SE 7 = 51,Java SE 6.0 = 50,Jav ...

  10. Python的环境是如何安装的,我来教你

    01 初见Python Python编程语言是荷兰人Guido van Rossum在1990年代开发出来的. Gudio拥数学和计算机双硕士学位,但他更喜欢计算机.当时Gudio觉得现有的编程语言无 ...