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出具體對象,再反序列化,這樣反序列化時就可以明確有哪些成員。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM