经验首页 前端设计 程序设计 Java相关 移动开发 数据库/运维 软件/图像 大数据/云计算 其他经验
当前位置:技术经验 » JS/JS库/框架 » JSON » 查看文章
C# 中使对象序列化/反序列化 Json 支持使用派生类型以及泛型的方式
来源:cnblogs  作者:有什么不能一笑而过呢  时间:2024/3/13 10:18:34  对本文有异议

C# 中使对象序列化/反序列化 Json 支持使用派生类型以及泛型方式

废话

前言

为啥想写这个博客

  • 最近自己写的框架有用到这个

    1. 类似工作流,支持节点编码自定义,动态运行自定义.
    2. 尽量减少动态解析这就需要确定类型.

    有什么好的奇思妙想可以一起来讨论噢 (现在还是毛坯,测试各种可能性)

  • 方便C#编码过程有泛型 写起来舒服

  • 编译期确定类型

RoslynPad 以及 .Dump() 方法说明

RoslynPad 是基于Roslyn 实现的跨平台C# 编辑器,简洁轻巧
支持nuget引用包
支持.NET框架版本切换

.Dump() 方法是 RoslynPad 支持的一个诊断方法,方便 赋值并打印对象信息(看作是 Console.WriteLine就行 但是 Dump方法会返回当前访问实例 例如 int i = 1.Dump() ,i依然会被赋值为 1);

通过 [JsonDerivedType] 特性实现支持派生类型序列化/反序列化

首先定义 Base 以及 它的派生类 Sub 并重写父类的GetValue方法

  1. public class Sub:Base
  2. {
  3. public object? Value { get; set; } = 15;
  4. public override object? GetValue()
  5. {
  6. return Value;
  7. }
  8. }
  9. public class Base
  10. {
  11. public virtual object? GetValue()
  12. {
  13. return default;
  14. }
  15. }

当我们在程序中直接使用 Base 接收并调用 Sub 这个派生类的时候肯定没有任何问题(因为b运行时类型还是原来的Sub).

但是当我们如果需要将它序列化为json字符串传输的时候.

由于他已经脱离了原本类型的运行环境,只是一个json字符串,它当中没有任何关于它原来的类型信息记录,反序列化时json解析器根本不认识原来的运行时类型,他只知道应用定义的解析需要的类型是Base 而派生类 Sub.Value属性会被丢弃,但由于程序中很多地方都是用父类类型接收的,所以会导致信息的丢失.

  1. using System.Text.Json;
  2. using System.Text.Json.Serialization;
  3. Base b = new Sub();
  4. b.GetValue().Dump();
  5. string json = JsonSerializer.Serialize(b).Dump();
  6. Base desb = JsonSerializer.Deserialize<Base>(json).Dump();

输出

  1. 15 //b.GetValue().Dump();
  2. {} // string json = JsonSerializer.Serialize(b).Dump();
  3. Base //Base desb = JsonSerializer.Deserialize<Base>(json).Dump();

所以我们需要做的是在序列化/反序列化的时候生成/解析它原本类型的标记信息,让我们的应用识别到他的具体类型,这样在程序中使用父类接收的地方可以保证运行时类型正确.

System.Text.Json 提供了 JsonDerivedType 特性用以在父类中标注派生类以及序列化时候的标记名称

  1. Base b = new Sub();
  2. b.GetValue().Dump();
  3. string json = JsonSerializer.Serialize(b).Dump();
  4. Base desb = JsonSerializer.Deserialize<Base>(json).Dump();
  5. public class Sub:Base
  6. {
  7. public object? Value { get; set; } = 15;
  8. public override object? GetValue()
  9. {
  10. return Value;
  11. }
  12. }
  13. [JsonDerivedType(typeof(Sub),"subType")] // 添加特性
  14. public class Base
  15. {
  16. public virtual object? GetValue()
  17. {
  18. return default;
  19. }
  20. }

输出

  1. 15 //b.GetValue().Dump();
  2. {"$type":"subType","Value":15} // string json = JsonSerializer.Serialize(b).Dump();
  3. Sub //Base desb = JsonSerializer.Deserialize<Base>(json).Dump();
  4. Value = 15
  5. Item = <null>
  6. ValueKind = Number
  7. value__ = 4

