強(qiáng)大!JSON解析神器,再?gòu)?fù)雜也不怕了
環(huán)境:SpringBoot3.4.0
1. 簡(jiǎn)介
你是否遇到過需要從一個(gè)復(fù)雜的JSON結(jié)構(gòu)中提取特定信息的場(chǎng)景?
當(dāng)你面對(duì)一個(gè)嵌套層級(jí)很多、結(jié)構(gòu)復(fù)雜的JSON對(duì)象時(shí),你是否覺得逐一創(chuàng)建對(duì)應(yīng)的Java實(shí)體類(或者是Map)來接收這些數(shù)據(jù)既繁瑣又低效?
在進(jìn)行單元測(cè)試或模擬請(qǐng)求時(shí),你是否需要構(gòu)建或修改JSON數(shù)據(jù)?
在測(cè)試過程中,我們可能需要構(gòu)建特定的JSON請(qǐng)求體或修改響應(yīng)數(shù)據(jù)。有沒有一種工具可以幫助我們輕松地完成這些操作?
答案是肯定的!有一種名為JSONPath的工具,它提供了一種類似于XPath的表達(dá)式語(yǔ)言,用于在JSON文檔中查詢和操作數(shù)據(jù)。JSONPath不僅能夠高效地定位和提取JSON中的特定信息,還支持?jǐn)?shù)據(jù)過濾、轉(zhuǎn)換和更新等操作。
2. 實(shí)戰(zhàn)案例
2.1 準(zhǔn)備環(huán)境
<dependency>
<groupId>com.jayway.jsonpath</groupId>
<artifactId>json-path</artifactId>
</dependency>
如果你是基于Spring Boot環(huán)境,那么你不需要指定版本,Spring Boot已經(jīng)幫我們管理了該組件的版本。截至撰寫該篇文章時(shí),JSONPath的最新版本是2.9.0。
說明:JsonPath表達(dá)式總是以與XPath表達(dá)式在XML文檔中結(jié)合使用相同的方式來引用JSON結(jié)構(gòu)。在JsonPath中,“根成員對(duì)象”無(wú)論是對(duì)象還是數(shù)組,總是被引用為$。
2.2 語(yǔ)法介紹
JsonPath 表達(dá)式可以使用點(diǎn)符號(hào)"."
$.store.book[0].title
也可以使用括號(hào)表示法
$['store']['book'][0]['title']
操作符
操作 | 說明 |
$ | 查詢的根元素,所有路徑表達(dá)式的起點(diǎn) |
@ | 由過濾器謂詞處理的當(dāng)前節(jié)點(diǎn) |
* | 通配符,在需要名稱或數(shù)字的位置可用 |
.. | 深度掃描,在需要名稱的位置可用 |
.<name> | 點(diǎn)表示法的子節(jié)點(diǎn) |
['<name>' (, '<name>')] | 方括號(hào)表示法的子節(jié)點(diǎn)或子節(jié)點(diǎn)集合 |
[<number> (, <number>)] | 數(shù)組索引或索引集合 |
[start:end] | 數(shù)組切片操作符 |
[?(<expression>)] | 過濾器表達(dá)式,表達(dá)式必須計(jì)算為布爾值 |
函數(shù)
函數(shù)可以在路徑的末尾被調(diào)用——函數(shù)的輸入是路徑表達(dá)式的輸出。函數(shù)的輸出由函數(shù)本身決定。支持如下的函數(shù):
過濾操作
過濾器是用于篩選數(shù)組的邏輯表達(dá)式。一個(gè)典型的過濾器可能是[?(@.age > 18)],其中@代表當(dāng)前正在處理的項(xiàng)??梢允褂眠壿嬤\(yùn)算符&&和||創(chuàng)建更復(fù)雜的過濾器。字符串字面量必須用單引號(hào)或雙引號(hào)括起來(例如[?(@.color == 'blue')]或[?(@.color == "blue")])。支持如下過濾:
操作符 | 說明 |
== | 左值等于右值(注意1不等于'1') |
!= | 左值不等于右值 |
< | 左值小于右值 |
<= | 左值小于或等于右值 |
> | 左值大于右值 |
>= | 左值大于或等于右值 |
=~ | 左值匹配正則表達(dá)式 [?(@.name =~ /foo.*?/i)] |
in | 左值存在于右值中 [?(@.size in ['S', 'M'])] |
nin | 左值不存在于右值中 |
subsetof | 左值是右值的子集 [?(@.sizes subsetof ['S', 'M', 'L'])] |
anyof | 左值與右值有交集 [?(@.sizes anyof ['M', 'L'])] |
noneof | 左值與右值無(wú)交集 [?(@.sizes noneof ['M', 'L'])] |
size | 左值(數(shù)組或字符串)的大小應(yīng)與右值匹配 |
empty | 左值(數(shù)組或字符串)應(yīng)為空 |
接下來,我們介紹具體的使用示例。
2.3 使用示例
準(zhǔn)備json數(shù)據(jù)
{
"store": {
"book": [
{
"category": "Java",
"author": "張三",
"title": "Java從入門到放棄",
"price": 88.6
},
{
"category": "Spring",
"author": "Pack",
"title": "Spring從入門到精通",
"price": 99.8
},
{
"category": "Java",
"author": "李四",
"title": "多線程并發(fā)編程",
"isbn": "1-9527-6688-9",
"price": 66.9
},
{
"category": "Spring",
"author": "Pack",
"title": "Spring Boot3實(shí)戰(zhàn)案例100講",
"isbn": "6-9527-7799-2",
"price": 70
}
],
"bicycle": {
"color": "red",
"price": 666
}
},
"expensive": 10
}
層級(jí)格式如下:
接下來,我們將在代碼中通過jsonpath來操作上面的json數(shù)據(jù)。
讀取json內(nèi)容
String json = new ClassPathResource("data.json")
.getContentAsString(StandardCharsets.UTF_8) ;
下面都將基于上面讀取到的json數(shù)據(jù)進(jìn)行操作。
獲取所有圖書的作者
List<String> list = JsonPath.parse(json).read("$.store.book[*].author");
System.err.println("Authors: " + list) ;
// 輸出結(jié)果
Authors: ["張三","Pack","李四","Pack"]
獲取所有作者
List<String> list = JsonPath.parse(json).read("$..author") ;
System.err.println("Authors: " + list) ;
// 輸出結(jié)果
Authors: ["張三","Pack","李四","Pack"]
獲取store節(jié)點(diǎn)下的所有數(shù)據(jù)
Object result = JsonPath.parse(json).read("$.store.*") ;
System.err.println(result) ;
輸出結(jié)果如下:
類型是:net.minidev.json.JSONArray
獲取所有價(jià)格price
/**獲取store節(jié)點(diǎn)下的所有price*/
List<Double> prices = JsonPath.parse(json).read("$.store..price") ;
System.err.println(prices) ;
// 輸出結(jié)果
[88.6,99.8,66.9,70,666]
獲取指定索引的書
/**獲取第三本書*/
Object result = JsonPath.parse(json).read("$..book[2]") ;
System.err.println(result) ;
/**輸出結(jié)果; 類型:net.minidev.json.JSONArray*/
[{"category":"Java","author":"李四","title":"多線程并發(fā)編程","isbn":"1-9527-6688-9","price":66.9}]
result = JsonPath.parse(json).read("$..book[0,1]") ;
System.err.println(result) ;
// 輸出結(jié)果
[
{"category":"Java","author":"張三","title":"Java從入門到放棄","price":88.6},
{"category":"Spring","author":"Pack","title":"Spring從入門到精通","price":99.8}
]
所有帶有ISBN編號(hào)的書
/**所有帶有ISBN編號(hào)的書*/
Object result = JsonPath.parse(json).read("$..book[?(@.isbn)]") ;
System.err.println(result) ;
// 輸出結(jié)果
[
{"category":"Java","author":"李四","title":"多線程并發(fā)編程","isbn":"1-9527-6688-9","price":66.9},
{"category":"Spring","author":"Pack","title":"Spring Boot3實(shí)戰(zhàn)案例100講","isbn":"6-9527-7799-2","price":70}
]
獲取所有價(jià)格小于70元的圖書
/**獲取所有價(jià)格小于70元的圖書*/
Object result = JsonPath.parse(json).read("$.store.book[?(@.price < 70)]") ;
System.err.println(result) ;
// 輸出結(jié)果
[
{"category":"Java","author":"李四","title":"多線程并發(fā)編程","isbn":"1-9527-6688-9","price":66.9}
]
獲取圖書的個(gè)數(shù)
// 獲取圖書的個(gè)數(shù)
Integer count = JsonPath.parse(json).read("$..book.length()") ;
System.err.println(count) ;
// 輸出結(jié)果
4
自定義過濾條件
import static com.jayway.jsonpath.Criteria.where;
import static com.jayway.jsonpath.Filter.filter;
// 自定義filter
Filter cheapFictionFilter = filter(
where("category").is("Java").and("price").lte(70D)
);
List<Map<String, Object>> books = JsonPath.parse(json).read("$.store.book[?]", cheapFictionFilter);
System.err.println(books) ;
// 輸出結(jié)果
[
{"category":"Java","author":"李四","title":"多線程并發(fā)編程","isbn":"1-9527-6688-9","price":66.9}
]
修改數(shù)據(jù)
// 修改數(shù)據(jù)
String newJson = JsonPath.parse(json)
.set("$['store']['book'][0]['author']", "pack_xg")
.jsonString();
System.out.println(newJson) ;
輸出結(jié)果:
圖片