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

  1. {
  2. "id": {
  3. "value": 1
  4. },
  5. "name": "Apple",
  6. "unitPrice": 0.8
  7. }

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

System.Text.Json

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

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

  1. public class StronglyTypedIdJsonConverter<TStronglyTypedId, TValue> : JsonConverter<TStronglyTypedId>
  2. where TStronglyTypedId : StronglyTypedId<TValue>
  3. where TValue : notnull
  4. {
  5. public override TStronglyTypedId Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
  6. {
  7. if (reader.TokenType is JsonTokenType.Null)
  8. return null;
  9. var value = JsonSerializer.Deserialize<TValue>(ref reader, options);
  10. var factory = StronglyTypedIdHelper.GetFactory<TValue>(typeToConvert);
  11. return (TStronglyTypedId)factory(value);
  12. }
  13. public override void Write(Utf8JsonWriter writer, TStronglyTypedId value, JsonSerializerOptions options)
  14. {
  15. if (value is null)
  16. writer.WriteNullValue();
  17. else
  18. JsonSerializer.Serialize(writer, value.Value, options);
  19. }
  20. }

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

然后在启动类中配置:

  1. services.AddControllers()
  2. .AddJsonOptions(options =>
  3. {
  4. options.JsonSerializerOptions.Converters.Add(
  5. new StronglyTypedIdJsonConverter<ProductId, int>());
  6. });

现在拿到了想要的结果:

  1. {
  2. "id": 1,
  3. "name": "Apple",
  4. "unitPrice": 0.8
  5. }

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

  1. public class StronglyTypedIdJsonConverterFactory : JsonConverterFactory
  2. {
  3. private static readonly ConcurrentDictionary<Type, JsonConverter> Cache = new();
  4. public override bool CanConvert(Type typeToConvert)
  5. {
  6. return StronglyTypedIdHelper.IsStronglyTypedId(typeToConvert);
  7. }
  8. public override JsonConverter CreateConverter(Type typeToConvert, JsonSerializerOptions options)
  9. {
  10. return Cache.GetOrAdd(typeToConvert, CreateConverter);
  11. }
  12. private static JsonConverter CreateConverter(Type typeToConvert)
  13. {
  14. if (!StronglyTypedIdHelper.IsStronglyTypedId(typeToConvert, out var valueType))
  15. throw new InvalidOperationException($"Cannot create converter for '{typeToConvert}'");
  16. var type = typeof(StronglyTypedIdJsonConverter<,>).MakeGenericType(typeToConvert, valueType);
  17. return (JsonConverter)Activator.CreateInstance(type);
  18. }
  19. }

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

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

  1. services.AddControllers()
  2. .AddJsonOptions(options =>
  3. {
  4. options.JsonSerializerOptions.Converters.Add(
  5. new StronglyTypedIdJsonConverterFactory());
  6. });

