JSON.NET使用简单说明
Posted
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了JSON.NET使用简单说明相关的知识,希望对你有一定的参考价值。
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 name="serializedType">序列化的具体类型</param>
/// <param name="assemblyName">写入json的程序集名</param>
/// <param name="typeName">写入json的类型名</param>
public void BindToName(Type serializedType, out string assemblyName, out string typeName)
{
assemblyName = "程序集";
typeName = serializedType.FullName;
}
/// <summary>
///
/// </summary>
/// <param name="assemblyName">从json读入的程序集名</param>
/// <param name="typeName">从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()
});
如果TypeNameSerializationBinder的BindToName函数中对输出参数assemblyName指定了“程序集”,输出的json就是第二段的样子,而在BindToType时的assemblyName的参数值就会时“程序集”字样,依靠这些信息我们就能使用反射来正确的生成子类对象
BSON
看文档Json.net还支持直接反序列化二进制的json文件,缩小文件体积,加快速度,具体使用下回再补了
改进
使用下来后,我觉得在json反序列化时可以增加根据实例对象进行反序列化,先new出具体对象,再反序列化,这样反序列化时就可以明确有哪些成员。
以上是关于JSON.NET使用简单说明的主要内容,如果未能解决你的问题,请参考以下文章
带有JSONPath的JSON.NET SelectToken
markdown 打字稿...编码说明,提示,作弊,指南,代码片段和教程文章