可以看到 b 在序列化为json字符串时带上了我们特性上指定的subType并赋值给了$type 属性
当我们反序列化为运行时对象时应用正确反序列化为了Sub对象.

但这只是最简单的一个场景, 我们日常使用最多的场景还是 在继承的基础上还要加上泛型,但System.Text.Json中默认不支持泛型的序列化/反序列化.

当我们把代码改造为泛型之后会得到以下错误

  • 无法支持泛型类型

    1. [JsonDerivedType(typeof(SubT<>),"subType_G")]
    2. public class Base<T>
    3. {
    4. public virtual T? GetValue()
    5. {
    6. return default;
    7. }
    8. }

    异常

    1. Specified type 'SubT`1[T]' is not a supported derived type for the polymorphic type 'Base`1[System.Int32]'. Derived types must be assignable to the base type, must not be generic and cannot be abstract classes or interfaces unless 'JsonUnknownDerivedTypeHandling.FallBackToNearestAncestor' is specified.
  • 也无法支持不同泛型单独定义

    1. [JsonDerivedType(typeof(SubT<int>),"subType_Int")]
    2. [JsonDerivedType(typeof(SubT<bool>),"subType_Bool")]
    3. public class Base<T>
    4. {
    5. public virtual T? GetValue()
    6. {
    7. return default;
    8. }
    9. }

    异常

    1. Specified type 'SubT`1[System.Boolean]' is not a supported derived type for the polymorphic type 'Base`1[System.Int32]'. Derived types must be assignable to the base type, must not be generic and cannot be abstract classes or interfaces unless 'JsonUnknownDerivedTypeHandling.FallBackToNearestAncestor' is specified.
  • 当只定义单一泛型基础类型时可以序列化,但反序列化依然异常仍需要单独定义读取,且父类及派生类都需要定义单一泛型类型实现定义(繁琐且不实用,谁定义泛型只会用一种基础类型的泛型啊)

    1. [JsonDerivedType(typeof(SubT<int>),"subType_Int")]
    2. public class SubT<T>:Base<T>
    3. {
    4. public T TValue { get; set; }
    5. public override T? GetValue()
    6. {
    7. return TValue;
    8. }
    9. }
    10. [JsonDerivedType(typeof(Base<int>),"base_Int")]
    11. public class Base<T>
    12. {
    13. public virtual T? GetValue()
    14. {
    15. return default;
    16. }
    17. }
    1. 15 //b.GetValue().Dump();
    2. {"$type":"subType_Int","TValue":15} // string json = JsonSerializer.Serialize(b).Dump();
    3. Read unrecognized type discriminator id 'subType_Int'. Path: $ | LineNumber: 0 | // Base<int> desb = JsonSerializer.Deserialize<Base<int>>(json).Dump();BytePositionInLine: 32.

通过 [JsonConverter]特性 以及 [KnowType]特性标注派生类型实现支持自定义类型序列化

通过使用 System.Text.Json [JsonDerivedType] 可以实现简单的派生类型与基类转换.

但是遇到复杂的派生类型例如(泛型)则显得十分无力.

当我们需要支持复杂的类型转换的时候得需要用到另一个特性 JsonConvertAttribute 搭配自定义实现 JsonConvert<T> 了.

先定义一个特性用来标注序列化/反序列化过程中类型的定义包含泛型信息

  1. // 自定义泛型类型名特性
  2. public class GenericTypeNameAttribute:Attribute
  3. {
  4. // 生成的属性名称
  5. public string GenericTypePropertyName { get; set; }
  6. // 泛型基础名称
  7. public string BaseName { get; set; }
  8. // 根据泛型基础类型T属性值
  9. public string GetGValue (string genericTypeName) => $"{GeneratePrefix}_{genericTypeName}";
  10. // 生成值前缀
  11. public string GeneratePrefix => $"{BaseName}_G";
  12. }

