本文内容由阿里云实名注册用户自发贡献,版权归原作者所有,阿里云开发者社区不拥有其著作权,亦不承担相应法律责任。具体规则请查看《
前段时间武哥安排了个任务:
把结构动态的Json数据结构解析出来。所以要求无论嵌套了多少层,都要拿到最终节点,并且给特定的节点赋予规则,让这一类json数据对应节点进行对比时,遵循节点的规则
。这个任务其实可以拆解为三个任务:
-
拿到这类json的标准结构描述,并且在节点上标记规则
-
将json数据层层解构拿到所有节点,然后拿着数据节点去标准结构json里找到对应的节点,然后读取规则
-
将节点和规则存储为字典,key要独一无二
这样就将整个json数据转为了一个无序的规则字典。而这个规则字典不仅
key唯一,还要在value里存储值和规则
。这个任务不能说太难,也不能说太简单。首先我采用了一个看似合理的方案,即对json数据进行反序列化,心里想着反序列化为字典后就好搞多了,**但问题是面对复杂的json结构,你得层层反序字典,最关键的是你根本不知道有多少层。**最后武哥说可以参照下java里的JNode,于是乎在网上搜了很多,终于让我找到一种解决方案,就是用JToken,也就是Java里的JNode。然后翻遍全网,发现没有说的特别仔细点 ,于是基本JToken所有的方法我都实现了一遍,这里详细记录下,希望对大家有帮助。
当然,从这篇博客开始,我准备采用解决方案和日常学习分离的策略,所以解决方案或者最佳实践不会掺杂原理性的介绍,直接上实战,为什么这样做,文末会有答案
.
依照上边三个抛出的问题,制定了这样一套解决方案:
-
json的标准结构使用JsonSchema来搞定,通过在Shema这个json数据的结构标准化描述上添加规则,因为Schema是此类数据的最标准描述,无论结构如何嵌套,Schema都可以搞定
-
json层层结构拿到所有节点需要使用递归加循环的算法,这样才能走到每一个节点
-
唯一的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
还是那句话,这里只讲实战:
-
首先要生成Json Schema就要拿到标准的Json数据,设计方案前期,由于考虑到xml也有解析的需求,所以通过xml转json来使两种数据结构公用一套代码,那么乱序的json和xml首先就要格式化一下,直接用在线格式化:
xml转Json以及json格式标准化网址:
https://www.json.cn/
-
其次就要对拿到的标准json数据进行Schema生成,这个也可以使用在线生成工具:
Json Schema自动生成工具:
https://jsonschema.net/
-
最后可以校验生成的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树获取所有规则和路径