Unity技术沉淀--读取json数据(反序列化)

上一节讲到了如何借助第三方工具从excel数据表中导出json数据。Execl表格导出json、lua

Json的序列化和反序列化相关的第三方工具非常多,litjson,fastjson等等。

JsonUtility

1.特点

JsonUtility 是unity 官方5.3以后推出的工具,其主要的特点有

  • 效率高
  • 不依赖第三方库

2.局限性

  • 无法直接序列化和反序列化List<T>和Dictionary<TKey,TValue>
  • 3.0自动属性也无法序列和反序列化

实际上List<T>是支持的,只是无法直接的对List<T>对象进行序列化和反序列化,需要将List<T>包装(Wrap)到一个类当中,然后序列化和反序列化包含这个List<T>的对象即可


Dictionary是确实不支持的,这在官方的文档中也有说明
https://docs.unity3d.com/Manual/JSONSerialization.html

对于JsonUtility无法序列化和反序列化List<T>和Dictionary<TKey,TValue>的情况,大家如果去搜索相关的资料,应该都会找到下面链接中的解决方案:

http://kou-yeung.hatenablog.com/entry/2015/12/31/014611

List<T>,无法直接序列化,将它包装在一个类中就可以了

比如,下面这样是无法序列化的:(我直接使用参考文档中的代码)

[Serializable] 
public class Enemy 
{ 
    public string name; 
    public List<string> skills; 
    public Enemy(string name, List<string> skills) 
    { 
        this.name = name; 
        this.skills = skills; 
    } 
}

//序列化
var enemies = new List<Enemy>(); 
enemies.Add(new Enemy("Json", new List<string>() { "Attack" })); 
enemies.Add(new Enemy("Kate", new List<string>() { "Attack", "Defence" })); 
Debug.Log(JsonUtility.ToJson(enemies));

输出的内容为空,无法序列化enemies到Json
解决方案:
我们只需要将var enemies = new List<Enemy>();包装到一个类中即可

public class EnemyWrap 
{ 
    public List<Enemy> enemies = new List<Enemy>(); 
    public EnemyWrap() 
    {  
        enemies.Add(new Enemy("Json", new List<string>() { "Attack" })); 
        enemies.Add(new Enemy("Kate", new List<string>() { "Attack", "Defence" })); 
    } 
} 

放在EnemyWrap类中,再进行序列化操作:

EnemyWrap enemy = new EnemyWrap(); 
Debug.Log(JsonUtility.ToJson(enemy));

输出结果

{"enemies":[{"name":"Json","skills":["Attack"]},{"name":"Kate","skills":["Attack","Defence"]}]}

下面是它封装好的泛型类List<T>:

// List<T> 
[Serializable] 
public class Serialization<T> 
{ 
    [SerializeField] 
    List<T> target; 
    public List<T> ToList() { return target; } 
    public Serialization(List<T> target) 
    { 
        this.target = target; 
    } 
} 

和上面EnemyWrap类是相同的处理方式,只是字段变成了target
JsonUtility不支持Dictionary<TKey,TValue>的序列化和反序列化,上面的网址中提供的方式其实是将Dictionary<TKey,TValue>分成了两个List<T>实现

// Dictionary<TKey, TValue> 
[Serializable] 
public class Serialization<TKey, TValue> : ISerializationCallbackReceiver 
{ 
    [SerializeField] 
    List<TKey> keys; 
    [SerializeField] 
    List<TValue> values; 
    Dictionary<TKey, TValue> target; 
    public Dictionary<TKey, TValue> ToDictionary() { return target; } 
    public Serialization(Dictionary<TKey, TValue> target) 
    { 
        this.target = target; 
    } 
    public void OnBeforeSerialize() 
    { 
        keys = new List<TKey>(target.Keys); 
        values = new List<TValue>(target.Values); 
    } 
    public void OnAfterDeserialize() 
    { 
        var count = Math.Min(keys.Count, values.Count); 
        target = new Dictionary<TKey, TValue>(count); 
        for (var i = 0; i < count; ++i) 
        { 
            target.Add(keys[i], values[i]); 
        } 
    } 
} 

List<TKey> keys;
List<TValue> values;
拆成了两个List,分别存放key和value
这里实现了ISerializationCallbackReceiver,由Unity提供,只有两个接口:

void OnAfterDeserialize(); 
void OnBeforeSerialize(); 