Newtonsoft.Json

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

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

  1. {
  2. "id": "1",
  3. "name": "Apple",
  4. "unitPrice": 0.8
  5. }

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

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

  1. public class StronglyTypedIdNewtonsoftJsonConverter : JsonConverter
  2. {
  3. private static readonly ConcurrentDictionary<Type, JsonConverter> Cache = new();
  4. public override bool CanConvert(Type objectType)
  5. {
  6. return StronglyTypedIdHelper.IsStronglyTypedId(objectType);
  7. }
  8. public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
  9. {
  10. var converter = GetConverter(objectType);
  11. return converter.ReadJson(reader, objectType, existingValue, serializer);
  12. }
  13. public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
  14. {
  15. if (value is null)
  16. {
  17. writer.WriteNull();
  18. }
  19. else
  20. {
  21. var converter = GetConverter(value.GetType());
  22. converter.WriteJson(writer, value, serializer);
  23. }
  24. }
  25. private static JsonConverter GetConverter(Type objectType)
  26. {
  27. return Cache.GetOrAdd(objectType, CreateConverter);
  28. }
  29. private static JsonConverter CreateConverter(Type objectType)
  30. {
  31. if (!StronglyTypedIdHelper.IsStronglyTypedId(objectType, out var valueType))
  32. throw new InvalidOperationException($"Cannot create converter for '{objectType}'");
  33. var type = typeof(StronglyTypedIdNewtonsoftJsonConverter<,>).MakeGenericType(objectType, valueType);
  34. return (JsonConverter)Activator.CreateInstance(type);
  35. }
  36. }
  37. public class StronglyTypedIdNewtonsoftJsonConverter<TStronglyTypedId, TValue> : JsonConverter<TStronglyTypedId>
  38. where TStronglyTypedId : StronglyTypedId<TValue>
  39. where TValue : notnull
  40. {
  41. public override TStronglyTypedId ReadJson(JsonReader reader, Type objectType, TStronglyTypedId existingValue, bool hasExistingValue, JsonSerializer serializer)
  42. {
  43. if (reader.TokenType is JsonToken.Null)
  44. return null;
  45. var value = serializer.Deserialize<TValue>(reader);
  46. var factory = StronglyTypedIdHelper.GetFactory<TValue>(objectType);
  47. return (TStronglyTypedId)factory(value);
  48. }
  49. public override void WriteJson(JsonWriter writer, TStronglyTypedId value, JsonSerializer serializer)
  50. {
  51. if (value is null)
  52. writer.WriteNull();
  53. else
  54. writer.WriteValue(value.Value);
  55. }
  56. }

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

  1. services.AddControllers()
  2. .AddNewtonsoftJson(options =>
  3. {
  4. options.SerializerSettings.Converters.Add(
  5. new StronglyTypedIdNewtonsoftJsonConverter());
  6. });

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

  1. {
  2. "id": 1,
  3. "name": "Apple",
  4. "unitPrice": 0.8
  5. }

原文作者: 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. 把java编译成exe和安装包

    由于某些项目甲方迟迟不结算尾款,这就很烦,只能想一些办法 我们知道java,python之类的代码是没有隐私可言的,那么怎么办,总要发给甲方验收,这就要做一些操作来确保自己的利益. 通过在源代码里加上 ...

  2. Maven笔记之核心概念及常用命令

    Maven的核心概念 Maven是一款服务于java平台的自动化构建工具. 自动化构建工具还有:make->ant->maven->gradle       1.约定的目录  2.P ...

  3. Oracle数据导入Mysql中

    一.Navicat Premium中的数据迁移工具 为了生产库释放部分资源,需要将API模块迁移到mysql中,及需要导数据. 尝试了oracle to mysql工具,迁移时报错不说,这么大的数据量 ...

  4. matplotlib的学习6-annotation的标注

    import matplotlib.pyplot as plt import numpy as np ''' 当图线中某些特殊地方需要标注时,我们可以使用 annotation. matplotlib ...

  5. 属于同一网段的ip是不是就在同一个局域网?

    参考文章链接: https://zhidao.baidu.com/question/350887200.html?qbl=relate_question_0&word=%D4%DA%D2%BB ...

  6. Spring Cloud 入门教程(一): Eureka 服务注册

    创建一个Maven工程,New-Other-Maven-Maven Probject 点击Next,红色框里的选上 点击Next 点击Finsh就完成了一个Maven Probject的创建. (1) ...

  7. 5行Python代码就能实现刷爆全网的动态条形图!

    说起动态图表,最火的莫过于动态条形图了. 在B站上搜索「数据可视化」这个关键词,可以看到很多与动态条形图相关的视频. 好多视频都达到了上百万的播放量,属实厉害. 目前网上实现动态条形图现成的工具也很多 ...

  8. PHP可回调类型

    一些函数如usort和call_user_func()可以作为用户自对应函数做为回调参数,回调函数不止是简单的函数,还可以是对象的方法(类方法),包括静态方法. 用户自定义函数作为回调函数的参数,PH ...

  9. Powerdesigner中表导出sql语句关于字段注释乱码的问题

    问题说明 注释中的汉字都变成了?,应该是编码的问题. declare @CurrentUser sysname select @CurrentUser = user_name() execute sp ...

  10. .net MVC 微信公众号 获取 access_token

    官方文档说明:https://mp.weixin.qq.com/wiki?t=resource/res_main&id=mp1421140183&token=&lang=zh_ ...