c#: Newtonsoft.Json 高级用法二(jsonpath)

环境:

  • .net6.0
  • vs2022
  • Newtonsoft.Json 12.0.3

关于newtonsoft.json的使用常见问题参考:
《c#:序列化json常见问题及处理方法》
《c#:关于NewtonsoftJson序列化和Grpc序列化的冲突问题》
《c#: Newtonsoft.Json 高级用法》

问题:如何从json中快速提取数据?

比如有下面的json:

[
    {
        "name":"小明",
        "books":[
            {
                "name":"明-语文",
                "year":5
            }
        ]
    },
    {
        "name":"Tom",
        "books":[
            {
                "name":"Tom-物理",
                "year":3
            }
        ]
    }
]
  • 问题1: 如何提取所有的书籍名字呢?
  • 问题2: 如歌提取year在3年以上的书籍名字?

解决办法:使用jsonPath

直接上代码:

 //使用c#11语法,当前需要将工程设置预览,即在.csproj的根目录下加入:
 /*
 <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|AnyCPU'">
  <LangVersion>preview</LangVersion>
</PropertyGroup>

<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|AnyCPU'">
   <LangVersion>preview</LangVersion>
</PropertyGroup>
 */
 var str = """
     [
         {
             "name":"小明",
             "books":[
                 {
                     "name":"明-语文",
                     "year":5
                 }
             ]
         },
         {
             "name":"Tom",
             "books":[
                 {
                     "name":"Tom-物理",
                     "year":3
                 }
             ]
         }
     ]
     """;
 var root = Newtonsoft.Json.JsonConvert.DeserializeObject<JArray>(str);
 //提取所有的书籍名字呢
 Console.WriteLine(Newtonsoft.Json.JsonConvert.SerializeObject(root.SelectTokens("$[*].books[*].name")));//输出: ["明-语文","Tom-物理"]
 //提取year在3年以上的书籍名字
 Console.WriteLine(Newtonsoft.Json.JsonConvert.SerializeObject(root.SelectTokens("$[*].books[?(@.year>3)].name")));//输出: ["明-语文"]

jsonPath 语法细节

参考:
《Newtonsoft.Json文档: Querying JSON with JSON Path》
《JSONPath规则》

首先,我们需要明确:
在 newtonsoft 中我们可以将对象粗略的表示为下面两种类型:

  • 数组: 用JArray表示;
  • 非数组(对象或值):用JObject表示;

它们都继承自:JToken

然后,我们看newtonsoft提供jsonpath的方法:

  • public IEnumerable<JToken> SelectTokens(string path):

    期待返回的是数组,所以:无论是否找到或找到几个都会返回一个数组

  • public JToken? SelectToken(string path):

    期待返回的是非数组,所以:没找到返回null,找到1个就返回JToken,找到多个就报错!!!

有了上面的前提,我们来看jsonpath的语法介绍:
在这里插入图片描述

下面用一个实例介绍:

private static void TestBase()
{
    var str = """
        [
            {
                "name":"小明",
                "age":20,
                "books":[
                    {
                        "name":"明-语文",
                        "year":5,
                        "must":true
                    },
                    {
                        "name":"明-数学",
                        "year":2,
                        "must":false
                    }
                ]
            },
            {
                "name":"小红",
                "age":18,
                "birth":"1998-01-01",
                "books":[
                    {
                        "name":"红-政治",
                        "year":6,
                        "must":false
                    },
                    {
                        "name":"红-体育",
                        "year":4,
                        "must":false
                    }
                ]
            },
            {
                "name":"Tom",
                "age":22,
                "birth":null,
                "books":[
                    {
                        "name":"Tom-物理",
                        "year":6,
                        "must":"true"
                    }
                ]
            }
        ]
""";
    var root = str.ToObject<JArray>();
    //输出原样
    Console.WriteLine(root.SelectToken("$").ToJson());//output: 省略

    //打印null,因为$是一个数组,它不是对象,没有name属性
    Console.WriteLine(root.SelectToken($"$.name").ToJson());//output: null

    //打印所有的name,包括人的姓名和书籍名称
    Console.WriteLine(root.SelectTokens($"$..name").ToJson());//output: ["小明","明-语文","明-数学","小红","红-政治","红-体育","Tom","Tom-物理"]

    //打印所有书籍的名称
    Console.WriteLine(root.SelectTokens($"$..books..name").ToJson());//output: ["明-语文","明-数学","红-政治","红-体育","Tom-物理"]

    //打印年龄小于20的人所有书籍名称
    Console.WriteLine(root.SelectTokens($"$[?(@.age < 20)].books..name").ToJson());//output: ["红-政治","红-体育"]

    //打印小明的所有书籍名称
    Console.WriteLine(root.SelectTokens($"$[?(@.name == '小明')].books..name").ToJson());//output: ["明-语文","明-数学"]

    //打印有birth属性的所有书籍名称
    Console.WriteLine(root.SelectTokens($"$[?(@.birth)].books..name").ToJson());//output: ["红-政治","红-体育","Tom-物理"]

    //打印有birth属性并且age>20的人的所有书籍名称
    Console.WriteLine(root.SelectTokens($"$[?(@.birth && @.age>20)].books..name").ToJson());//output: ["Tom-物理"]

    //打印有birth属性并且birth不为空的人的所有书籍名称
    Console.WriteLine(root.SelectTokens($"$[?(@.birth && @.birth != '' && @.birth <>null)].books..name").ToJson());//output: ["红-政治","红-体育"]

    //正则: 打印所有姓名"小"开头人的所有书籍名称
    Console.WriteLine(root.SelectTokens($"$[?(@.name =~ /^小.+$/)].books..name").ToJson());//output: ["明-语文","明-数学","红-政治","红-体育"]

    //打印拥有必读书籍的人的所有书籍名称: 注意 must 是完全相等,字符串 "true" 不行
    Console.WriteLine(root.SelectTokens($"$[?(@.books[?(@.must===true)])].books..name").ToJson());//output: ["明-语文","明-数学"]

    //打印所有人的所有书籍: 注意 是二维数组结构
    Console.WriteLine(root.SelectTokens($"$..books").ToJson());//output: 省略

    //打印所有人的所有书籍: 注意 是一维结构
    Console.WriteLine(root.SelectTokens($"$..books[*]").ToJson());//output: 省略

    //打印所有书籍名称
    Console.WriteLine(root.SelectTokens($"$..books[*].name").ToJson());//output: ["明-语文","明-数学","红-政治","红-体育","Tom-物理"]

    //数组切片 [start: end:step]
    //打印最后一个人的最后一个书籍信息
    Console.WriteLine(root.SelectTokens($"$[-1:].books[-1:]").ToJson());//output: [{"name":"Tom-物理","year":6,"must":"true"}]

    //打印前两个人的最后一个书籍信息
    Console.WriteLine(root.SelectTokens($"$[0,1].books[-1:]").ToJson());//output: [{"name":"明-数学","year":2,"must":false},{"name":"红-体育","year":4,"must":false}]

    //打印第一个、第三个人的最后一个书籍信息(起始0,结束3,步长2,正好跳过中间的小红)
    Console.WriteLine(root.SelectTokens($"$[0:3:2].books[-1:]").ToJson());//output: [{"name":"明-数学","year":2,"must":false},{"name":"Tom-物理","year":6,"must":"true"}]

    //打印第一个人的最后一个书籍信息(从第一个到倒数第二个,不包含倒数第二个,所以就只能是第一个人(总共3个))
    Console.WriteLine(root.SelectTokens($"$[0:-2].books[-1:]").ToJson());//output: [{"name":"明-数学","year":2,"must":false}]
}

//扩展方法: ToJson ToObject
public static class JsonExtensions
{
    public static string ToJson(this object obj)
    {
        return JsonConvert.SerializeObject(obj);
    }

    public static T ToObject<T>(this string str)
    {
        return JsonConvert.DeserializeObject<T>(str);
    }
}

其中的切片语法:

参考:《Python-序列切片原理和切片协议-[start🔚step]》

在这里插入图片描述

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

jackletter

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值