本文内容由阿里云实名注册用户自发贡献,版权归原作者所有,阿里云开发者社区不拥有其著作权,亦不承担相应法律责任。具体规则请查看《 阿里云开发者社区用户服务协议 》和 《 阿里云开发者社区知识产权保护指引 》。如果您发现本社区中有涉嫌抄袭的内容,填写 侵权投诉表单 进行举报,一经查实,本社区将立刻删除涉嫌侵权内容。

前段时间武哥安排了个任务: 把结构动态的Json数据结构解析出来。所以要求无论嵌套了多少层,都要拿到最终节点,并且给特定的节点赋予规则,让这一类json数据对应节点进行对比时,遵循节点的规则 。这个任务其实可以拆解为三个任务:

  1. 拿到这类json的标准结构描述,并且在节点上标记规则
  2. 将json数据层层解构拿到所有节点,然后拿着数据节点去标准结构json里找到对应的节点,然后读取规则
  3. 将节点和规则存储为字典,key要独一无二

这样就将整个json数据转为了一个无序的规则字典。而这个规则字典不仅 key唯一,还要在value里存储值和规则 。这个任务不能说太难,也不能说太简单。首先我采用了一个看似合理的方案,即对json数据进行反序列化,心里想着反序列化为字典后就好搞多了,**但问题是面对复杂的json结构,你得层层反序字典,最关键的是你根本不知道有多少层。**最后武哥说可以参照下java里的JNode,于是乎在网上搜了很多,终于让我找到一种解决方案,就是用JToken,也就是Java里的JNode。然后翻遍全网,发现没有说的特别仔细点 ,于是基本JToken所有的方法我都实现了一遍,这里详细记录下,希望对大家有帮助。 当然,从这篇博客开始,我准备采用解决方案和日常学习分离的策略,所以解决方案或者最佳实践不会掺杂原理性的介绍,直接上实战,为什么这样做,文末会有答案 .

依照上边三个抛出的问题,制定了这样一套解决方案:

  1. json的标准结构使用JsonSchema来搞定,通过在Shema这个json数据的结构标准化描述上添加规则,因为Schema是此类数据的最标准描述,无论结构如何嵌套,Schema都可以搞定
  2. json层层结构拿到所有节点需要使用递归加循环的算法,这样才能走到每一个节点
  3. 唯一的key可以使用JToken里的JPath来限定,限定好后,就可以生成规则字典,value使用两个属性的类,一个为规则,一个为值,通过key可以拿到节点一切相关信息。

准备工作

在真正的代码实现前,先对要用到的知识来个实战,这样实现代码的时候很方便, 再强调一下,每一个方法都要有单元测试!单元测试很重要!可以节约大量的找bug时间

JsonSchema简单使用

简单来说就是描述Json数据的一种数据结构,本身也是Json结构。什么是JsonSchema,以及基本原理,我在自己的另一篇博客里有对类似的XML Schema 的详细记录,如果想详细了解,传送门送上:

【XML学习 三】XML Schema原理及使用 https://blog.csdn.net/sinat_33087001/article/details/80890714

如果想了解的更加深入,这里有标准制定的文档,一并奉上:

Json Schema文档说明 http://json-schema.org/latest/json-schema-validation.html#rfc.section.3.2.1

还是那句话,这里只讲实战:

  1. 首先要生成Json Schema就要拿到标准的Json数据,设计方案前期,由于考虑到xml也有解析的需求,所以通过xml转json来使两种数据结构公用一套代码,那么乱序的json和xml首先就要格式化一下,直接用在线格式化:

xml转Json以及json格式标准化网址: https://www.json.cn/

  1. 其次就要对拿到的标准json数据进行Schema生成,这个也可以使用在线生成工具:

Json Schema自动生成工具: https://jsonschema.net/

image

image

  1. 最后可以校验生成的Schema是否标准(其实第二步生成的应该是标准的,这一步无需做):

Json Schema在线验证工具 https://jsonschemalint.com/#/version/draft-06/markup/json

做完了这些,一份Json Schema就生成成功了**(注意我会在Schema的description里添加Description属性,并且在里边写上自己的对比规则)**,我自己的项目里是存储到了数据库里面,为了方便说明和讲解,这里我放到txt里说明,并且为此写了一个txt的读取方法。

#region 文件读取类
        /// <summary>
        /// 读取Schema文件
        /// </summary>
        /// <param name="filePath"></param>
        /// <returns></returns>
        public string ReadFile(string filePath)
            StreamReader sr = null;
            string json = "";
             `这里写代码片`  //一定要注意这里要用Encoding.Default限定编码格式,否则你的数据可能会乱码哦。
                sr = new StreamReader(@filePath, Encoding.Default); 
                string nextLine;
                while ((nextLine = sr.ReadLine()) != null)
                    json = json + nextLine.ToString();
            catch (Exception ex)
                logger.Error(ex + "文件读取失败");
            finally
                sr.Close();
            return json;
        #endregion 文件读取类

