[摘要]本文是对JSON.NET使用简单说明的讲解,对学习C#编程技术有所帮助,与大家分享。
NewtonJson算是比较常用的json解析库了,我的项目中基本都使用该库,因为Unity上也有其相关的库,所以保证了多种项目之间的统一。同时NewtonJson的序列化和反序列化的接口比较简单,相对的功能也比较强大。不过在使用中也不是没有坑的,所以把一些心得记录下,以备日后查询。
序列化和反序列化
序列化和反序列化很简单,调用相关的接口即可。反序列化的时候可以指定泛型参数,直接解析成对应的对象,这个功能比很多轻量级的JSON库要强很多了,省去了我们大量的new对象和赋值的步骤。也可以不指定泛型,解析成的对象可以转换成Dictionary,键值是字符串,value是object。
var data = new JsonData() {IntValue = 1000, FValue = 3.14f, IsTrue = true, Text = "Hello World"}; //序列化 json = "{\"IntValue\":1000,\"Text\":\"Hello World\",\"IsTrue\":true,\"FValue\":3.14}" var json = JsonConvert.SerializeObject(data); //反序列化 data = JsonConvert.DeserializeObject<JsonData>(json); //不指定泛型的反序列化 var dict = JsonConvert.DeserializeObject<Dictionary<string, object>>(json);
反序列化的时候,json键值对中的value,如果是整型,统一被转换成long,然后再进行转换,浮点型统一转换成Double,然后转换,string,bool就直接转换了。数组,list可以转换成JArray类型,然后再转换成我们需要的集合类型。
var dict = JsonConvert.DeserializeObject<Dictionary<string, object>>(json); var new_data = new JsonData(); new_data.IntValue = (int)((long)dict["IntValue"]); new_data.FValue = (float)((double)dict["FValue"]); new_data.Text = (string)dict["Text"]; new_data.IsTrue = (bool)dict["IsTrue"]; new_data.array = ((JArray)dict["array"]).Values<int>().ToArray();
反序列化的时候,JSON.NET是将键值名和对象的成员名对应起来进行赋值的,它只认名称,不管顺序,甚至不管类,比如由以下两个类
class JsonData { public int IntValue; public string Text; public bool IsTrue; public float FValue; public int[] array; public List<int> list; } class JsonDataB { public bool IsTrue; public int IntValue; public float FValue; public int[] array; public List<int> list; public string Text; } var datab = JsonConvert.DeserializeObject<JsonDataB>(json);
同一段JSON可以解析成A也可以解析成B,这在一些项目中存在大量类似的类,或是相同类,处于不同的程序集中非常好用,节省大量的写反序列化的代码。假设Bname3,依然可以解析,并不会因为多了一个成员而无法解析,只是name3没有值而已。
指定反序列化的对象
如果我们想指定JSON字符串反序列化的对象,除了使用泛型参数,还可以使用自定义的Converter,继承JsonConverter后,在接受json字符串时,我们可以选择解析成A,或是B。换句话说,对一段JSON字符串指定一个new函数,但new什么,哪怕完全不相关的东西都可以,而json字符串只是new时的参数。
class DataConvert : JsonConverter { public override bool CanConvert(Type objectType) { return true; } public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer) { var data = new JsonDataB(); while(reader.Read()) { if(reader.Value.ToString() == "IntValue") { data.IntValue = reader.ReadAsInt32().Value; } } return data; } public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer) { throw new NotImplementedException(); } }
在使用泛型解析的时候,也可以对具体类的构造函数使用[JsonConstructor]特性来指定被反序列化使用的构造函数。在这里Json.Net中它会依次查找合适的构造函数,如果不指定该特性,就会先从默认的构造函数找起。
class JsonData { public int IntValue; public string Text; public bool IsTrue; public float FValue; public int[] array; public List<int> list; public JsonData() { } [JsonConstructor] public JsonData(int intvalue, string text, bool istrue) { IntValue = intvalue * 2; //改变 Text = "We can do every thing we want here"; FValue = 0.001f; //不改变 //istrue丢失 } } data = JsonConvert.DeserializeObject<JsonData>(json);
上面两个构造函数中,JSON.NET会在反序列化时执行第二个带参数的,而且两个参数值分别对应Json字符串中的值,如果形参的名字对应JSON字符串中的key值,而类型不对应,还会抛出异常。形参中没有包括的json的key-value字符串的值,会在构造后再次赋值。比如FValue就不会等于0.001,而是3.14
反序列化时,json.net是先将对象的字符串抽取出来,然后new出对象,并将这部分的json字符串传递给构造函数进行赋值,如果是默认构造函数,则会在new出后,进行赋值。这里一个问题是,如果使用了[JsonConstructor]指定了构造函数,而该函数是接受参数的,那么再new之后就不会再次赋值了,如果构造函数内没有对这个参数进行赋值,那这个值就丢失了。这个在我们使用时,就因为这个原因,造成总是丢失数据
多态反序列化
反序列化多态对象时,因为可能具体的类的成员比泛型参数来的多,想要正确反序列化的话,需要在序列化时,在JSON字符串中增加有效的类型信息。要继承SerializeBinder,该类的两个接口可以将类型转换成字符串,添加到json字符串中,反序列化时,通过拿到类型的字符串,使用反射来new出具体对象。
public class TypeNameSerializationBinder : ISerializationBinder { public TypeNameSerializationBinder() { } /// <summary> /// /// </summary> /// <param>序列化的具体类型</param> /// <param>写入json的程序集名</param> /// <param>写入json的类型名</param> public void BindToName(Type serializedType, out string assemblyName, out string typeName) { assemblyName = "程序集"; typeName = serializedType.FullName; } /// <summary> /// /// </summary> /// <param>从json读入的程序集名</param> /// <param>从json读入的类型名</param> /// <returns></returns> public Type BindToType(string assemblyName, string typeName) { var asm = AppDomain.CurrentDomain.GetAssemblies(); foreach (var assembly in asm) { var types = assembly.GetTypes(); foreach (var type in types) { if (type.FullName == typeName) { return type; } } } return null; } } class clsBase { public int num; } class subcls: clsBase { public string txt; } class cls { public clsBase m_base; } var c = new cls(); var b = new subcls() {num = 1001, txt = "I'm sub"}; c.m_base = b; json = JsonConvert.SerializeObject(c); var _c = JsonConvert.DeserializeObject<cls>(json); //以下代码正确序列化 //json = "{\"m_base\":{\"$type\":\"JsonDotNetDemo.Program+subcls\",\"txt\":\"I'm sub\",\"num\":1001}}" //json = "{\"m_base\":{\"$type\":\"JsonDotNetDemo.Program+subcls, 程序集\",\"txt\":\"I'm sub\",\"num\":1001}}" json = JsonConvert.SerializeObject(c, new JsonSerializerSettings() { TypeNameHandling = TypeNameHandling.Auto, SerializationBinder = new TypeNameSerializationBinder() }); _c = JsonConvert.DeserializeObject<cls>(json, new JsonSerializerSettings() { TypeNameHandling = TypeNameHandling.Auto, SerializationBinder = new TypeNameSerializationBinder() });