Java DSL สำหรับการอ่านเอกสาร JSON
Jayway JsonPath เป็นพอร์ต Java ของการนำ Stefan Goessner JsonPath ไปใช้งาน
JsonPath มีให้ใช้งานที่ Central Maven Repository ผู้ใช้ Maven เพิ่มสิ่งนี้ลงใน POM ของคุณ
< dependency >
< groupId >com.jayway.jsonpath</ groupId >
< artifactId >json-path</ artifactId >
< version >2.9.0</ version >
</ dependency >
หากคุณต้องการความช่วยเหลือ ให้ถามคำถามที่ Stack Overflow แท็กคำถาม 'jsonpath' และ 'java'
นิพจน์ JsonPath อ้างอิงถึงโครงสร้าง JSON ในลักษณะเดียวกับนิพจน์ XPath ที่ใช้ร่วมกับเอกสาร XML เสมอ "วัตถุสมาชิกรูท" ใน JsonPath จะถูกเรียกว่า $
เสมอ ไม่ว่าจะเป็นวัตถุหรืออาร์เรย์
นิพจน์ JsonPath สามารถใช้เครื่องหมายจุดได้
$.store.book[0].title
หรือเครื่องหมายวงเล็บเหลี่ยม
$['store']['book'][0]['title']
ผู้ดำเนินการ | คำอธิบาย |
---|---|
$ | องค์ประกอบรูทที่จะสอบถาม สิ่งนี้จะเริ่มต้นนิพจน์พาธทั้งหมด |
@ | โหนดปัจจุบันกำลังถูกประมวลผลโดยเพรดิเคตตัวกรอง |
* | ไวลด์การ์ด ใช้ได้ทุกที่ที่ต้องการชื่อหรือตัวเลข |
.. | สแกนลึก ใช้ได้ทุกที่ที่ต้องการชื่อ |
.<name> | เด็กที่มีเครื่องหมายจุด |
['<name>' (, '<name>')] | เด็กที่มีเครื่องหมายวงเล็บเหลี่ยมหรือเด็ก |
[<number> (, <number>)] | ดัชนีอาร์เรย์หรือดัชนี |
[start:end] | ตัวดำเนินการแบ่งอาร์เรย์ |
[?(<expression>)] | กรองนิพจน์ นิพจน์จะต้องประเมินเป็นค่าบูลีน |
สามารถเรียกใช้ฟังก์ชันได้ที่ส่วนท้ายของเส้นทาง - อินพุตของฟังก์ชันคือเอาต์พุตของนิพจน์เส้นทาง เอาต์พุตของฟังก์ชันถูกกำหนดโดยฟังก์ชันเอง
การทำงาน | คำอธิบาย | ประเภทเอาต์พุต |
---|---|---|
min() | ระบุค่าต่ำสุดของอาร์เรย์ตัวเลข | สองเท่า |
max() | ระบุค่าสูงสุดของอาร์เรย์ตัวเลข | สองเท่า |
avg() | ระบุค่าเฉลี่ยของอาร์เรย์ของตัวเลข | สองเท่า |
stddev() | ให้ค่าเบี่ยงเบนมาตรฐานของอาร์เรย์ตัวเลข | สองเท่า |
length() | ให้ความยาวของอาร์เรย์ | จำนวนเต็ม |
sum() | ระบุค่าผลรวมของอาร์เรย์ของตัวเลข | สองเท่า |
keys() | จัดเตรียมคีย์คุณสมบัติ (ทางเลือกสำหรับเทอร์มินัลทิลเดอ ~ ) | Set<E> |
concat(X) | จัดเตรียมเวอร์ชันที่ต่อกันของเอาต์พุตพาธกับไอเท็มใหม่ | เหมือนอินพุต |
append(X) | เพิ่มรายการลงในอาร์เรย์เอาต์พุตเส้นทาง json | เหมือนอินพุต |
first() | จัดเตรียมรายการแรกของอาร์เรย์ | ขึ้นอยู่กับอาร์เรย์ |
last() | ให้รายการสุดท้ายของอาร์เรย์ | ขึ้นอยู่กับอาร์เรย์ |
index(X) | ระบุรายการของอาร์เรย์ของดัชนี: X หาก X เป็นลบ ให้นำมาจากข้างหลัง | ขึ้นอยู่กับอาร์เรย์ |
ตัวกรองคือนิพจน์เชิงตรรกะที่ใช้ในการกรองอาร์เรย์ ตัวกรองทั่วไปจะเป็น [?(@.age > 18)]
โดยที่ @
แสดงถึงรายการปัจจุบันที่กำลังประมวลผล คุณสามารถสร้างตัวกรองที่ซับซ้อนมากขึ้นได้ด้วยตัวดำเนินการเชิงตรรกะ &&
และ ||
- ตัวอักษรสตริงจะต้องอยู่ในเครื่องหมายคำพูดเดี่ยวหรือคู่ ( [?(@.color == 'blue')]
หรือ [?(@.color == "blue")]
)
ผู้ดำเนินการ | คำอธิบาย |
---|---|
== | ซ้ายเท่ากับขวา (โปรดทราบว่า 1 ไม่เท่ากับ '1') |
!= | ซ้ายไม่เท่ากับขวา |
< | ซ้ายน้อยกว่าขวา |
<= | ซ้ายน้อยกว่าหรือเท่ากับขวา |
> | ซ้ายมากกว่าขวา |
>= | ซ้ายมากกว่าหรือเท่ากับขวา |
=~ | left ตรงกับนิพจน์ทั่วไป [?(@.name =~ /foo.*?/i)] |
in | left มีอยู่ในด้านขวา [?(@.size in ['S', 'M'])] |
nin | ซ้ายไม่มีอยู่ในด้านขวา |
subsetof | left เป็นเซตย่อยของ right [?(@.sizes subsetof ['S', 'M', 'L'])] |
anyof | left มีทางแยกกับ right [?(@.sizes anyof ['M', 'L'])] |
noneof | left ไม่มีจุดตัดกับ right [?(@.sizes noneof ['M', 'L'])] |
size | ขนาดด้านซ้าย (อาร์เรย์หรือสตริง) ควรตรงกับด้านขวา |
empty | ซ้าย (อาร์เรย์หรือสตริง) ควรว่างเปล่า |
ด้วย json
{
"store" : {
"book" : [
{
"category" : "reference" ,
"author" : "Nigel Rees" ,
"title" : "Sayings of the Century" ,
"price" : 8.95
} ,
{
"category" : "fiction" ,
"author" : "Evelyn Waugh" ,
"title" : "Sword of Honour" ,
"price" : 12.99
} ,
{
"category" : "fiction" ,
"author" : "Herman Melville" ,
"title" : "Moby Dick" ,
"isbn" : "0-553-21311-3" ,
"price" : 8.99
} ,
{
"category" : "fiction" ,
"author" : "J. R. R. Tolkien" ,
"title" : "The Lord of the Rings" ,
"isbn" : "0-395-19395-8" ,
"price" : 22.99
}
] ,
"bicycle" : {
"color" : "red" ,
"price" : 19.95
}
} ,
"expensive" : 10
}
JsonPath | ผลลัพธ์ |
---|---|
$.store.book[*].author | ผู้เขียนหนังสือทุกเล่ม |
$..author | ผู้เขียนทุกคน |
$.store.* | ทุกสิ่งทั้งหนังสือและจักรยาน |
$.store..price | ราคาของทุกอย่าง |
$..book[2] | หนังสือเล่มที่สาม |
$..book[-2] | เล่มที่สองต่อจากเล่มสุดท้าย |
$..book[0,1] | สองเล่มแรก |
$..book[:2] | หนังสือทั้งหมดตั้งแต่ดัชนี 0 (รวม) ถึงดัชนี 2 (ไม่รวม) |
$..book[1:2] | หนังสือทั้งหมดตั้งแต่ดัชนี 1 (รวม) ถึงดัชนี 2 (ไม่รวม) |
$..book[-2:] | สองเล่มสุดท้าย |
$..book[2:] | หนังสือทั้งหมดตั้งแต่ดัชนี 2 (รวม) ถึงล่าสุด |
$..book[?(@.isbn)] | หนังสือทุกเล่มที่มีหมายเลข ISBN |
$.store.book[?(@.price < 10)] | หนังสือทุกเล่มในร้านราคาถูกกว่า 10 |
$..book[?(@.price <= $['expensive'])] | หนังสือทุกเล่มในร้านที่ไม่ "แพง" |
$..book[?(@.author =~ /.*REES/i)] | หนังสือทั้งหมดที่ตรงกับ regex (ไม่สนใจตัวพิมพ์) |
$..* | ให้ฉันทุกสิ่ง |
$..book.length() | จำนวนหนังสือ |
วิธีที่ง่ายที่สุดในการใช้ JsonPath คือการใช้ static read API
String json = "..." ;
List < String > authors = JsonPath . read ( json , "$.store.book[*].author" );
หากคุณต้องการอ่านเพียงครั้งเดียวก็ไม่เป็นไร ในกรณีที่คุณต้องการอ่านเส้นทางอื่นด้วย นี่ไม่ใช่วิธีที่จะไปเนื่องจากเอกสารจะถูกแยกวิเคราะห์ทุกครั้งที่คุณเรียก JsonPath.read(...) เพื่อหลีกเลี่ยงปัญหา คุณสามารถแยกวิเคราะห์ json ก่อนได้
String json = "..." ;
Object document = Configuration . defaultConfiguration (). jsonProvider (). parse ( json );
String author0 = JsonPath . read ( document , "$.store.book[0].author" );
String author1 = JsonPath . read ( document , "$.store.book[1].author" );
JsonPath ยังมี API ได้อย่างคล่องแคล่วอีกด้วย นี่เป็นสิ่งที่ยืดหยุ่นที่สุด
String json = "..." ;
ReadContext ctx = JsonPath . parse ( json );
List < String > authorsOfBooksWithISBN = ctx . read ( "$.store.book[?(@.isbn)].author" );
List < Map < String , Object >> expensiveBooks = JsonPath
. using ( configuration )
. parse ( json )
. read ( "$.store.book[?(@.price > 10)]" , List . class );
เมื่อใช้ JsonPath ใน java สิ่งสำคัญคือต้องรู้ว่าคุณคาดหวังประเภทใดในผลลัพธ์ของคุณ JsonPath จะพยายามแปลงผลลัพธ์เป็นประเภทที่ผู้เรียกใช้คาดหวังโดยอัตโนมัติ
//Will throw an java.lang.ClassCastException
List < String > list = JsonPath . parse ( json ). read ( "$.store.book[0].author" );
//Works fine
String author = JsonPath . parse ( json ). read ( "$.store.book[0].author" );
เมื่อประเมินเส้นทาง คุณต้องเข้าใจแนวคิดว่าเมื่อใดเส้นทางนั้น definite
เส้นทาง indefinite
หากมี:
..
- ตัวดำเนินการสแกนเชิงลึก?(<expression>)
- นิพจน์[<number>, <number> (, <number>)]
- ดัชนีอาร์เรย์หลายรายการ เส้นทาง Indefinite
จะส่งคืนรายการเสมอ (ตามที่แสดงโดย JsonProvider ปัจจุบัน)
ตามค่าเริ่มต้น ตัวทำแผนที่วัตถุอย่างง่ายจะถูกจัดเตรียมโดย MappingProvider SPI สิ่งนี้ช่วยให้คุณสามารถระบุประเภทการส่งคืนที่คุณต้องการและ MappingProvider จะพยายามทำการแมป ในตัวอย่างด้านล่าง จะมีการสาธิตการแมประหว่าง Long
และ Date
String json = "{ " date_as_long " : 1411455611975}" ;
Date date = JsonPath . parse ( json ). read ( "$['date_as_long']" , Date . class );
หากคุณกำหนดค่า JsonPath ให้ใช้ JacksonMappingProvider
, GsonMappingProvider
หรือ JakartaJsonProvider
คุณสามารถแมปเอาต์พุต JsonPath ของคุณลงใน POJO ได้โดยตรง
Book book = JsonPath . parse ( json ). read ( "$.store.book[0]" , Book . class );
หากต้องการรับข้อมูลประเภทข้อมูลทั่วไปแบบเต็ม ให้ใช้ TypeRef
TypeRef < List < String >> typeRef = new TypeRef < List < String >>() {};
List < String > titles = JsonPath . parse ( JSON_DOCUMENT ). read ( "$.store.book[*].title" , typeRef );
มีสามวิธีในการสร้างเพรดิเคตตัวกรองใน JsonPath
เพรดิเคตอินไลน์เป็นเพรดิเคตที่กำหนดไว้ในพาธ
List < Map < String , Object >> books = JsonPath . parse ( json )
. read ( "$.store.book[?(@.price < 10)]" );
คุณสามารถใช้ &&
และ ||
เพื่อรวมภาคแสดงหลายรายการ [?(@.price < 10 && @.category == 'fiction')]
, [?(@.category == 'reference' || @.price > 10)]
คุณสามารถใช้ !
เพื่อลบล้างภาคแสดง [?(!(@.price < 10 && @.category == 'fiction'))]
เพรดิเคตสามารถสร้างขึ้นได้โดยใช้ Filter API ดังที่แสดงด้านล่าง:
import static com . jayway . jsonpath . JsonPath . parse ;
import static com . jayway . jsonpath . Criteria . where ;
import static com . jayway . jsonpath . Filter . filter ;
...
...
Filter cheapFictionFilter = filter (
where ( "category" ). is ( "fiction" ). and ( "price" ). lte ( 10D )
);
List < Map < String , Object >> books =
parse ( json ). read ( "$.store.book[?]" , cheapFictionFilter );
สังเกตเห็นตัวยึดตำแหน่ง ?
สำหรับตัวกรองในเส้นทาง เมื่อมีการระบุตัวกรองหลายตัว ตัวกรองเหล่านี้จะถูกนำมาใช้ตามลำดับโดยที่จำนวนตัวยึดตำแหน่งต้องตรงกับจำนวนตัวกรองที่ให้ไว้ คุณสามารถระบุตัวยึดตำแหน่งเพรดิเคตได้หลายรายการในการดำเนินการกรองครั้งเดียว [?, ?]
ทั้งสองเพรดิเคตต้องตรงกัน
ตัวกรองยังสามารถใช้ร่วมกับ 'OR' และ 'AND'
Filter fooOrBar = filter (
where ( "foo" ). exists ( true )). or ( where ( "bar" ). exists ( true )
);
Filter fooAndBar = filter (
where ( "foo" ). exists ( true )). and ( where ( "bar" ). exists ( true )
);
ตัวเลือกที่สามคือการใช้เพรดิเคตของคุณเอง
Predicate booksWithISBN = new Predicate () {
@ Override
public boolean apply ( PredicateContext ctx ) {
return ctx . item ( Map . class ). containsKey ( "isbn" );
}
};
List < Map < String , Object >> books =
reader . read ( "$.store.book[?].isbn" , List . class , booksWithISBN );
ในการใช้งาน Goessner นั้น JsonPath สามารถส่งคืน Path
หรือ Value
ได้ Value
เป็นค่าเริ่มต้นและสิ่งที่ตัวอย่างทั้งหมดข้างต้นส่งคืน หากคุณต้องการเส้นทางขององค์ประกอบที่แบบสอบถามของเรากำลังกดปุ่มนี้ คุณสามารถทำได้ด้วยตัวเลือก
Configuration conf = Configuration . builder ()
. options ( Option . AS_PATH_LIST ). build ();
List < String > pathList = using ( conf ). parse ( json ). read ( "$..author" );
assertThat ( pathList ). containsExactly (
"$['store']['book'][0]['author']" ,
"$['store']['book'][1]['author']" ,
"$['store']['book'][2]['author']" ,
"$['store']['book'][3]['author']" );
ห้องสมุดมีความเป็นไปได้ที่จะตั้งค่า
String newJson = JsonPath . parse ( json ). set ( "$['store']['book'][0]['author']" , "Paul" ). jsonString ();
เมื่อสร้างการกำหนดค่าของคุณ มีแฟล็กตัวเลือกสองสามตัวที่สามารถเปลี่ยนพฤติกรรมเริ่มต้นได้
DEFAULT_PATH_LEAF_TO_NULL
ตัวเลือกนี้ทำให้ JsonPath ส่งคืนค่าว่างสำหรับลีฟที่หายไป พิจารณา json ต่อไปนี้
[
{
"name" : "john" ,
"gender" : "male"
} ,
{
"name" : "ben"
}
]
Configuration conf = Configuration . defaultConfiguration ();
//Works fine
String gender0 = JsonPath . using ( conf ). parse ( json ). read ( "$[0]['gender']" );
//PathNotFoundException thrown
String gender1 = JsonPath . using ( conf ). parse ( json ). read ( "$[1]['gender']" );
Configuration conf2 = conf . addOptions ( Option . DEFAULT_PATH_LEAF_TO_NULL );
//Works fine
String gender0 = JsonPath . using ( conf2 ). parse ( json ). read ( "$[0]['gender']" );
//Works fine (null is returned)
String gender1 = JsonPath . using ( conf2 ). parse ( json ). read ( "$[1]['gender']" );
ALWAYS_RETURN_LIST
ตัวเลือกนี้จะกำหนดค่า JsonPath ให้ส่งคืนรายการแม้ว่าเส้นทางนั้น definite
ก็ตาม
Configuration conf = Configuration . defaultConfiguration ();
//ClassCastException thrown
List < String > genders0 = JsonPath . using ( conf ). parse ( json ). read ( "$[0]['gender']" );
Configuration conf2 = conf . addOptions ( Option . ALWAYS_RETURN_LIST );
//Works fine
List < String > genders0 = JsonPath . using ( conf2 ). parse ( json ). read ( "$[0]['gender']" );
SUPPRESS_EXCEPTIONS
ตัวเลือกนี้ทำให้แน่ใจว่าไม่มีการแพร่กระจายข้อยกเว้นจากการประเมินเส้นทาง เป็นไปตามกฎง่ายๆเหล่านี้:
ALWAYS_RETURN_LIST
รายการว่างจะถูกส่งกลับALWAYS_RETURN_LIST
ไม่มี ค่า null ส่งคืน REQUIRE_PROPERTIES
ตัวเลือกนี้จะกำหนดค่า JsonPath ให้ต้องการคุณสมบัติที่กำหนดไว้ในเส้นทางเมื่อมีการประเมินเส้นทาง indefinite
Configuration conf = Configuration . defaultConfiguration ();
//Works fine
List < String > genders = JsonPath . using ( conf ). parse ( json ). read ( "$[*]['gender']" );
Configuration conf2 = conf . addOptions ( Option . REQUIRE_PROPERTIES );
//PathNotFoundException thrown
List < String > genders = JsonPath . using ( conf2 ). parse ( json ). read ( "$[*]['gender']" );
JsonPath มาพร้อมกับ JsonProviders ที่แตกต่างกันห้ารายการ:
การเปลี่ยนค่าเริ่มต้นของการกำหนดค่าดังที่แสดงไว้ควรทำเฉพาะเมื่อแอปพลิเคชันของคุณกำลังเริ่มต้นเท่านั้น ไม่สนับสนุนการเปลี่ยนแปลงระหว่างรันไทม์อย่างยิ่ง โดยเฉพาะอย่างยิ่งในแอปพลิเคชันแบบมัลติเธรด
Configuration . setDefaults ( new Configuration . Defaults () {
private final JsonProvider jsonProvider = new JacksonJsonProvider ();
private final MappingProvider mappingProvider = new JacksonMappingProvider ();
@ Override
public JsonProvider jsonProvider () {
return jsonProvider ;
}
@ Override
public MappingProvider mappingProvider () {
return mappingProvider ;
}
@ Override
public Set < Option > options () {
return EnumSet . noneOf ( Option . class );
}
});
โปรดทราบว่า JacksonJsonProvider ต้องการ com.fasterxml.jackson.core:jackson-databind:2.4.5
และ GsonJsonProvider ต้องการ com.google.code.gson:gson:2.3.1
บน classpath ของคุณ
ผู้ให้บริการ Jakarta EE 9 JSON-P (JSR-342) และ JSON-B (JSR-367) คาดหวังอย่างน้อย Java 8 และต้องการการใช้งาน JSON API ที่เข้ากันได้ (เช่น Eclipse Glassfish และ Eclipse Yasson) บนคลาสพาธรันไทม์ของแอปพลิเคชัน การใช้งานดังกล่าวอาจจัดทำโดยคอนเทนเนอร์แอปพลิเคชัน Java EE โปรดทราบว่า Apache Johnzon ยังไม่เข้ากันได้กับคลาสพาธกับข้อกำหนด Jakarta EE 9 และหากเลือกผู้ให้บริการการแมป JSON-B ก็จะต้องกำหนดค่าและใช้งานผู้ให้บริการ JSON-P ด้วยเช่นกัน
ลักษณะเฉพาะประการหนึ่งของข้อกำหนดจำเพาะ Jakarta EE 9 สำหรับการประมวลผล JSON และการเชื่อมโยงข้อมูล (การแมป) คือความไม่เปลี่ยนแปลงของอาร์เรย์และอ็อบเจ็กต์ Json ทันทีที่มีการแยกวิเคราะห์หรือเขียนอย่างสมบูรณ์ เพื่อให้เป็นไปตามข้อกำหนด API แต่อนุญาตให้ JsonPath แก้ไขเอกสาร Json ผ่านการดำเนินการเพิ่ม ตั้งค่า/วาง แทนที่ และลบ JakartaJsonProvider
จะต้องเริ่มต้นด้วยอาร์กิวเมนต์ true
ที่เป็นตัวเลือก:
JsonProvider jsonProvider = new JakartaJsonProvider(true)
(เปิดใช้งานอาร์เรย์และอ็อบเจ็กต์ Json ที่ไม่แน่นอน)JsonProvider jsonProvider = new JakartaJsonProvider()
(ค่าเริ่มต้น การปฏิบัติตาม JSON-P API ที่เข้มงวด)รองรับการดำเนินการค้นหาและอ่านทั้งหมดด้วย JsonPath โดยไม่คำนึงถึงโหมดการเริ่มต้น โหมดเริ่มต้นยังต้องการหน่วยความจำน้อยลงและมีประสิทธิภาพมากกว่า
ใน JsonPath 2.1.0 มีการแนะนำ Cache SPI ใหม่ ซึ่งช่วยให้ผู้บริโภค API สามารถกำหนดค่าการแคชเส้นทางในลักษณะที่เหมาะสมกับความต้องการของพวกเขาได้ ต้องกำหนดค่าแคชก่อนที่จะมีการเข้าถึงเป็นครั้งแรก มิฉะนั้น JsonPathException จะถูกส่งออกไป JsonPath มาพร้อมกับการใช้งานแคชสองครั้ง
com.jayway.jsonpath.spi.cache.LRUCache
(ค่าเริ่มต้น ปลอดภัยสำหรับเธรด)com.jayway.jsonpath.spi.cache.NOOPCache
(ไม่มีแคช)หากคุณต้องการใช้แคชของคุณเอง API นั้นเรียบง่าย
CacheProvider . setCache ( new Cache () {
//Not thread safe simple cache
private Map < String , JsonPath > map = new HashMap < String , JsonPath >();
@ Override
public JsonPath get ( String key ) {
return map . get ( key );
}
@ Override
public void put ( String key , JsonPath jsonPath ) {
map . put ( key , jsonPath );
}
});