JsonPath简介
JsonPath
是源自于XPath
的使用方式,总所周知XPath
是XML
路径语言(XML Path Language),它是一种用来确定XML
文档中某部分位置的语言。显而易见JsonPath
是用于Json
的。
在.NET
中有不少第三方库提供了JsonPath
的功能,如很流行的Newtonsoft.json
,它目前应该是在.NET
中最流行的Json
库,但似乎它的JsonPath
功能有一些简陋。为了确认这一点(而不是博主不会用),博主特意去翻阅了它的源代码,确实仅有一些基本的功能,且兼容性不是很好。
STJson
STJson
是博主自己开发的一套库,包含了Json
的解析,序列化和反序列化甚至数据聚合等功能,同时也包含了JsonPath
功能。应该是JsonPath
支持算是相对比较完整的了。因为博主并非像其他库一样将JsonPath
字符串进行一个正则解析拆分然后判断。博主为其写了一个语法解析器,所以在STJson
的JsonPath
可以使用很复杂的表达式,且提供了用户自定义函数和调试功能。
项目地址: https://github.com/DebugST/STJson
在线教程: https://debugst.github.io/STJson/tutorial_cn.html
Nuget: https://www.nuget.org/packages/STLib.Json
测试数据
[{
"name": "Tom", "age": 16, "gender": 0,
"hobby": [
"cooking", "sing"
]
},{
"name": "Tony", "age": 16, "gender": 0,
"hobby": [
"game", "dance"
]
},{
"name": "Andy", "age": 20, "gender": 1,
"hobby": [
"draw", "sing"
]
},{
"name": "Kun", "age": 26, "gender": 1,
"hobby": [
"sing", "dance", "rap", "basketball"
]
}]
假设上述数据保存在test.json
文件中。将其加载到程序中。
var json_src = STJson.Deserialize(System.IO.File.ReadAllText("./test.json"));
选择器
在STJsonPath
支持一下选择器。
选择器 | 说明 |
---|---|
$ | 根节点选择器,可视作代表根节点对象。 |
@ | 当前元素选择器,在遍历过程中指代当前被遍历的元素。 |
* | 通配符,表示可以代表任何一个节点。 |
. | 子节点选择器,指定子节点的key。 |
… | 深度选择器,表示可以是任意路径。 |
[‘<name>’(,‘<name>’)] | 列表选择器,指定子节点的key集合。 |
[<number>(,(<number>))] | 列表选择器,指定子节点的index集合。 |
[Start:End:Step] | 切片选择器,用于指定索引区间。 |
[(<expression>)] | 表达式选择器,用于输入一个运算表达式,并将结果作为索引继续向下选择。 |
[?(<expression>)] | 表达式选择器,用于输入一个运算表达式,并将结果转换为布尔值,决定是否继续选择。 |
注: 在STJsonPath
中$
开头并非必修的,且在内部会移除掉开头的$
和@
。他们仅用于在表达式中作为变量使用。应为博主认为,作为开头似乎有点多余。
构建JsonPath
通过以下方式可以构建一个STJsonPath
:
// var jp = new STJsonPath("$[0]name");
// var jp = new STJsonPath("$[0].name");
var jp = new STJsonPath("[0]'name'"); // 以上方式均可以使用 $不是必须的
Console.WriteLine(jp.Select(json_src));
/*******************************************************************************
* [output] *
*******************************************************************************/
["Tom"]
当然在STJson
中的扩展函数中已经集成STJsonPath
,可以通过下面的方式直接使用:
// var jp = new STJsonPath("[0].name");
// Console.WriteLine(json_src.Select(jp));
Console.WriteLine(json_src.Select("[0].name")); // 内部动态构建 STJsonPath
/*******************************************************************************
* [output] *
*******************************************************************************/
["Tom"]
在STJsonPath
中允许使用'
或者"
,比如:'a.b'
"a.b"
,STJsonPath
会将其视为一个独立的个体。而不是两个。列如有如下Json
:
{
"a.b": "this is a test"
}
很明显通过Select("a.b")
是无法获取到数据的,需要通过Select("'a.b'")
。
string strTemp = "{\"a.b\": \"this is a test\"}";
var json_temp = STJson.Deserialize(strTemp);
Console.WriteLine(json_temp.Select("a.b"));
Console.WriteLine(json_temp.Select("'a.b'"));
/*******************************************************************************
* [output] *
*******************************************************************************/
[]
["this is a test"]
在字符串中支持\
进行转义:\r
\n
\t
\f
\b
\a
\v
\0
\x..
\u...
\.
通配符
通配符可表示当前层级中的任何一个节点。获取所有人员姓名。
Console.WriteLine(json_src.Select("*.name").ToString(4));
/*******************************************************************************
* [output] *
*******************************************************************************/
[
"Tom", "Tony", "Andy", "Kun"
]
深度选择器
深度选择器与通配符类似,但深度选择器可以是任意层级。
Console.WriteLine(json_src.Select("..name").ToString(4));
/*******************************************************************************
* [output] *
*******************************************************************************/
[
"Tom", "Tony", "Andy", "Kun"
]
列表选择器
在上面的表格中可以看到有两个列表选择器
,但事实上可以混用,也就是说[0,'abc',1]
这样也是可以被允许的。在内部会直接创建两个列表选择器,而根据情况选择使用哪一个。
选择索引为0
和2
的元素。
Console.WriteLine(json_src.Select("[0,2]").ToString(4));
/*******************************************************************************
* [output] *
*******************************************************************************/
[
{
"name": "Tom",
"age": 16,
"gender": 0,
"hobby": [
"cooking", "sing"
]
}, {
"name": "Andy",
"age": 20,
"gender": 1,
"hobby": [
"draw", "sing"
]
}
]
对于int
索引可以使用负数,比如-1
则表示获取最后一个元素。当STJsonPath
检测到负数时候会执行STJson.Count - n
将结果作为索引。
//Console.WriteLine(json_src.Select("-1").ToString(4));
Console.WriteLine(json_src.Select("[-1]").ToString(4));
/*******************************************************************************
* [output] *
*******************************************************************************/
[
{
"name": "Kun",
"age": 26,
"gender": 1,
"hobby": [
"sing", "dance", "rap", "basketball"
]
}
]
切片选择器
切片选择器用于在数组中选择一个片段,切片选择器默认值[0:-1:1]
,切片中的三个值等同于for
循环中的三个条件,所以原理与效果就不再说明。
expression | range | note |
---|---|---|
[::] | 0 <= R <= {OBJ}.length - 1 | 等同于* |
[5:] | 5 <= R <= {OBJ}.length - 1 | 从第6个元素开始,获取所有元素 |
[-1:0] | {OBJ}.length - 1 >= R >= 0 | 倒序获取数据 |
[0::2] | 0 <= R <= {OBJ}.length - 1 | 顺序获取数据,且间隔一个数据 |
切片选择器中至少出现一个:
且step
大于0
,否则将获得异常。
Console.WriteLine(json_src.Select("[-1:]").ToString(4));
/*******************************************************************************
* [output] *
*******************************************************************************/
[
{
"name": "Kun",
"age": 26,
"gender": 1,
"hobby": [
"sing", "dance", "rap", "basketball"
]
}
]
表达式
在[?()]
中可支持下列运算符,优先级从上至下依次升高
-
&&
||
-
<
<=
>
>=
==
!=
re
-
&
|
<<
>>
^
~
-
+
-
-
*
/
%
-
in
nin
anyof
!
operator | note | e.g |
---|---|---|
re | 正则表达式 | [?(@.name re ‘un’)] |
in | 左边的值或数组包含在右边的数组中 | [?(@.age in [16,20])] |
nin | 左边的值或数组不包含在右边的数组中 | [?(@.hobby nin [‘sing’,‘draw’])] |
anyof | 左边的值或数组和右边的数组存在交集 | [?(@.hobby anyof [‘sing’,‘draw’])] |
表达式有两种模式:
- [?(<expression>)] - 过滤表达式,用于计算出一个布尔值,确定是否继续匹配。
- [(<expression>)] - 普通表达式,用于计算出一个值,并将值作为索引继续匹配。
过滤表达式
选中name
中包含字母ku
的元素:
//Console.WriteLine(json_src.Select("*.[?(@.name == 'kun')]").ToString(4));
Console.WriteLine(json_src.Select("*.[?(@.name re '(?i)ku')]").ToString(4));
/*******************************************************************************
* [output] *
*******************************************************************************/
[
{
"name": "Kun",
"age": 26,
"gender": 1,
"hobby": [
"sing", "dance", "rap", "basketball"
]
}
]
(?i)
中的i表示忽略大小写,其正则表达式以.Net
中Regex
为标准。(?...)
开头则表示设置匹配模式。至于匹配模式自行查阅相关资料。
选中hobby
不包含sing
和swing
的元素:
Console.WriteLine(json_src.Select("*.[?(@.hobby nin ['sing','draw'])]").ToString(4));
/*******************************************************************************
* [output] *
*******************************************************************************/
[
{
"name": "Tony",
"age": 16,
"gender": 0,
"hobby": [
"game", "dance"
]
}
]
普通表达式
普通表达式会将结果作为STJsonPath
的部分继续匹配。
Console.WriteLine(json_src.Select("*.[('na' + 'me')]").ToString(4));
/*******************************************************************************
* [output] *
*******************************************************************************/
[
"Tom", "Tony", "Andy", "Kun"
]
在[('na' + 'me')]
中'na' + 'me'
的结果为'name'
,并且会将这个值作为索引,所以上述效果等同于*.name
,当然返回值也可以是一个集合。
Console.WriteLine(json_src.Select("*.[(['na' + 'me', 'age', 0, 1 + 1])]").ToString(4));
/*******************************************************************************
* [output] *
*******************************************************************************/
[
"Tom", 16, "Tony", 16, "Andy", 20, "Kun", 26
]
上面表达式的结算结果值为['name', 'age', 0, 2]
。但是很显然0
和2
将不会起到任何作用,因为第二层的数据对象并不是一个数组。
上面的表达式等同于*.['name', 'age', 0, 2]
。如果将上面的换成第三层会得到下面的结果。
Console.WriteLine(json_src.Select("*.*.[(['na' + 'me', 'age', 0, 1 + 1])]").ToString(4));
/*******************************************************************************
* [output] *
*******************************************************************************/
[
"cooking", "game", "draw", "sing", "rap"
]
可以看到'name'
和'age'
对于hobby
来说是无效的,因为hobby
是一个数组。
测试表达式
可能读者并不了解表达式在内部是如何被执行了并且会输出什么样的结果,博主提供了一个静态测试函数TestExpression()
可用于调试表达式。若有什么不明白的地方测试一下就会看到过程及结果。
Console.WriteLine(STJsonPath.TestExpression(
null, // [STJson] 用于替代表达式中出现的 $
null, // [STJson] 用于替代表达式中出现的 @
"1+2+3" // 表达式文本
).ToString(4));
/*******************************************************************************
* [output] *
*******************************************************************************/
{
"type": "expression",
"parsed": "{1 + 2 + 3}", // 格式化后的文本 {}表示此部分需要单独执行 如: [1, {1+1}, 3]
"polish": [
"1", "2", " + ", "3", " + " // 逆波兰方式排列
],
"steps": [ // 执行步骤
{
"type": "excute",
"operator": "+",
"get_left_token": { // 计算操作符左边元素的值,表达式左边也可能是一个表达式
"parsed": "1",
"type": "value",
"result": {
"value_type": "Long",
"text": "1"
}
},
"get_right_token": {
"parsed": "2",
"type": "value",
"result": {
"value_type": "Long",
"text": "2"
}
},
"result": { // 该步骤执行结果
"value_type": "Long",
"text": "3"
}
}, {
"type": "excute",
"operator": "+",
"get_left_token": { // 此时操作符左边的元素为上一步的计算结果
"parsed": "3",
"type": "value",
"result": {
"value_type": "Long",
"text": "3"
}
},
"get_right_token": {
"parsed": "3",
"type": "value",
"result": {
"value_type": "Long",
"text": "3"
}
},
"result": {
"value_type": "Long",
"text": "6"
}
}
],
"check_result": { // 清空波兰表达式数据栈,确定最终输出结果。
"parsed": "6",
"type": "value",
"result": {
"value_type": "Long",
"text": "6"
}
},
"return": { // 最终返回值
"value_type": "Long",
"text": "6",
"bool": true // 如果用作布尔表达式则转换为 true
}
}
如果过程不重要,仅仅是想看执行结果。文章来源:https://www.toymoban.com/news/detail-551377.html
Console.WriteLine(STJsonPath.TestExpression(
null, // [STJson] 用于替代表达式中出现的 $
null, // [STJson] 用于替代表达式中出现的 @
"1+2+3" // 表达式文本
).SelectFirst("return").ToString(4));
/*******************************************************************************
* [output] *
*******************************************************************************/
{
"return": { // 最终返回值
"value_type": "Long",
"text": "6",
"bool": true // 如果用作布尔表达式则转换为 true
}
}
结束
上面仅仅列举了部分常用功能,在STJson
中还有许多其他功能由于篇幅原因这里仅介绍这么多,读者可以查看在线完整教程。
https://debugst.github.io/STJson/tutorial_cn.html
如果你觉得项目不错可以支持一下博主:
https://github.com/DebugST/STJson文章来源地址https://www.toymoban.com/news/detail-551377.html
到了这里,关于[STJson]在.NET中使用JsonPath的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!