JToken的简单使用

这里展示JToken的常用方法,我认为解构一个Json解构这些操作基本就够了。

首先给出一个Json数据的样本格式

{
  "checked": false,
  "dimensions": {
    "width": 5,
    "height": 10
  "variables": [
      "code": "id",
      "name": "TML",
      "dataType": "String",
      "defaultValue": "",
      "showFlag": "0",
      "showValue": ""
      "code": "metaObjName",
      "name": "beijing",
      "defaultValue": "",
      "showValue": "",
      "dataType": "String",
      "showFlag": "0"
      "code": "detailViewName",
      "name": "shagnhai ",
      "defaultValue": "",
      "showValue": "",
      "dataType": "String",
      "showFlag": "0"
  "id": 1,
  "name": "A green door",
  "price": 12.5,
  "tags": [
    "home",
    "green"
}

然后给出Jtoken对该数据的解析操作:

#region Jtoken数据使用测试
        [TestMethod]
        public void JsonTokenTest()
            //一切的一切开始之前,需要把Json数据转换为JTken格式的数据
            JToken jsonValueToken = JToken.Parse(jsr.ReadFile(filePath));//将json数据转换为JToken
            JToken first = jsonValueToken.First; //first为:{"checked": false},也就是第一个子节点
            JToken last = jsonValueToken.Last;//last为:{"tags": ["home","green"]},也就是最后一个子节点
            var jsonHaveChild = jsonValueToken.HasValues;//为true,表名当前节点并非叶子节点
            JToken itemTages = jsonValueToken.SelectToken("tags");//{"tags": ["home","green"]},该方法的作用是依据传入的路径来获取节点的值,这个方法非常重要!!!,就是依靠它和唯一的路径我才能拿到值
            var itemTagesType = itemTages.Type;//当前节点类型,目前已知有Array.object,int ,string,bool
            var itemTagesHaveChild = itemTages.HasValues;
            JToken items = jsonValueToken.SelectToken("variables");
            var itemType = items.Type;
            var itemHaveChild = items.HasValues;
            var jpath = "variables[0].code";  //如果遇到数组,路径会加索引标记哦,这就是为什么虽然数组结构统一,我依然能有唯一的路径!!
            var enumNode = jsonValueToken.SelectToken(jpath);//通过路径获取一个实体
            var enumType = enumNode.Type;
            var enumHaveChild = enumNode.HasValues;
            foreach (var item in items)
                var path = item.Path;   //路径为从根节点root开始一直到当前节点
                var next = item.Next;    //当前节点的兄弟节点,同级的
                var parent = item.Parent;  //当前节点的父节点
                var lasts = item.Last;
                var root = item.Root; //当前节点的根节点
                var type = item.Type; //当前节点的类型
                var haveChild = item.HasValues;
            var childs = jsonValueToken.Children();
            foreach (var item in jsonValueToken)
                var path = item.Path;
                var next = item.Next;
                var parent = item.Parent;
                var lasts = item.Last;
                var root = item.Root;
                var type = item.Type;
                var haveChild = item.HasValues;
        #endregion Jtoken数据使用测试

开始实战

掌握了上边两个利器,就可以开始实战了(以下过程都是基于json数据和jsonSchema数据都已经搞定并存储在txt): 最初的想法开始的想法是将Json Schema读取出来,然后生成一个key为路径,value为规则的字典,然后拿着路径到json数据中找到数据,最后达到生成<path,(rule,value)>d字典的目标

初始化Json Schema字典

代码清单如下:

#region 递归schema树获取所有规则和路径
        public void SchemalevelTraverse(JToken json, ref Dictionary<string, string> jsonDic)
            //如果没有属性节点了,说明已经是叶子节点了
            if (json.SelectToken("properties") == null)
                if (json.SelectToken("items") == null)
                    if (json.SelectToken("description") != null)  //这里是我用于填充规则的
                        string rule = json.Value<string>("description");//从Json里取出规则值
                        if (!jsonDic.ContainsKey(json.Path))
                            jsonDic.Add(json.Path, rule);
                else {
                    var itemProperties = json.SelectToken("items").SelectToken("properties");
                    if (itemProperties != null)
                        foreach (var item in itemProperties)
                            if (item.First != null)
                                SchemalevelTraverse(item.First, ref jsonDic);
                return;
            foreach (var item in json.SelectToken("properties"))    //循环所有子节点
                if (item.First != null)
                    SchemalevelTraverse(item.First, ref jsonDic);   //递归调用
        #endregion 递归schema树获取所有规则和路径