• Json的数据映射(基于LitJson)


      最近用到 Echarts 之类的图表显示插件, 它基于不同的 Json 结构可以得到不同的图表, 我们从 http 请求来的数据一般就是 Json 的, 肯定就想通过图形式的数据映射来完成图表的显示了, 不过首先C#它不是JS这种对 Json 结构友好的语言, 要对一个Json节点进行更新的时候, 需要把Json转换成数据结构, 然后找到相应点, 再用新的Json进行节点读取, 生成新的结构, 然后再从根节点读取Json的字符串出来, 才能完成数据映射......

      然后就是复杂结构映射, 比如一个Object和Array进行反复嵌套的情况, 要筛选出来某些数据或者结构, 再映射到其它结构中去的话, 会非常困难, 如果考虑用UE的蓝图的话, 有可能可以做到, Unity这个光是自己做可视化界面以及无限展开的映射选项, 就已经要命了......

      先说一个数据映射的过程, 因为使用的是LitJson, 它的扩展性比较强, 数据映射的函数正是用到了 JsonData 的 ReadValue 的方式, 不过我们稍微改动了一下 : 

        /// <summary>
        /// 核心数据映射逻辑, 跟LitJson中的有差别, 简单数据通过强制转换得到, 并保留原有数据类型不变
        /// </summary>
        /// <param name="jsonData"></param>
        /// <param name="reader"></param>
        /// <param name="json"></param>
        /// <returns></returns>
        private static IJsonWrapper ReadValue(JsonData jsonData, JsonReader reader, string json = null)
        {
            try
            {
                reader.Read();      // the json maybe error with read while read prime data
    
                if(reader.Token == JsonToken.ArrayEnd ||
                    reader.Token == JsonToken.Null)
                    return null;
    
                IJsonWrapper instance = jsonData;
    
                if(reader.Token == JsonToken.String)
                {
                    instance.SetString((string)reader.Value);
                    return instance;
                }
    
                if(reader.Token == JsonToken.Double)
                {
                    instance.SetDouble((double)reader.Value);
                    return instance;
                }
    
                if(reader.Token == JsonToken.Int)
                {
                    instance.SetInt((int)reader.Value);
                    return instance;
                }
    
                if(reader.Token == JsonToken.Long)
                {
                    instance.SetLong((long)reader.Value);
                    return instance;
                }
    
                if(reader.Token == JsonToken.Boolean)
                {
                    instance.SetBoolean((bool)reader.Value);
                    return instance;
                }
    
                if(reader.Token == JsonToken.ArrayStart)
                {
                    instance.SetJsonType(JsonType.Array);
    
                    while(true)
                    {
                        IJsonWrapper item = ReadValue(new JsonData(), reader);
                        if(item == null && reader.Token == JsonToken.ArrayEnd)
                            break;
    
                        ((IList)instance).Add(item);
                    }
                }
                else if(reader.Token == JsonToken.ObjectStart)
                {
                    instance.SetJsonType(JsonType.Object);
    
                    while(true)
                    {
                        reader.Read();
    
                        if(reader.Token == JsonToken.ObjectEnd)
                            break;
    
                        string property = (string)reader.Value;
    
                        ((IDictionary)instance)[property] = ReadValue(new JsonData(), reader);
                    }
                }
            }
            catch { }
    
            if(reader.Token == JsonToken.None)
            {
                JsonDataSetPrime(jsonData, json);
            }
    
            return jsonData;
        }
        
        public static string ToRawString(string str)
        {
            if(string.IsNullOrEmpty(str))
            {
                return string.Empty;
            }
            var rawStr = str;
            if(rawStr.StartsWith(""") && rawStr.EndsWith("""))
            {
                rawStr = rawStr.Substring(1, rawStr.Length - 2);
            }
            return rawStr;
        }
    
        public static void JsonDataSetPrime(JsonData jsonData, string json)
        {
            var rawType = jsonData.GetJsonType();
            DataTable dataTable = json;
            DataTable rawData = ToRawString((string)dataTable);
            switch(rawType)
            {
                case JsonType.Int: { ((IJsonWrapper)jsonData).SetInt((int)rawData); } break;
                case JsonType.Long: { ((IJsonWrapper)jsonData).SetLong((long)rawData); } break;
                case JsonType.Double: { ((IJsonWrapper)jsonData).SetDouble((double)rawData); } break;
                case JsonType.Boolean: { ((IJsonWrapper)jsonData).SetBoolean((bool)rawData); } break;
                case JsonType.String:
                    {
                        ((IJsonWrapper)jsonData).SetString((string)rawData);
                    }
                    break;
            }
        }

      这个 ReadValue 就是置换 JsonData 里的内容的, 使用 try catch 是因为它转换的逻辑是从上往下的, 节点的类型是由前置读取的类型给出的, 所以如果是一个Json对象, 它的字符串是合法的就能正确读取, 比如:

    {"aa":100}

      可是如果修改对象是普通值类型, 比如要修改的是 100 这个对应的JsonData, 传入的Json可能是这样的:

    "200"

      它就不是一个合法的Json, 是无法正常读取的, 所以会抛出异常, 并且导致 JsonReader 的类型是 None, 所以 JsonDataSetPrime() 方法就强制给JsonData赋值了. 这样就封装了节点数据置换的方法了.

      然后做一个数据映射的路径记录, 就能把数据映射到模板里面去了, 比如下图, 左边是数据, 右边是一个 Echarts 图表的模板 : 

      获取它的路径就很简单, 得到 From:xAxis/type To:title/text 这样的结果, 那么只需要获取到左边的节点, 然后ToJson, 用右边相应节点的JsonData来读取一遍即可.

      然后就是有些复杂的映射, 比如 Object(Dictionary) 到 Array 的映射, Array 中嵌套的 Object/Array 对应的结构的映射, 可以从下面的截图看出来 : 

      想要把 yAxis 这个 Object 映射到 data 这个 Array 里面去, 可以映射 Key 或者 Value 的方式 : 

     

      这里是把 yAxis 的 Keys 映射成 Array了. 或者也可以用 Values 映射, 只要结构能够对的上就行了. 代码逻辑跟下面相同, 只是把对应的数值取出来罢了:

        JsonData fromTag;
        var list = new List<string>(fromTag.Keys);
        var json = LitJson.JsonMapper.ToJson(list);

      因为 Object 的 Key 一定是 string 类型的, 所以可以简单的创建 List 对象, 可是如果是 Value 这种复杂对象, 就不能简单构建了!!! 这些映射可以通过简单的数据结构就能保存, 比如上面的我的结构是这样的 : 

        public enum MappingLogic
        {
            Replace,
            ObjectToArray_Keys,
            ObjectToArray_Values
        }
        public class MappingInfo
        {
            public string from;
            public string to;
            public MappingLogic mappingLogic = MappingLogic.Replace;
            public int order = 100;      // 数值越小越先执行
        }
        
        // Json 文件
        [{"from":"yAxis","to":"xAxis/data","mappingLogic":1,"order":100}]

      [ 从 yAxis 节点获取 Keys 映射到 xAxis/data 节点中去 ]

      嵌套式的映射 : 

       要将左边的 data (Array) 队列中的 Object 结构中的 xAxis 映射到右边的 data(Array)结构中去, 得到下图 : 

      其实可以看出, 复杂结构就复杂在 Array 的操作上, Object 的每个对象都是有 Key 的, 而 Array 需要通过自己获得 index 来进行映射(普通映射), 如果作为结构映射, 又需要通过 Foreach 进行结构厉遍, 看一下怎样通过代码完成映射结构的 : 

    先看生成的映射文件 

    [{
        "from": "series/[array:0]/markLine/data/[array:Foreach]/xAxis",
        "to": "series/[array:0]/data",
        "mappingLogic": 0,
        "order": 100
    }]

      from 的结构已经不是简单的节点路径了, 到 data/[array:Foreach] 这个节点, 表示的就是对于 data 这个节点的 array 需要进行厉遍, 然后再获取每个厉遍的 xAxis 节点的值, 映射到右边的 data 节点去, 

      代码结构 : 

        public const string Array = "array";
        public const string Foreach = "Foreach";
        
        
        public static void DoMap(JsonData from, JsonData to, MappingInfo info)
        {
            var fromTag = GetHierarchy(from, info.from, true);
            var json = "{}";
            switch(info.mappingLogic)
            {
                case MappingLogic.ObjectToArray_Keys:
                    {
                        var list = new List<string>(fromTag.Keys);
                        json = LitJson.JsonMapper.ToJson(list);
                    }
                    break;
                case MappingLogic.ObjectToArray_Values:
                    {
                        var list = new List<string>();
                        fromTag.ForeachDictionary((_key, _data) =>
                        {
                            list.Add(_data.ToString());
                        });
                        json = LitJson.JsonMapper.ToJson(list);
                    }
                    break;
                default: { json = fromTag.ToJson(); } break;
            }
            WrapJsonData(to, info.to, json, true, false);
        }
    
        // it must be root json node
        public static void WrapJsonData(JsonData jsonData, string hierarchy, string json, bool clear = true, bool keepRawData = true)
        {
            jsonData = GetHierarchy(jsonData, hierarchy, keepRawData);
            WrapJsonData(jsonData, json, clear);
        }
        public static void WrapJsonData(JsonData jsonData, string json, bool clear = true)
        {
            JsonReader reader = new JsonReader(json);
            if(clear)
            {
                jsonData.Clear();
                jsonData.ClearJsonCache();
            }
            ReadValue(jsonData, reader, json);
        }
        public static void WrapJsonData(JsonData jsonData, string json, bool clear = true)
        {
            JsonReader reader = new JsonReader(json);
            if(clear)
            {
                jsonData.Clear();
                jsonData.ClearJsonCache();
            }
            ReadValue(jsonData, reader, json);
        }
        public static JsonData GetHierarchy(JsonData root, string hierarchy, bool keepRawData = true)
        {
            var node = root;
            if(string.IsNullOrEmpty(hierarchy) == false)
            {
                var layers = hierarchy.Split('/');
                if(layers != null && layers.Length > 0)
                {
                    for(int i = 0; i < layers.Length; i++)
                    {
                        var target = layers[i];
                        int index = -1;
                        if(node.IsArray && IsArrayIndex(target, ref index))
                        {
                            node = node[index];
                        }
                        else if(node.IsArray && IsArrayForeach(target, ref index))
                        {
                            var tempNode = new JsonData();
                            foreach(JsonData data in node)
                            {
                                var lastHierarchy = string.Join("/", layers, i + 1, layers.Length - i - 1);
                                tempNode.Add(GetHierarchy(data, lastHierarchy, true));
                            }
                            if(keepRawData)
                            {
                                node = tempNode;
                            }
                            else
                            {
                                WrapJsonData(node, tempNode.ToJson(), true);
                            }
                            break;
                        }
                        else
                        {
                            node = node[target];
                        }
                    }
                }
            }
            return node;
        }
        public static bool IsArrayIndex(string pattern, ref int index)
        {
            string element = "";
            if(IsArrayElement(pattern, ref element))
            {
                if(int.TryParse(element, out index))
                {
                    return true;
                }
            }
            return false;
        }
        public static bool IsArrayForeach(string pattern, ref int index)
        {
            string element = "";
            if(IsArrayElement(pattern, ref element))
            {
                if(string.Equals(element, Foreach, System.StringComparison.OrdinalIgnoreCase))
                {
                    return true;
                }
            }
            return false;
        }
        public static bool IsArrayElement(string pattern, ref string element)
        {
            if(string.IsNullOrEmpty(pattern) == false)
            {
                if(pattern.StartsWith("[") && pattern.EndsWith("]"))
                {
                    var sp = pattern.Substring(1, pattern.Length - 2).Split(':');
                    if(sp != null && sp.Length > 1)
                    {
                        if(string.Equals(sp[0], Array, System.StringComparison.OrdinalIgnoreCase))
                        {
                            element = sp[1];
                            return true;
                        }
                    }
                }
            }
            return false;
        }

      现在主要的就是这些了, 有了半图形化的界面方式, 点击连线就能生成映射数据了, 并且从普通数据的类型保持, 到简单嵌套类型的数据映射, 它基本上能完成90%的需求了, 并且很贴心的添加了代码生成:

      减少了大量的工作量, 未来可期...

      对了, 在 EditorWindow 下怎样实现画线, 这里用了一个取巧的方法, 每个元素做成一个Toggle, 当左边的某个元素被点击之后, 就设定当前鼠标位置为起始位置(它们都在Scroll里, 需要偏移位置) : 

     leftPoint = (UnityEngine.Event.current.mousePosition) + new Vector2(0, _jsonStringScroll1.y);

      然后通过跟当前鼠标位置做连线(有个 Drawing 的脚本, wiki 可找到) : 

        if(_selectedLeft != null)
        {
            Drawing.DrawArrow(leftPoint - new Vector2(0, _jsonStringScroll1.y), UnityEngine.Event.current.mousePosition, Color.gray, 2, 10);
        }

      

      

    ---------------------------------------------------------------------

    今天发现对于结构映射没有很好的方法, 比如下面这样的 : 

      右边给出了结构, 左边给出了数据, 因为它们的命名不同, 所以需要根据右边的结构来对左边的数据进行映射, 就不能简单地把左边替换到右边去了.

      只能额外添加一个映射功能了, 添加了一个 MappingLogic.StructureMapping 的映射逻辑, 这里 默认为模板中的第一条数据为结构, 从数据中对它进行映射:

        public static void DoMap(JsonData from, JsonData to, MappingInfo info)
        {
            var fromTag = info.mappingLogic != MappingLogic.Write ? GetHierarchy(from, info.from, true) : null;
            var json = "{}";
            switch(info.mappingLogic)
            {
                case MappingLogic.ObjectToArray_Keys:
                    {
                        var list = new List<string>(fromTag.Keys);
                        json = LitJson.JsonMapper.ToJson(list);
                    }
                    break;
                case MappingLogic.ObjectToArray_Values:
                    {
                        var list = new List<string>();
                        fromTag.ForeachDictionary((_key, _data) =>
                        {
                            list.Add(_data.ToString());
                        });
                        json = LitJson.JsonMapper.ToJson(list);
                    }
                    break;
                case MappingLogic.StructureMapping:
                    {
                        var targetNode = GetHierarchy(to, info.to);
                        var structure = targetNode[0];
    
                        var fromVar = GetVarName(info.from);
                        var toVar = GetVarName(info.to);
                        for(int i = 0, imax = fromTag.Count; i < imax; i++)
                        {
                            JsonData sourceData = fromTag[i];
                            JsonData destData = targetNode.Count > i ? targetNode[i] : targetNode[targetNode.Add(Clone(structure))];
                            destData[toVar] = sourceData[fromVar];
                        }
                        return;
                    }
                    break;
                case MappingLogic.Write:
                    {
                        var targetNode = GetHierarchy(to, info.to);
                        WrapJsonData(targetNode, info.writeData, true);
                        return;
                    }
                    break;
                default: { json = fromTag.ToJson(); } break;
            }
            WrapJsonData(to, info.to, json, true, false);
        }

      而在获取目标节点的时候直接对结构节点返回了:

        public static JsonData GetHierarchy(JsonData root, string hierarchy, bool keepRawData = true)
        {
            var node = root;
            if(string.IsNullOrEmpty(hierarchy) == false)
            {
                var layers = hierarchy.Split('/');
                if(layers != null && layers.Length > 0)
                {
                    for(int i = 0; i < layers.Length; i++)
                    {
                        var target = layers[i];
                        int index = -1;
                        if(node.IsArray && IsArrayIndex(target, ref index))
                        {
                            node = node[index];
                        }
                        else if(node.IsArray && IsArrayForeach(target, ref index))
                        {
                            if(IsStructureMapping(target))
                            {
                                return node;  // 返回了
                            }
                            var tempNode = new JsonData();
                            foreach(JsonData data in node)
                            {
                                var lastHierarchy = string.Join("/", layers, i + 1, layers.Length - i - 1);
                                tempNode.Add(GetHierarchy(data, lastHierarchy, true));
                            }
                            if(keepRawData)
                            {
                                node = tempNode;
                            }
                            else
                            {
                                WrapJsonData(node, tempNode.ToJson(), true);
                            }
                            break;
                        }
                        else
                        {
                            node = node[target];
                        }
                    }
                }
            }
            return node;
        }

      当然这个必然是在 Array 节点下才需要的映射功能, 要不然直接映射就行了.

      映射的代码比较简单:

                case MappingLogic.StructureMapping:
                    {
                        var targetNode = GetHierarchy(to, info.to);
                        var structure = targetNode[0];
    
                        var fromVar = GetVarName(info.from);
                        var toVar = GetVarName(info.to);
                        for(int i = 0, imax = fromTag.Count; i < imax; i++)
                        {
                            JsonData sourceData = fromTag[i];
                            JsonData destData = targetNode.Count > i ? targetNode[i] : targetNode[targetNode.Add(Clone(structure))];
                            destData[toVar] = sourceData[fromVar];
                        }
                        return;
                    }
                    break;

      这里只需要把两个相关根节点找出来, 然后把模板节点进行克隆, 然后根据映射关系设置即可.

      这样的映射没有改变原有结构, 所以即使进行多层映射也是没有问题的

     

  • 相关阅读:
    报表设计器的使用之一:入门
    统计图开发之二:点图元
    统计图开发之一:画法定义
    集算器之五:序表
    集算器之四:程序流程
    忏悔录
    请不要离我而去
    所想和所做 所梦和所成
    做出改变,不断改变。
    Linux 操作命令
  • 原文地址:https://www.cnblogs.com/tiancaiwrk/p/15185160.html
Copyright © 2020-2023  润新知