上期(Unity中Newtonsoft.Json的使用(二))讲完了对Newtonsoft.Json序列化反序列化的一些常用设置,这期将教大家解决一个很多同学在使用Json时常会遇到的一个问题:派生类数据丢失。
1.Json中派生类字段丢失问题
我们先来测试一下吧
先定义一下带继承关系的数据结构,分别是生物Creature基类,包含通用的属性id和name;猪Pig子类,养猪我们比较关心长多重了可以吃了没;竹子Bamboo子类,竹子我们关心它的高度,是否可以做家具了。
/// <summary>
/// 生物
/// </summary>
public class Creature
{
public int id; //id编号
public string name; //生物名字
}
/// <summary>
/// 猪
/// </summary>
public class Pig : Creature
{
public float weight; //重量
}
/// <summary>
/// 竹子
/// </summary>
public class Bamboo : Creature
{
public float height; //高度
}
写一个测试类,看一下序列化结果
private void Start()
{
List<Creature> creatureDatas = new List<Creature>();
Creature pig = new Pig()
{
id = 10086,
name = "小猪佩奇",
weight = 300,
};
Creature bamboo = new Bamboo()
{
id = 10010,
name = "节节高",
height = 10,
};
creatureDatas.Add(pig);
creatureDatas.Add(bamboo);
string json = JsonMgr.Serialize(creatureDatas);
Debug.Log(json);
}
可以看到序列化没什么问题,包含了所有数据
好,继续加上反序列的代码
private void Start()
{
List<Creature> creatureDatas = new List<Creature>();
Creature pig = new Pig()
{
id = 10086,
name = "小猪佩奇",
weight = 300,
};
Creature bamboo = new Bamboo()
{
id = 10010,
name = "节节高",
height = 10,
};
creatureDatas.Add(pig);
creatureDatas.Add(bamboo);
string json = JsonMgr.Serialize(creatureDatas);
Debug.Log(json);
List<Creature> newCreateDatas = JsonMgr.DeSerialize<List<Creature>>(json);
Debug.Log(JsonMgr.Serialize(newCreateDatas));
}
再看一些下序列化的结果,发现什么问题没有
我们发现子类Pig的weight和子类Bamboo的height这两个专属字段的数据都丢失了,为什么会发生这样的事情呢?因为我们在反序列化时指定的类型是基类Creature的,反序列化时只会去处理Creature中有的字段。
如果我们想处理单个数据,只需要指定成具体的子类类型就行了,比如反序列化Pig类型时指定成Pig类型而不是Creature就可以反序列化出Pig中的weight字段了。
但是我们通常来说并不会单独地进行子类的使用,因为要符合依赖倒置原则和里氏替换原则,且不同子类可能会用同一个容器进行缓存,或者包含嵌套关系使用,这时候就没办法指定所有的子类类型了。通常来说声明时使用的更多还是基类的类型。
接下来就教大家对Newtonsoft.Json进行扩展来解决这样的问题。
2.扩展Newtonsoft.Json来反序列化
首先编写一个反序列化的基类DataCreationConverter,继承自JsonCnverter,该库来源于这位大佬的文章
using System;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
/// <summary>
/// Newtonsoft.json数据反序列化转换器
/// </summary>
/// <typeparam name="T"></typeparam>
public abstract class DataCreationConverter<T> : JsonConverter
{
/// <summary>
/// Create an instance of objectType, based properties in the JSON object
/// </summary>
/// <param name="objectType">type of object expected</param>
/// <param name="jObject">
/// contents of JSON object that will be deserialized
/// </param>
/// <returns></returns>
protected abstract T Create(Type objectType, JObject jObject);
public override bool CanConvert(Type objectType)
{
return typeof(T).IsAssignableFrom(objectType);
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
// Load JObject from stream
JObject jObject = JObject.Load(reader);
// Create target object based on JObject
T target = Create(objectType, jObject);
// Populate the object properties
serializer.Populate(jObject.CreateReader(), target);
return target;
}
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
throw new NotImplementedException();
}
protected bool FieldExists(JObject nJObject, string nPropertyName)
{
return nJObject[nPropertyName] != null;
}
protected bool FieldExists(string nFieldName, JObject nJObject, out string nEntityVel)
{
nEntityVel = nJObject[nFieldName] == null ? "" : nJObject[nFieldName].ToString();
return nJObject[nFieldName] != null;
}
}
这个类的原理就是在反序列化时识别子类的专属字段来判断是哪个子类,比如识别到Pig专属的weight字段就说明是Pig类型
然后我们针对Creature类写一个继承自DataCreationConverter的反序列化转换器
public class CreatureDataConverter : DataCreationConverter<Creature>
{
protected override Creature Create(Type objectType, JObject jObject)
{
//判断属性值来确认是哪个子类
if (FieldExists(jObject, "weight"))
{
return new Pig();
}
else if (FieldExists(jObject, "height"))
{
return new Bamboo();
}
else
{
return new Creature();
}
}
}
反序列化时传入转换器
public static T DeSerialize <T>(string json, JsonConverter[] jsonConverters)
{
return JsonConvert.DeserializeObject<T>(json, jsonConverters);
}
private void Start()
{
List<Creature> creatureDatas = new List<Creature>();
Creature pig = new Pig()
{
id = 10086,
name = "小猪佩奇",
weight = 300,
};
Creature bamboo = new Bamboo()
{
id = 10010,
name = "节节高",
height = 10,
};
creatureDatas.Add(pig);
creatureDatas.Add(bamboo);
string json = JsonMgr.Serialize(creatureDatas);
Debug.Log(json);
JsonConverter[] jsonConverters = new JsonConverter[1] {
new CreatureDataConverter(),
};
List<Creature> newCreateDatas = JsonMgr.DeSerialize<List<Creature>>(json, jsonConverters);
Debug.Log(JsonMgr.Serialize(newCreateDatas));
}
可以看到现在子类数据完完整整地反序列化出来了
简单小结
本期教大家解决了Newtonsoft.Json中使用基类进行反序列化造成派生类专属数据丢失问题,到此,Newtonsoft.Json中所有常见的用法都已经教给了大家。
如果大家在使用Newtonsoft.Json时遇到了什么问题,也欢迎在评论区进行讨论~