C# 中使对象序列化/反序列化 Json 支持使用派生类型以及泛型的方式
C# 中使对象序列化/反序列化 Json 支持使用派生类型以及泛型方式
废话
前言
为啥想写这个博客
最近自己写的框架有用到这个
类似工作流,支持节点编码自定义,动态运行自定义. 尽量减少动态解析这就需要确定类型.
有什么好的奇思妙想可以一起来讨论噢 (现在还是毛坯,测试各种可能性)
方便C#编码过程有泛型 写起来舒服
编译期确定类型
RoslynPad 以及 .Dump() 方法说明
RoslynPad 是基于Roslyn 实现的跨平台C# 编辑器,简洁轻巧
支持nuget引用包
支持.NET框架版本切换
.Dump() 方法是 RoslynPad 支持的一个诊断方法,方便 赋值并打印对象信息(看作是 Console.WriteLine就行 但是 Dump方法会返回当前访问实例 例如 int i = 1.Dump() ,i依然会被赋值为 1);
通过 [JsonDerivedType] 特性实现支持派生类型序列化/反序列化
首先定义 Base 以及 它的派生类 Sub 并重写父类的GetValue方法
public class Sub:Base
{
public object? Value { get; set; } = 15;
public override object? GetValue()
{
return Value;
}
}
public class Base
{
public virtual object? GetValue()
{
return default;
}
}
当我们在程序中直接使用 Base 接收并调用 Sub 这个派生类的时候肯定没有任何问题(因为b运行时类型还是原来的Sub).
但是当我们如果需要将它序列化为json字符串传输的时候.
由于他已经脱离了原本类型的运行环境,只是一个json字符串,它当中没有任何关于它原来的类型信息记录,反序列化时json解析器根本不认识原来的运行时类型,他只知道应用定义的解析需要的类型是Base 而派生类 Sub.Value属性会被丢弃,但由于程序中很多地方都是用父类类型接收的,所以会导致信息的丢失.
using System.Text.Json;
using System.Text.Json.Serialization;
Base b = new Sub();
b.GetValue().Dump();
string json = JsonSerializer.Serialize(b).Dump();
Base desb = JsonSerializer.Deserialize<Base>(json).Dump();
输出
15 //b.GetValue().Dump();
{} // string json = JsonSerializer.Serialize(b).Dump();
Base //Base desb = JsonSerializer.Deserialize<Base>(json).Dump();
所以我们需要做的是在序列化/反序列化的时候生成/解析它原本类型的标记信息,让我们的应用识别到他的具体类型,这样在程序中使用父类接收的地方可以保证运行时类型正确.
System.Text.Json 提供了 JsonDerivedType 特性用以在父类中标注派生类以及序列化时候的标记名称
Base b = new Sub();
b.GetValue().Dump();
string json = JsonSerializer.Serialize(b).Dump();
Base desb = JsonSerializer.Deserialize<Base>(json).Dump();
public class Sub:Base
{
public object? Value { get; set; } = 15;
public override object? GetValue()
{
return Value;
}
}
[JsonDerivedType(typeof(Sub),"subType")] // 添加特性
public class Base
{
public virtual object? GetValue()
{
return default;
}
}
输出
15 //b.GetValue().Dump();
{"$type":"subType","Value":15} // string json = JsonSerializer.Serialize(b).Dump();
Sub //Base desb = JsonSerializer.Deserialize<Base>(json).Dump();
Value = 15
Item = <null>
ValueKind = Number
value__ = 4
可以看到 b 在序列化为json字符串时带上了我们特性上指定的subType并赋值给了$type 属性
当我们反序列化为运行时对象时应用正确反序列化为了Sub对象.
但这只是最简单的一个场景, 我们日常使用最多的场景还是 在继承的基础上还要加上泛型,但System.Text.Json中默认不支持泛型的序列化/反序列化.
当我们把代码改造为泛型之后会得到以下错误
无法支持泛型类型
[JsonDerivedType(typeof(SubT<>),"subType_G")]
public class Base<T>
{
public virtual T? GetValue()
{
return default;
}
}异常
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.
也无法支持不同泛型单独定义
[JsonDerivedType(typeof(SubT<int>),"subType_Int")]
[JsonDerivedType(typeof(SubT<bool>),"subType_Bool")]
public class Base<T>
{
public virtual T? GetValue()
{
return default;
}
}异常
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.当只定义单一泛型基础类型时可以序列化,但反序列化依然异常仍需要单独定义读取,且父类及派生类都需要定义单一泛型类型实现定义(繁琐且不实用,谁定义泛型只会用一种基础类型的泛型啊)
[JsonDerivedType(typeof(SubT<int>),"subType_Int")]
public class SubT<T>:Base<T>
{
public T TValue { get; set; } public override T? GetValue()
{
return TValue;
}
} [JsonDerivedType(typeof(Base<int>),"base_Int")]
public class Base<T>
{
public virtual T? GetValue()
{
return default;
}
}15 //b.GetValue().Dump(); {"$type":"subType_Int","TValue":15} // string json = JsonSerializer.Serialize(b).Dump(); 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> 了.
先定义一个特性用来标注序列化/反序列化过程中类型的定义包含泛型信息
// 自定义泛型类型名特性
public class GenericTypeNameAttribute:Attribute
{
// 生成的属性名称
public string GenericTypePropertyName { get; set; }
// 泛型基础名称
public string BaseName { get; set; }
// 根据泛型基础类型T属性值
public string GetGValue (string genericTypeName) => $"{GeneratePrefix}_{genericTypeName}";
// 生成值前缀
public string GeneratePrefix => $"{BaseName}_G";
}
然后将原来的 Base ,Sub 改为 Base<T>,Sub<T>,由于有了泛型 可以将之前返回值从object 改为 对应的泛型T,
并将 [GenericTypeName] 和 关键的 [JsonConverter] 添加上
[GenericTypeName(GenericTypePropertyName = "$type",BaseName = nameof(Sub<T>))]
[JsonConverter(typeof(SubConverter<int>))]
public class Sub<T> :Base<T>
{
public T Value { get; set; }
public override T? GetValue()
{
return Value;
}
}
[GenericTypeName(GenericTypePropertyName = "$type",BaseName = nameof(Base<T>))]
[KnownType(typeof(Sub<>))]
[JsonConverter(typeof(BaseConverter<int>))]
public class Base<T>
{
public virtual T? GetValue()
{
return default;
}
}
并实现 JsonConverter<Base<T>> 和 JsonConverter<Sub<T>>
BaseConvert<T>
public class BaseConverter<T>:JsonConverter<Base<T>>
{
public override Base<T>? Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
{
var markerAttribute = typeToConvert.GetCustomAttribute<GenericTypeNameAttribute>()!;
string genericTypeName = markerAttribute.GenericTypePropertyName!;
string? typeName = default;
T? tV = default;
while(reader.Read())
{
if(reader.TokenType == JsonTokenType.EndObject)
break;
if(reader.TokenType == JsonTokenType.PropertyName)
{
string propertyName = reader.GetString() ?? throw new ArgumentException("Base<T> PropertyName");
// 如果名称等于标注特性上的属性名称
if(propertyName == genericTypeName)
{
// 提前读取
reader.Read();
typeName = reader.GetString();
continue;
}
}else
{
JsonConverter<T> tConverter = (JsonConverter<T>)options.GetConverter(typeof(T));
tV = tConverter.Read(ref reader,typeof(T),options);
}
}
ArgumentException.ThrowIfNullOrWhiteSpace(typeName);
//这里只演示 ,偷懒,如果有值就为 Sub<T> 如果要更通用的需要根据类型手动构造
if(tV is not null)
{
return new Sub<T>{ Value = tV };
}
return new Base<T>();
}
public override void Write(Utf8JsonWriter writer, Base<T> value, JsonSerializerOptions options)
{
// 获取要写入的的类型
var sourceType = value.GetType()!;
// 获取 泛型 T 类型的名称
string gernericName = sourceType.GenericTypeArguments.First().Name;
// 我们自定义的标注特性
// 可以缓存起来
string genericTypeName = sourceType.GetCustomAttribute<GenericTypeNameAttribute>()!.GenericTypePropertyName!;
string gernericBaseTypeName = sourceType.GetCustomAttribute<GenericTypeNameAttribute>()!.BaseName;
// 如果是派生类型的泛型
if(sourceType.GetGenericTypeDefinition() != typeof(Base<>))
{
var knowTypes = Type.GetCustomAttributes<KnownTypeAttribute>();
// 从 KnownType 中查找注册类型
var targetType = knowTypes?.FirstOrDefault(
x => x.Type?.GetGenericTypeDefinition() == sourceType.GetGenericTypeDefinition());
if(targetType != null && targetType.Type != null)
{
// 构建泛型类型
var mkType = targetType.Type.MakeGenericType(sourceType.GenericTypeArguments[0]);
// 调用对应已注册类型序列化方法
writer.WriteRawValue(JsonSerializer.Serialize(value,mkType));
return;
}
}
// Base<T> 本身没任何属性 写入泛型类型就结束了
writer.WriteStartObject();
writer.WriteString(JsonNamingPolicy.CamelCase.ConvertName(genericTypeName),$"{gernericBaseTypeName}_G_{gernericName}");
writer.WriteEndObject();
}
}
SubConverter<T>
public class SubConverter<T>: JsonConverter<Sub<T>>
{
public override Sub<T>? Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
{
// 找到用于标记的特性
string genericTypeName = typeToConvert.GetCustomAttribute<GenericTypeNameAttribute>()!.GenericTypePropertyName!;
// 并未用到这个typeName 只是用来记录 可以根据具体需求使用
string? typeName = default;
T tV = default;
// 当可以继续读取的时候
while(reader.Read())
{
// 读到json对象末尾了 退出
if(reader.TokenType == JsonTokenType.EndObject)
break;
// 读到属性名称记录下
if(reader.TokenType == JsonTokenType.PropertyName)
{
string propertyName = reader.GetString();
// 如果属性名称是特性标记中的名称
if(propertyName == genericTypeName)
{
// 手动继续读取
reader.Read();
// 获取到名称
typeName = reader.GetString();
// 并跳过当此循环 因为以及预读取过
continue;
}
}else
{
// 当初也在想怎么构建 泛型 T 的类型的实例
// 后面参照官网示例
// 是通过获取 T 对应的 JsonConverter 获取 并调用 Read 方法构建 (妙啊)
// 例如: T为 int 则 JsonConverter<T> 其实就是获取 JsonConverter<int> 而基础类型基本都内置
// 所以不用专门去写
JsonConverter<T> tConverter = (JsonConverter<T>)options.GetConverter(typeof(T));
tV = tConverter.Read(ref reader,typeof(T),options);
}
}
ArgumentException.ThrowIfNullOrWhiteSpace(typeName);
return new Sub<T>(){ Value = tV };
}
public override void Write(Utf8JsonWriter writer, Sub<T> value, JsonSerializerOptions options)
{
var sourceType = value.GetType()!;
string genericName = sourceType.GenericTypeArguments.First().Name;
var markerAttribute = sourceType.GetCustomAttribute<GenericTypeNameAttribute>()!;
string genericTypePropName = markerAttribute.GenericTypePropertyName!;
writer.WriteStartObject();
if(value is Sub<string> st)
{
writer.WriteString("Value",st.Value);
}else if(value is Sub<int> it)
{
writer.WriteNumber("Value",it.Value);
}else if(value is Sub<bool> bt)
{
writer.WriteBoolean("Value",bt.Value);
}
writer.WriteString(JsonNamingPolicy.CamelCase.ConvertName(genericTypePropName),markerAttribute.GetGValue(genericName));
writer.WriteEndObject();
}
}
完成上述步骤之后我们就可以愉快的开始愉快的泛型序列化了......吗?
将我们的调用改为泛型调用
Base<int> i = new Sub<int>{ Value = 15 };
string json = JsonSerializer.Serialize(i).Dump();
Base<int> des = JsonSerializer.Deserialize<Base<int>>(json);
des.Dump();
输出
{"Value":15,"$type":"Sub_G_Int32"} // string json = JsonSerializer.Serialize(i).Dump();
Sub`1[System.Int32] // des.Dump();
Value = 15
貌似没什么问题了...
等等...
泛型,那我改改类型试试
将 上面Base<T>,Sub<T> 上的 JsonConvert<T> 泛型改为 bool 试试
输出
{"Value":true,"$type":"Sub_G_Boolean"} // string json = JsonSerializer.Serialize(i).
Sub`1[System.Boolean] // des.Dump();
Value = True
好像也没问题
Ok, 那把 Base<T>,Sub<T> 上的 JsonConvert<T> 的 T 去掉 不指定类型 让他通用起来
......省略代码
[JsonConverter(typeof(BaseConverter<>))]
public class Base<T>
......省略代码
运行试试
Cannot create an instance of BaseConverter`1[T] because Type.ContainsGenericParameters is true.
啊 ?
竟然异常了,这不是玩我吗 ? 竟然 JsonConvertAttribute 传入的 Type 不支持泛型
从异常信息来看 ,好像是某种约束默认不让泛型参数
because Type.ContainsGenericParameters is true
经过一番查找最后在微软官方指引里发现了 JsonConverterFactory 这个类,用来
给支持泛型的房子加上最后一块砖
借由 JsonConverterFactory 实现支持泛型序列化/反序列化
继承并重写 JsonConverterFactory 的 CanConvert 以及 CreateConverter 方法
// 定义泛型转换器创建工厂
public abstract class GenericTypeConverterFactory : JsonConverterFactory
{
// 泛型类型
public abstract Type GenericType { get; }
// 对应转换器泛型类型
public abstract Type GenericJsonConvertType { get; }
// 什么类型可以转换
public override bool CanConvert(Type typeToConvert)
{
// 这里约束了只有泛型类型可以转换
return typeToConvert.IsGenericType && typeToConvert.GetGenericTypeDefinition() == GenericType;
}
public override JsonConverter? CreateConverter(Type typeToConvert, JsonSerializerOptions options)
{
// 获取泛型类型
Type valueType = typeToConvert.GetGenericArguments()[0];
// 手动构造泛型转换器的类型
Type converterType = GenericJsonConvertType.MakeGenericType(valueType);
// 获取对应的实例
var ist = (JsonConverter?)Activator.CreateInstance(converterType);
return ist;
}
}
public sealed class BaseConverterFactory:GenericTypeConverterFactory
{
public override Type GenericType => typeof(Base<>);
public override Type GenericJsonConvertType => typeof(MyConverter<>);
}
public sealed class SubConverterFactory:GenericTypeConverterFactory
{
public override Type GenericType => typeof(Sub<>);
public override Type GenericJsonConvertType => typeof(SubConverter<>);
}
由于 JsonConverterFactory 是继承 JsonConverter 的 , 所以我们需要将 Base<T> 和 Sub<T> 上的 JsonConvert 替换为刚刚实现的两个工厂
......省略代码
[JsonConverter(typeof(BaseConverterFactory))]
public class Base<T>
......省略代码
运行 bool
{"Value":true,"$type":"Sub_G_Boolean"}
Sub`1[System.Boolean]
Value = True
运行 int
{"Value":12,"$type":"Sub_G_Int32"}
Sub`1[System.Int32]
Value = 12
运行 string
{"Value":"hello world","$type":"Sub_G_String"}
Sub`1[System.String]
Value = hello world
完美 !!!!
结尾
上面就是我探索 json 泛型序列化的过程.
过程还是挺曲折
感觉这个需求挺小众,找了各个网站都没有这方面的解决方案.
不甘心的我对着微软的文档一个个特性研究,生怕错过一个关于这方面的能力.
最后的解决方案已经满足了我的需求
最后,上面的代码都是我想尽快发出博客手敲出来的,难免会有错误和没有达到最优性能的情况,但总体过程还是挺完整的.
C# 中使对象序列化/反序列化 Json 支持使用派生类型以及泛型的方式的更多相关文章
- 【译】Java中的对象序列化
前言 好久没翻译simple java了,睡前来一篇. 译文链接: http://www.programcreek.com/2014/01/java-serialization/ 什么是对象序列化 在 ...
- 从DataTable中删除不被控件支持的字段类型
DataTable dt = DB.GetDataTable(sql); //从dt中删除不被控件支持的字段类型 for (int ...
- C#[Serializable]在C#中的作用-NET 中的对象序列化
为什么要使用序列化?最重要的两个原因是:将对象的状态保存在存储媒体中以便可以在以后重新创建出完全相同的副本:按值将对象从一个应用程序域发送至另一个应用程序域.例如,序列化可用于在 ASP.NET 中保 ...
- C#中的二进制序列化和Json序列化
序列化就是把一个对象变成流的形式,方便传输和还原.小弟不才,总结下对二进制序列化和Json序列化的使用: 1.首先,二进制序列化(BinaryFormatter)要求要序列化的类必须是可序列化的(即在 ...
- Serializable在C#中的作用.net中的对象序列化 (转)
序列化是指将对象实例的状态存储到存储媒体的过程,在此过程中,先将对象的公共字段和私有字段以及类的名称(包括类所在的程序集)转 换为字节流,然后再把字节流写入数据流,在随后对对象进行反序列化时,将创建出 ...
- Serializable在C#中的作用——.net中的对象序列化
序列化是指将对象实例的状态存储到存储媒体的过程,在此过程中,先将对象的公共字段和私有字段以及类的名称(包括类所在的程序集)转换为字节流,然后再把字节流写入数据流,在随后对对象进行反序列化时,将创建出与 ...
- Java对象序列化/反序列化的注意事项(转)
Java对象序列化 对于一个存在Java虚拟机中的对象来说,其内部的状态只是保存在内存中.JVM退出之后,内存资源也就被释放,Java对象的内部状态也就丢失了.而在很多情况下,对象内部状态是需要被持久 ...
- Java对象序列化/反序列化的注意事项
Java对象序列化 对于一个存在Java虚拟机中的对象来说,其内部的状态只是保存在内存中.JVM退出之后,内存资源也就被释放,Java对象的内部状态也就丢失了.而在很多情况下,对象内部状态是需要被持久 ...
- 一个高性能的对象属性复制类,支持不同类型对象间复制,支持Nullable<T>类型属性
由于在实际应用中,需要对大量的对象属性进行复制,原来的方法是通过反射实现,在量大了以后,反射的性能问题就凸显出来了,必须用Emit来实现. 搜了一圈代码,没发现适合的,要么只能在相同类型对象间复制,要 ...
- c# 类实例序列化反序列化json文件 (原发布 csdn 2017-10-01 20:02:12)
前言 前段时间使用了net.json保存对象数据.添加完成后,测试发现300多实例数据保存加载json文件,速度比原方式(BinaryFormatter)慢.但是功能加上后也懒再删掉代码了,索性就采用 ...
随机推荐
- 【2】Pycharm插件推荐,超级实用!每个小trick都可以快速提升变成效率!
相关文章: [1]Pycharm 主题设置推荐Material Theme UI以及编辑环境配置(字体大小和颜色) [2]Pycharm插件推荐,超级实用!每个小trick都可以快速提升变成效率! [ ...
- 【CSDN浏览器插件测评超详细版】——万能便捷的C功能,直接爱不释手,强烈推荐!更多功能等你探索
我的界面预览: 1.下载安装 下载链接:https://plugin.csdn.net/?utm_source=chajian-contest-1210#/chrome-index-help 说明:若 ...
- 19.6 Boost Asio 文本压缩传输
Base64是一种二进制到文本的编码方案,用于将二进制数据转换为ASCII字符串格式.它通过将二进制数据流转换为一系列64个字符来工作,这些字符都可以安全地传输到设计用于处理文本数据的系统中. 如下代 ...
- C/C++ 简单特征码匹配
特征码定位查杀 简单提取一段特征码字段. 代码编写. #include <stdio.h> #include <Windows.h> #define FileLEN 20 // ...
- C++ STL 标准模板库(容器总结)算法
C++ 标准模板库STL,是一个使用模板技术实现的通用程序库,该库由容器container,算法algorithm,迭代器iterator,容器和算法之间通过迭代器进行无缝连接,其中所包含的数据结构都 ...
- HTTP请求头引发的注入问题 (SQL注入)
关于请求头中注入问题的演示,这里我写了一些测试案例,用来测试请求头中存在的问题.我们常见的会发生注入的点有 Referer.X-Forwarded-For.Cookie.X-Real-IP.Accep ...
- Pdfium.Net.Free 一个免费的Pdfium的 .net包装器--加载字体
项目地址: Pdfium.Net:https://github.com/1000374/Pdfium.Net PdfiumViewer:https://github.com/1000374/Pdfiu ...
- 字节码编程,Javassist篇四《通过字节码插桩监控方法采集运行时入参出参和异常信息》
作者:小傅哥 博客:https://bugstack.cn 沉淀.分享.成长,让自己和他人都能有所收获! 一.前言 字节码编程插桩这种技术常与 Javaagent 技术结合用在系统的非入侵监控中,这样 ...
- Docker从认识到实践再到底层原理(六-1)|Docker容器基本介绍+命令详解
前言 那么这里博主先安利一些干货满满的专栏了! 首先是博主的高质量博客的汇总,这个专栏里面的博客,都是博主最最用心写的一部分,干货满满,希望对大家有帮助. 高质量博客汇总 然后就是博主最近最花时间的一 ...
- 如何快速获取AWR中涉及到的表
最近遇到一个很少见的需求,是关于应用测试方面的. 具体来说,这个应用的测试需求要基于一个固定的时间点数据,而且只能测试一轮,再测试就需要还原到测试前状态. 因为我们使用的存储是分层的(热数据在Flas ...