顾名思义,OnAfterDeserialize在序列化完成之后调用
OnBeforeSerialize在序列化开始之前调用
在OnBeforeSerialize序列化开始之前,将传入进来的Dictionary参数的keys,values存放到
两个List<T>当中
在OnAfterDeserialize序列化完成后,将反序列化回来的两个List,通过for循环的方式还原成
Dictionary<TKey,TValue>
这样序列出来的结果如下:

{"keys":[1000,2000],"values":[{"name":"怪物1","skills":["攻击"]},{"name":"怪物2","skills":["攻击","恢复"]}]}  

可以看到,里面存放了两个数组keys[…],values[…]
如果你要在实际的项目中,使用上面的Dictionary<Tkey,TValue>,要记得包装(Wrap)一个类,派生自Serialization<TKey, TValue>,这样才会有效

[Serializable] 
public class SampleDictionary : Serialization<int, int> 
{ 
    public SampleDictionary(Dictionary<int, int> targe) : base(targe) { } 
}

对于作者提供的Dictionary<TKey,TValue>方案,需要我们在过程中注意Dictionary的实现是由两个List<T>组成的。
通常来讲,我们使用Dictonary<TKey,TValue>主要是哈希的查找效率,如果不是对大量数据进行频繁的查找,那么可以使用数组来替代Dictionary<TKey,TValue>,或是你的数据结构比较简单,
可以以字符串的形式,通过特定的标志,在OnAfterDeserialize接口, 将字符串分割再转换成Dictionary<Tkey,TValue>等等
目前使用JsonUtility来处理Dictionary<Tkey,TValue>是要二次转换的,总之要在“规则”之下使 ,具体项目中也要根据需求来决定是否采用。
对于不参与序列化或反序化的字段,可以添加特性[NonSerialized]

全序列化FullSerializer

下载链接Github–FullSerializer插件下载链接

FullSerializer 是一个易于使用且健壮的 JSON 序列化器。它几乎可以序列化任何你可以扔给它的东西,并在每个主要的 Unity 平台上工作,包括控制台。
最重要的是,Full Serializer 完全免费使用

用法

用法与JsonUtility序列化相同,不必将类型标记为[Serializable].

struct SerializedStruct {
    public int Field;
    public Dictionary<string, string> DictionaryAutoProperty { get; set; }
    [SerializeField]
    private int PrivateField;
}

序列化默认的规则

  • 公共变量默认序列化
  • 至少部分公开的自动属性默认被序列化
  • 所有用[SerializeField]或被[fsProperty]序列化的字段或属性
  • 如果用[NonSerialized]或注释,公共字段/公共自动属性不会被序列化[fsIgnore]。[fsIgnore]可用于属性(与 不同[NonSerialized])。

FullSerializer 会自动处理继承和循环对象图。你不需要做任何事情。
以下代码是我常用的将对象序列化为字符串或从字符串序列化成对象。

using System;
using FullSerializer;


public static class FullSerializerAPI
{
    private static readonly fsSerializer _serializer = new fsSerializer();

    public static string Serialize(Type type, object value, bool isPretty = false,bool isEncryption = true)
    {
        // serialize the data
        fsData data;
        _serializer.TrySerialize(type, value, out data).AssertSuccessWithoutWarnings();

        // emit the data via JSON
        if (isPretty)
        {
            string jsonStr = fsJsonPrinter.PrettyJson(data);
            if(isEncryption)
                jsonStr = StringEncryption.EncryptDES(jsonStr);   //加密
            return jsonStr;
        }
        else
        {
            string jsonStr = fsJsonPrinter.CompressedJson(data);
            if (isEncryption)
                jsonStr = StringEncryption.EncryptDES(jsonStr);   //加密
            return jsonStr;
            //return fsJsonPrinter.CompressedJson(data);
        }
    }

    public static object Deserialize(Type type, string serializedState)
    {
        serializedState = StringEncryption.DecryptDES(serializedState); //////解密
        // step 1: parse the JSON data
        fsData data = fsJsonParser.Parse(serializedState);

        // step 2: deserialize the data
        object deserialized = null;
        _serializer.TryDeserialize(data, type, ref deserialized).AssertSuccessWithoutWarnings();

        return deserialized;
    }

    public static T Deserialize<T>(string jsonStr) where T : new()
    {
        fsData data = fsJsonParser.Parse(jsonStr);
        T deserialized = new T();
        _serializer.TryDeserialize(data, ref deserialized);
        return deserialized;
    }
}