然后将原来的 Base ,Sub 改为 Base<T>,Sub<T>,由于有了泛型 可以将之前返回值从object 改为 对应的泛型T,
并将 [GenericTypeName] 和 关键的 [JsonConverter] 添加上

  1. [GenericTypeName(GenericTypePropertyName = "$type",BaseName = nameof(Sub<T>))]
  2. [JsonConverter(typeof(SubConverter<int>))]
  3. public class Sub<T> :Base<T>
  4. {
  5. public T Value { get; set; }
  6. public override T? GetValue()
  7. {
  8. return Value;
  9. }
  10. }
  11. [GenericTypeName(GenericTypePropertyName = "$type",BaseName = nameof(Base<T>))]
  12. [KnownType(typeof(Sub<>))]
  13. [JsonConverter(typeof(BaseConverter<int>))]
  14. public class Base<T>
  15. {
  16. public virtual T? GetValue()
  17. {
  18. return default;
  19. }
  20. }

并实现 JsonConverter<Base<T>>JsonConverter<Sub<T>>

BaseConvert<T>

  1. public class BaseConverter<T>:JsonConverter<Base<T>>
  2. {
  3. public override Base<T>? Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
  4. {
  5. var markerAttribute = typeToConvert.GetCustomAttribute<GenericTypeNameAttribute>()!;
  6. string genericTypeName = markerAttribute.GenericTypePropertyName!;
  7. string? typeName = default;
  8. T? tV = default;
  9. while(reader.Read())
  10. {
  11. if(reader.TokenType == JsonTokenType.EndObject)
  12. break;
  13. if(reader.TokenType == JsonTokenType.PropertyName)
  14. {
  15. string propertyName = reader.GetString() ?? throw new ArgumentException("Base<T> PropertyName");
  16. // 如果名称等于标注特性上的属性名称
  17. if(propertyName == genericTypeName)
  18. {
  19. // 提前读取
  20. reader.Read();
  21. typeName = reader.GetString();
  22. continue;
  23. }
  24. }else
  25. {
  26. JsonConverter<T> tConverter = (JsonConverter<T>)options.GetConverter(typeof(T));
  27. tV = tConverter.Read(ref reader,typeof(T),options);
  28. }
  29. }
  30. ArgumentException.ThrowIfNullOrWhiteSpace(typeName);
  31. //这里只演示 ,偷懒,如果有值就为 Sub<T> 如果要更通用的需要根据类型手动构造
  32. if(tV is not null)
  33. {
  34. return new Sub<T>{ Value = tV };
  35. }
  36. return new Base<T>();
  37. }
  38. public override void Write(Utf8JsonWriter writer, Base<T> value, JsonSerializerOptions options)
  39. {
  40. // 获取要写入的的类型
  41. var sourceType = value.GetType()!;
  42. // 获取 泛型 T 类型的名称
  43. string gernericName = sourceType.GenericTypeArguments.First().Name;
  44. // 我们自定义的标注特性
  45. // 可以缓存起来
  46. string genericTypeName = sourceType.GetCustomAttribute<GenericTypeNameAttribute>()!.GenericTypePropertyName!;
  47. string gernericBaseTypeName = sourceType.GetCustomAttribute<GenericTypeNameAttribute>()!.BaseName;
  48. // 如果是派生类型的泛型
  49. if(sourceType.GetGenericTypeDefinition() != typeof(Base<>))
  50. {
  51. var knowTypes = Type.GetCustomAttributes<KnownTypeAttribute>();
  52. // 从 KnownType 中查找注册类型
  53. var targetType = knowTypes?.FirstOrDefault(
  54. x => x.Type?.GetGenericTypeDefinition() == sourceType.GetGenericTypeDefinition());
  55. if(targetType != null && targetType.Type != null)
  56. {
  57. // 构建泛型类型
  58. var mkType = targetType.Type.MakeGenericType(sourceType.GenericTypeArguments[0]);
  59. // 调用对应已注册类型序列化方法
  60. writer.WriteRawValue(JsonSerializer.Serialize(value,mkType));
  61. return;
  62. }
  63. }
  64. // Base<T> 本身没任何属性 写入泛型类型就结束了
  65. writer.WriteStartObject();
  66. writer.WriteString(JsonNamingPolicy.CamelCase.ConvertName(genericTypeName),$"{gernericBaseTypeName}_G_{gernericName}");
  67. writer.WriteEndObject();
  68. }
  69. }

