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出具体对象,再反序列化,这样反序列化时就可以明确有哪些成员。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值