[STJson]在.NET中使用JsonPath

JsonPath简介

JsonPath是源自于XPath的使用方式,总所周知XPathXML路径语言(XML Path Language),它是一种用来确定XML文档中某部分位置的语言。显而易见JsonPath是用于Json的。

.NET中有不少第三方库提供了JsonPath的功能,如很流行的Newtonsoft.json,它目前应该是在.NET中最流行的Json库,但似乎它的JsonPath功能有一些简陋。为了确认这一点(而不是博主不会用),博主特意去翻阅了它的源代码,确实仅有一些基本的功能,且兼容性不是很好。

STJson

STJson是博主自己开发的一套库,包含了Json的解析,序列化和反序列化甚至数据聚合等功能,同时也包含了JsonPath功能。应该是JsonPath支持算是相对比较完整的了。因为博主并非像其他库一样将JsonPath字符串进行一个正则解析拆分然后判断。博主为其写了一个语法解析器,所以在STJsonJsonPath可以使用很复杂的表达式,且提供了用户自定义函数和调试功能。

项目地址: 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]这样也是可以被允许的。在内部会直接创建两个列表选择器,而根据情况选择使用哪一个。
选择索引为02的元素。

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循环中的三个条件,所以原理与效果就不再说明。

expressionrangenote
[::]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
  • !
operatornotee.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表示忽略大小写,其正则表达式以.NetRegex为标准。(?...)开头则表示设置匹配模式。至于匹配模式自行查阅相关资料。

选中hobby不包含singswing的元素:

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]。但是很显然02将不会起到任何作用,因为第二层的数据对象并不是一个数组。

上面的表达式等同于*.['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
    }
}

如果过程不重要,仅仅是想看执行结果。

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

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值