SubConverter<T>

  1. public class SubConverter<T>: JsonConverter<Sub<T>>
  2. {
  3. public override Sub<T>? Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
  4. {
  5. // 找到用于标记的特性
  6. string genericTypeName = typeToConvert.GetCustomAttribute<GenericTypeNameAttribute>()!.GenericTypePropertyName!;
  7. // 并未用到这个typeName 只是用来记录 可以根据具体需求使用
  8. string? typeName = default;
  9. T tV = default;
  10. // 当可以继续读取的时候
  11. while(reader.Read())
  12. {
  13. // 读到json对象末尾了 退出
  14. if(reader.TokenType == JsonTokenType.EndObject)
  15. break;
  16. // 读到属性名称记录下
  17. if(reader.TokenType == JsonTokenType.PropertyName)
  18. {
  19. string propertyName = reader.GetString();
  20. // 如果属性名称是特性标记中的名称
  21. if(propertyName == genericTypeName)
  22. {
  23. // 手动继续读取
  24. reader.Read();
  25. // 获取到名称
  26. typeName = reader.GetString();
  27. // 并跳过当此循环 因为以及预读取过
  28. continue;
  29. }
  30. }else
  31. {
  32. // 当初也在想怎么构建 泛型 T 的类型的实例
  33. // 后面参照官网示例
  34. // 是通过获取 T 对应的 JsonConverter 获取 并调用 Read 方法构建 (妙啊)
  35. // 例如: T为 int 则 JsonConverter<T> 其实就是获取 JsonConverter<int> 而基础类型基本都内置
  36. // 所以不用专门去写
  37. JsonConverter<T> tConverter = (JsonConverter<T>)options.GetConverter(typeof(T));
  38. tV = tConverter.Read(ref reader,typeof(T),options);
  39. }
  40. }
  41. ArgumentException.ThrowIfNullOrWhiteSpace(typeName);
  42. return new Sub<T>(){ Value = tV };
  43. }
  44. public override void Write(Utf8JsonWriter writer, Sub<T> value, JsonSerializerOptions options)
  45. {
  46. var sourceType = value.GetType()!;
  47. string genericName = sourceType.GenericTypeArguments.First().Name;
  48. var markerAttribute = sourceType.GetCustomAttribute<GenericTypeNameAttribute>()!;
  49. string genericTypePropName = markerAttribute.GenericTypePropertyName!;
  50. writer.WriteStartObject();
  51. if(value is Sub<string> st)
  52. {
  53. writer.WriteString("Value",st.Value);
  54. }else if(value is Sub<int> it)
  55. {
  56. writer.WriteNumber("Value",it.Value);
  57. }else if(value is Sub<bool> bt)
  58. {
  59. writer.WriteBoolean("Value",bt.Value);
  60. }
  61. writer.WriteString(JsonNamingPolicy.CamelCase.ConvertName(genericTypePropName),markerAttribute.GetGValue(genericName));
  62. writer.WriteEndObject();
  63. }
  64. }

完成上述步骤之后我们就可以愉快的开始愉快的泛型序列化了......吗?

将我们的调用改为泛型调用

  1. Base<int> i = new Sub<int>{ Value = 15 };
  2. string json = JsonSerializer.Serialize(i).Dump();
  3. Base<int> des = JsonSerializer.Deserialize<Base<int>>(json);
  4. des.Dump();

输出

  1. {"Value":15,"$type":"Sub_G_Int32"} // string json = JsonSerializer.Serialize(i).Dump();
  2. Sub`1[System.Int32] // des.Dump();
  3. Value = 15

貌似没什么问题了...

等等...

泛型,那我改改类型试试

将 上面Base<T>,Sub<T> 上的 JsonConvert<T> 泛型改为 bool 试试

输出

  1. {"Value":true,"$type":"Sub_G_Boolean"} // string json = JsonSerializer.Serialize(i).
  2. Sub`1[System.Boolean] // des.Dump();
  3. Value = True

好像也没问题

Ok, 那把 Base<T>,Sub<T> 上的 JsonConvert<T>T 去掉 不指定类型 让他通用起来

  1. ......省略代码
  2. [JsonConverter(typeof(BaseConverter<>))]
  3. public class Base<T>
  4. ......省略代码

