前言
在之前的一篇文章中:
【C#】复杂Json的反序列 + 任意Json获取_code bean的博客-CSDN博客其中result这个key对应的内容是可能发生变化的,所以这里可以用到泛型。如何将一个复杂类型的JSON进行反序列化。那就是如何把json拆解成一个个子类的过程。https://blog.csdn.net/songhuangong123/article/details/126842695?ops_request_misc=%257B%2522request%255Fid%2522%253A%2522168411610816782425112498%2522%252C%2522scm%2522%253A%252220140713.130102334.pc%255Fblog.%2522%257D&request_id=168411610816782425112498&biz_id=0&utm_medium=distribute.pc_search_result.none-task-blog-2~blog~first_rank_ecpm_v1~rank_v31_ecpm-1-126842695-null-null.article_score_rank_blog&utm_term=json&spm=1018.2226.3001.4450#:~:text=%E3%80%90C%23%E3%80%91%E5%A4%8D%E6%9D%82Json%E7%9A%84%E5%8F%8D%E5%BA%8F%E5%88%97%20%2B%20%E4%BB%BB%E6%84%8FJson%E8%8E%B7%E5%8F%96也讲到的Json序列化,使用的了微软自带的库:
这次我不得不再次用回 :
原因是, 在 .NET 7 之前的版本中,System.Text.Json
不支持多态类型层次结构的序列化。这就太坑了,我还在使用.net6啊!!! 算了还是用回老牌库吧,这个和 .NET版本不存在冲突。
背景
那问题来了,为啥我需要支持多态类型层次结构的序列化呢?原因是这样的,目前写了一很多工具类,本着遵循 do not copy your self 的原则,我使用了继承。一堆工具集成一个工具类。
所以在序列化的时候,我的数据结构中使用的类型是基类,而指向的对象是子类。而在使用System.Text.Json的时候发现,序列化后的内容是父类的类容,而子类的部分并没有序列化。这相当于数据丢失了! 所以我需要“
支持多态类型层次结构的序列化”。当更换为Newtonsoft.Json之前,序列化时子类的部分就得以保存成功。
自定义反序列化
接着,另一个问题来了,反序列化出问题了,因为我们保存的是子类对象,反序列化时,数据结构中任然是父类,这次程序无法得知 反序列化的这个子类到底是哪个子类,所以反序列化失败。为此,我们必须实现自定义反序列化,指定反序列化时子类类型。
继承JsonConverter
public class ToolJsonConverter : JsonConverter
{
//控制哪些类型要序列化,哪些不需要!
public override bool CanConvert(Type objectType)
{
return true;
}
public override object? ReadJson(JsonReader reader, Type objectType, object? existingValue, JsonSerializer serializer)
{
}
/// <summary>
/// 如果为false,将不会触发下面这个WriteJson!会走默认的反序列化
/// </summary>
public override bool CanWrite => false;
public override void WriteJson(JsonWriter writer, object? value, JsonSerializer serializer)
{
throw new NotImplementedException();
}
}
我们自定义一个类继承JsonConverter,继承这个类需要实现三个方法
1 CanConvert 针对序列化,控制哪些类型要序列化,哪些不需要! 我这里直接返回true表示全部都序列化,但是我们可以通过使用[JsonIgnore]特性,修饰不需要序列化的类。(ps:由于两个库里都有这个[JsonIgnore]特性,注意不要使用到System.Text.Json中的[JsonIgnore])
2 ReadJson 反序列化时,会调用该接口。
3 WriteJson 序列化时,调用!
但是,目前默认的序列化已经满足了我的要求,所以我不希望调用这个函数,默认的就好,那么做呢?答案是。重写属性CanWrite,让其变为false.
/// <summary>
/// 如果为false,将不会触发下面这个WriteJson!会走默认的反序列化
/// </summary>
public override bool CanWrite => false;
那关键就是第二步ReadJson
如何编写,这里先暂停一下。先暂时认为这个自定义的类已经完成了。这个类如何使用呢?
知识点补充
JsonSerializerSettings,这个可以控制序列化时的一些,细节,比如解决序列化时中文乱码的问题,比如,序列化时我需要格式化文本:
我们可以这么做:
static public JsonSerializerSettings options; static JsonConfigCtrl() { options = new JsonSerializerSettings(); options.Formatting = Formatting.Indented; //格式化 options.Culture = new System.Globalization.CultureInfo("zh-CN"); //解决中文乱码 }
我们还可以添加自定义序列化,也就是我们定义的继承
JsonConverter的类(toolJsonConverter)。
options.Converters.Add(toolJsonConverter);
这里是添加,而不是赋值,说明可以添加多个。这样就能针对不同类,进行不用方案的反序列化。
第一步
通过JsonSerializerSettings添加自定义的类对象 。
添加自定义序列化,也就是我们定义的继承JsonConverter的类(toolJsonConverter)。
options.Converters.Add(toolJsonConverter);
让后在反序列化的时候,传入options参数:
string str = JsonConvert.SerializeObject(obj, options);
第二步
我们需要找到工具的基类,然后修饰它:[JsonConverter(typeof(ToolJsonConverter))]
如图:
这样,我们在反序列的时候,如果遇到了 BaseToolObj类就会调用ToolJsonConverter中重写的ReadJson的函数:
public class ToolJsonConverter : JsonConverter
{
//控制哪些类型要序列化,哪些不需要!
public override bool CanConvert(Type objectType)
{
//throw new NotImplementedException();
return true;
}
public override object? ReadJson(JsonReader reader, Type objectType, object? existingValue, JsonSerializer serializer)
{
try
{
var jsonObject = JObject.Load(reader);
//string full_dll_path = jsonObject.GetValue("mModInfo.FullDllPath").ToString();
JObject mModInfo = (JObject)jsonObject.GetValue("mModInfo");
string full_dll_path = mModInfo.GetValue("FullDllPath").ToString();
Assembly assembly = Assembly.LoadFrom(full_dll_path.ToString());
var ToolClassName = mModInfo.GetValue("ToolClassName")?.ToString();
//获取对象类名称
var full_name = assembly.DefinedTypes.First(e => e.Name == ToolClassName).FullName;
//反射获得对象
var bto = assembly.CreateInstance(full_name);
//将json的值赋予对象
serializer.Populate(jsonObject.CreateReader(), bto);
return bto;
}
catch (Exception ex)
{
MessageBox.Show("自定义序列化(ToolJsonConverter)失败,配置文件可能损坏!" + ex.Message);
return null;
}
}
/// <summary>
/// 如果为false,将不会触发下面这个WriteJson!会走默认的反序列化
/// </summary>
public override bool CanWrite => false;
public override void WriteJson(JsonWriter writer, object? value, JsonSerializer serializer)
{
throw new NotImplementedException();
}
在 ReadJson 中,我首先利用反射得到子类对象,然后通过:
serializer.Populate(jsonObject.CreateReader(), bto); 将json的值一一对应赋值给对象! 然后将这个对象返回,这样基类就会指向这个子类对象了。
注意,我的数据结构中,有一个列表里面有多个BaseToolObj,所以ReadJson这个函数是会被多次调用的,var jsonObject = JObject.Load(reader); 得到的jsonObject也仅仅是BaseToolObj对应的部分,和其他的数据结构无关。这也是继承了JsonConverter的魅力所在。让我们只用关心被特性[JsonConverter(typeof(ToolJsonConverter))] 修饰的类。
小结
1 序列化过程,比较简单基本默认就好,不过需要,通过JsonSerializerSettings 做一些全局设置,比如添加自定义的序列化等。
2 反序列化时,需要得知子类的类型,然后返回一个子类类型,这样就等将父类指向之类类型了。