以下举例说明(文本可以是.txt文本格式,也可以使.json格式或者是你自己定义的某种格式)
JSON CODE

[
  {
    "Id": 1.0,
    "type": 1.0,
    "name": "红瓶",
    "desc": "加血药瓶",
    "prefabName": "prop_01"
  }
  {
    "Id": 2.0,
    "type": 1.0,
    "name": "蓝瓶",
    "desc": "加蓝药瓶",
    "prefabName": "prop_02"
  }
]

我们如果把上面json通过FullSerializer工具反序列化成Dictionary对象呢?
首先我们需要定义一个类作为反序列化对象

[System.Serializable]
    public class Prop 
    {
        public readonly int Id;
        public readonly int type;
        public readonly string name;
        public readonly string desc;
        public readonly string prefabName;
    }

然后通过代码读取json文本数据,这里用Resources.Load来读取,其他读取方式大家可以自行百度。

TextAsset textAsset = Resources.Load<TextAsset>(Files.prop);
string jsonStr = textAsset.text;
List<Prop> props = FullSerializerAPI.Deserialize(typeof(List<Prop>), jsonStr) as List<Prop>;
Dictionary propDict = props.ToDictionary(key => key.Id,value =>value);

如果说定义中的某些变量没有出现在json code中我们应该怎么处理呢?这里推荐下FullSerializer转换器的高级定制:

FullSerializer转换器支持对对象序列化的完全自定义。每个转换器都表示对它想要序列化的类型感兴趣;有两种方法可以做到这一点。FullSerializer中存在更强大(但速度较慢)的方法fsConverter,它通过函数回调确定它是否感兴趣,并且fsDirectConverter,它直接指定它将接管转换的对象类型。fsDirectConverter的主要限制fsDirectConverter是它不处理继承。

这里列举其中一种转化器实例,其他方案大家可以自行百度。
我们来看看这个模型:

public class Person {
    public string FirstName;
    public string LastName;
    public int Age;
}

我们想要序列化它,但我们希望序列化的数据看起来像这样:

{
    "Name": "John Doe",
    "Age": 25
}

转化为这个模型实例:

var person = new Person {
    FirstName = "John",
    LastName = "Doe",
    Age = 25
};

本质上,当我们处理 的实例时Person,我们希望将其序列化为单个字符串字段,即连接名字和姓氏。

这是转换器:

using System;
using System.Collections.Generic;
using FullSerializer;

public class PersonConverter : fsDirectConverter<Person> {
    public override object CreateInstance(fsData data, Type storageType) {
        return new Person();
    }

    protected override fsResult DoSerialize(Person model, Dictionary<string, fsData> serialized) {
        // Serialize name manually
        serialized["Name"] = new fsData(model.FirstName + " " + model.LastName);

        // Serialize age using helper methods
        SerializeMember(serialized, null, "Age", model.Age);

        return fsResult.Success;
    }

    protected override fsResult DoDeserialize(Dictionary<string, fsData> data, ref Person model) {
        var result = fsResult.Success;

        // Deserialize name mainly manually (helper methods CheckKey and CheckType)
        fsData nameData;
        if ((result += CheckKey(data, "Name", out nameData)).Failed) return result;
        if ((result += CheckType(nameData, fsDataType.String)).Failed) return result;
        var names = nameData.AsString.Split(new[] { ' ' }, StringSplitOptions.RemoveEmptyEntries);
        if (names.Length != 2) return result += fsResult.Fail("Too many names");
        model.FirstName = names[0];
        model.LastName = names[1];

        // Deserialize age using basically only helper methods
        if ((result += DeserializeMember(data, null, "Age", out model.Age)).Failed) return result;

        return result;
    }
}

FullSerializer转换器的限制

Full Serializer 有最小的限制

  • WebPlayer 构建目标要求所有反序列化类型都具有默认构造函数
  • 没有多维数组支持(但是可以使用自定义转换器添加)
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 193,968评论 5 459
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 81,682评论 2 371
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 141,254评论 0 319
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,074评论 1 263
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 60,964评论 4 355
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,055评论 1 272
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,484评论 3 381
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,170评论 0 253
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,433评论 1 290
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,512评论 2 308
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,296评论 1 325
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,184评论 3 312
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,545评论 3 298
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 28,880评论 0 17
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,150评论 1 250
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,437评论 2 341
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,630评论 2 335

推荐阅读更多精彩内容