运行试试

  1. Cannot create an instance of BaseConverter`1[T] because Type.ContainsGenericParameters is true.

啊 ?

竟然异常了,这不是玩我吗 ? 竟然 JsonConvertAttribute 传入的 Type 不支持泛型
从异常信息来看 ,好像是某种约束默认不让泛型参数

because Type.ContainsGenericParameters is true

经过一番查找最后在微软官方指引里发现了 JsonConverterFactory 这个类,用来
给支持泛型的房子加上最后一块砖

借由 JsonConverterFactory 实现支持泛型序列化/反序列化

继承并重写 JsonConverterFactoryCanConvert 以及 CreateConverter 方法

  1. // 定义泛型转换器创建工厂
  2. public abstract class GenericTypeConverterFactory : JsonConverterFactory
  3. {
  4. // 泛型类型
  5. public abstract Type GenericType { get; }
  6. // 对应转换器泛型类型
  7. public abstract Type GenericJsonConvertType { get; }
  8. // 什么类型可以转换
  9. public override bool CanConvert(Type typeToConvert)
  10. {
  11. // 这里约束了只有泛型类型可以转换
  12. return typeToConvert.IsGenericType && typeToConvert.GetGenericTypeDefinition() == GenericType;
  13. }
  14. public override JsonConverter? CreateConverter(Type typeToConvert, JsonSerializerOptions options)
  15. {
  16. // 获取泛型类型
  17. Type valueType = typeToConvert.GetGenericArguments()[0];
  18. // 手动构造泛型转换器的类型
  19. Type converterType = GenericJsonConvertType.MakeGenericType(valueType);
  20. // 获取对应的实例
  21. var ist = (JsonConverter?)Activator.CreateInstance(converterType);
  22. return ist;
  23. }
  24. }
  25. public sealed class BaseConverterFactory:GenericTypeConverterFactory
  26. {
  27. public override Type GenericType => typeof(Base<>);
  28. public override Type GenericJsonConvertType => typeof(MyConverter<>);
  29. }
  30. public sealed class SubConverterFactory:GenericTypeConverterFactory
  31. {
  32. public override Type GenericType => typeof(Sub<>);
  33. public override Type GenericJsonConvertType => typeof(SubConverter<>);
  34. }

由于 JsonConverterFactory 是继承 JsonConverter 的 , 所以我们需要将 Base<T>Sub<T> 上的 JsonConvert 替换为刚刚实现的两个工厂

  1. ......省略代码
  2. [JsonConverter(typeof(BaseConverterFactory))]
  3. public class Base<T>
  4. ......省略代码

运行 bool

  1. {"Value":true,"$type":"Sub_G_Boolean"}
  2. Sub`1[System.Boolean]
  3. Value = True

运行 int

  1. {"Value":12,"$type":"Sub_G_Int32"}
  2. Sub`1[System.Int32]
  3. Value = 12

运行 string

  1. {"Value":"hello world","$type":"Sub_G_String"}
  2. Sub`1[System.String]
  3. Value = hello world

完美 !!!!

结尾

上面就是我探索 json 泛型序列化的过程.

过程还是挺曲折

感觉这个需求挺小众,找了各个网站都没有这方面的解决方案.

不甘心的我对着微软的文档一个个特性研究,生怕错过一个关于这方面的能力.

最后的解决方案已经满足了我的需求

最后,上面的代码都是我想尽快发出博客手敲出来的,难免会有错误和没有达到最优性能的情况,但总体过程还是挺完整的.

原文链接:https://www.cnblogs.com/ablewang/p/18068949

 友情链接:直通硅谷  点职佳  北美留学生论坛

本站QQ群:前端 618073944 | Java 606181507 | Python 626812652 | C/C++ 612253063 | 微信 634508462 | 苹果 692586424 | C#/.net 182808419 | PHP 305140648 | 运维 608723728

W3xue 的所有内容仅供测试,对任何法律问题及风险不承担任何责任。通过使用本站内容随之而来的风险与本站无关。
关于我们  |  意见建议  |  捐助我们  |  报错有奖  |  广告合作、友情链接(目前9元/月)请联系QQ:27243702 沸活量
皖ICP备17017327号-2 皖公网安备34020702000426号