Introduktion til JsonPath

1. Oversigt

En af fordelene ved XML er tilgængeligheden af ​​behandling - inklusive XPath - som er defineret som en W3C-standard. For JSON er der kommet et lignende værktøj kaldet JSONPath.

Denne artikel vil give en introduktion til Jayway JsonPath, en Java-implementering af JSONPath-specifikationen. Den beskriver opsætning, syntaks, almindelige API'er og en demonstration af brugssager.

2. Opsætning

For at bruge JsonPath er vi simpelthen nødt til at inkludere en afhængighed i Maven pom:

 com.jayway.jsonpath json-path 2.4.0 

3. Syntaks

Følgende JSON-struktur vil blive brugt i dette afsnit til at demonstrere JsonPaths syntaks og API'er:

{"tool": {"jsonpath": {"creator": {"name": "Jayway Inc.", "location": ["Malmo", "San Francisco", "Helsingborg"]}}, "book ": [{" title ":" Begyndende JSON "," pris ": 49,99}, {" title ":" JSON på arbejdspladsen "," pris ": 29,99}]}

3.1. Notation

JsonPath bruger speciel notation til at repræsentere noder og deres forbindelser til tilstødende noder i en JsonPath-sti. Der er to stilarter med notation, nemlig prik og parentes.

Begge de følgende stier henviser til den samme node fra ovenstående JSON-dokument, som er det tredje element i Beliggenhed felt af skaberen node, det er et barn af jsonpath objekt, der hører til værktøj under rodnoden.

Med priknotation:

$ .tool.jsonpath.creator.location [2]

Med beslagnotation:

$ ['tool'] ['jsonpath'] ['creator'] ['location'] [2]

Dollartegnet ($) repræsenterer rodmedlemmerobjekt.

3.2. Operatører

Vi har flere nyttige operatører i JsonPath:

Rodknude ($): Dette symbol betegner rodelementet i en JSON-struktur, uanset om det er et objekt eller et array. Dets anvendelseseksempler blev inkluderet i det forrige underafsnit.

Nuværende knude (@): Repræsenterer den knude, der behandles, hovedsagelig brugt som en del af inputudtryk for predikater. Antag, at vi har at gøre med Bestil array i ovenstående JSON-dokument, udtrykket bog [? (@. pris == 49.99)] henviser til den første Bestil i den matrix.

Jokertegn (*): Udtrykker alle elementer inden for det angivne anvendelsesområde. For eksempel, Bestil[*] angiver alle noder inde i a Bestil array.

3.3. Funktioner og filtre

JsonPath har også funktioner, der kan bruges til slutningen af ​​en sti til at syntetisere stiens outputudtryk: min (), maks (), gennemsnit (), stddev (), længde ().

Endelig - vi har filtre; disse er boolske udtryk for at begrænse returnerede lister med noder til kun dem, som kaldemetoder har brug for.

Et par eksempler er ligestilling (==), matching af regulært udtryk (=~), inklusion (i), kontroller for tomhed (tom). Filtre bruges hovedsageligt til prædikater.

For en komplet liste og detaljerede forklaringer på forskellige operatører, funktioner og filtre henvises til JsonPath GitHub-projektet.

4. Operationer

Før vi går i drift, en hurtig side-note - dette afsnit bruger JSON-eksempelstrukturen, som vi definerede tidligere.

4.1. Adgang til dokumenter

JsonPath har en bekvem måde at få adgang til JSON-dokumenter, hvilket er gennem statisk Læs API'er:

 T JsonPath.read (String jsonString, String jsonPath, Predicate ... filtre);

Det Læs API'er kan arbejde med statiske flydende API'er for at give mere fleksibilitet:

 T JsonPath.parse (String jsonString) .læs (String jsonPath, Predicate ... filtre);

Andre overbelastede varianter af Læs kan bruges til forskellige typer JSON-kilder, herunder Objekt, InputStream, URLog Fil.

For at gøre tingene enkle inkluderer testen for denne del ikke prædikater i parameterlisten (tom varargs); prædikater vil blive diskuteret i senere underafsnit.

Lad os starte med at definere to eksempler på stier, vi kan arbejde på:

String jsonpathCreatorNamePath = "$ ['tool'] ['jsonpath'] ['creator'] ['name']"; String jsonpathCreatorLocationPath = "$ ['tool'] ['jsonpath'] ['creator'] ['location'] [*]";

Dernæst opretter vi en DocumentContext objekt ved at analysere den givne JSON-kilde jsonDataSourceString. Det nyoprettede objekt vil derefter blive brugt til at læse indhold ved hjælp af stierne defineret ovenfor:

DocumentContext jsonContext = JsonPath.parse (jsonDataSourceString); Streng jsonpathCreatorName = jsonContext.read (jsonpathCreatorNamePath); Liste jsonpathCreatorLocation = jsonContext.read (jsonpathCreatorLocationPath);

Den første Læs API returnerer en Snor indeholdende navnet på JsonPath-skaberen, mens den anden returnerer en liste med dens adresser. Og vi bruger JUnit Hævde API for at bekræfte metoderne fungerer som forventet:

assertEquals ("Jayway Inc.", jsonpathCreatorName); assertThat (jsonpathCreatorLocation.toString (), indeholderString ("Malmø")); assertThat (jsonpathCreatorLocation.toString (), indeholderString ("San Francisco")); assertThat (jsonpathCreatorLocation.toString (), indeholderString ("Helsingborg"));

4.2. Predikater

Nu hvor vi er færdige med det grundlæggende, lad os definere et nyt JSON-eksempel til at arbejde på og illustrere oprettelse og brug af prædikater:

{"book": [{"title": "Begyndende JSON", "author": "Ben Smith", "price": 49.99}, {"title": "JSON at Work", "author": "Tom Marrs "," price ": 29.99}, {" title ":" Lær JSON på en DAG "," author ":" Acodemy "," price ": 8.99}, {" title ":" JSON: Spørgsmål og svar ", "author": "George Duckett", "price": 6.00}], "price range": {"cheap": 10.00, "medium": 20.00}}

Predikater bestemmer sande eller falske inputværdier for filtre til at indsnævre returnerede lister til kun matchede objekter eller arrays. EN Prædikat kan let integreres i en Filter ved at bruge som argument for dens statiske fabriksmetode. Det ønskede indhold kan derefter læses ud af en JSON-streng ved hjælp af det Filter:

Filtrer expensiveFilter = Filter.filter (Criteria.where ("pris"). Gt (20.00)); Liste dyrt = JsonPath.parse (jsonDataSourceString) .read ("$ ['book'] [?]", expensiveFilter); predicateUsageAssertionHelper (dyrt);

Vi kan også definere vores tilpassede Prædikat og bruge det som et argument for Læs API:

Predicate expensivePredicate = new Predicate () {public boolean apply (PredicateContext context) {String value = context.item (Map.class) .get ("price"). ToString (); returner Float.valueOf (værdi)> 20,00; }}; Liste dyrt = JsonPath.parse (jsonDataSourceString) .read ("$ ['book'] [?]", expensivePredicate); predicateUsageAssertionHelper (dyrt);

Endelig kan et prædikat anvendes direkte på Læs API uden oprettelse af nogen objekter, der kaldes inline-prædikat:

Liste dyrt = JsonPath.parse (jsonDataSourceString) .read ("$ ['bog'] [? (@ ['pris']> $ ['prisinterval'] ['medium'])]"); predicateUsageAssertionHelper (dyrt);

Alle de tre af Prædikat eksemplerne ovenfor er verificeret ved hjælp af følgende påstandshjælpemetode:

private void predicateUsageAssertionHelper (List predicate) {assertThat (predicate.toString (), containString ("Begyndende JSON")); assertThat (predicate.toString (), containString ("JSON at Work")); assertThat (predicate.toString (), ikke (containString ("Lær JSON på en DAG")); assertThat (predicate.toString (), ikke (containString ("JSON: Spørgsmål og svar"))); }

5. Konfiguration

5.1. Muligheder

Jayway JsonPath giver flere muligheder for at tilpasse standardkonfigurationen:

  • Option.AS_PATH_LIST: Returnerer stier for evalueringshits i stedet for deres værdier.
  • Valgmulighed.DEFAULT_PATH_LEAF_TO_NULL: Returnerer null for manglende blade.
  • Option.ALWAYS_RETURN_LIST: Returnerer en liste, selv når stien er bestemt.
  • Option.SUPPRESS_EXCEPTIONS: Sørger for, at der ikke spredes nogen undtagelser fra stivurderingen.
  • Option.REQUIRE_PROPERTIES: Kræver egenskaber defineret i stien, når en ubestemt sti evalueres.

Sådan gør du Mulighed påføres fra bunden:

Konfigurationskonfiguration = Configuration.builder (). Muligheder (Option.). Build ();

og hvordan man tilføjer det til en eksisterende konfiguration:

Konfiguration newConfiguration = configuration.addOptions (Option.);

5.2. SPI'er

JsonPaths standardkonfiguration ved hjælp af Mulighed skal være nok til de fleste opgaver. Brugere med mere komplekse brugssager kan dog ændre JsonPaths adfærd i henhold til deres specifikke krav ved hjælp af tre forskellige SPI'er:

  • JsonProvider SPI: Lad os ændre måder, JsonPath analyserer og håndterer JSON-dokumenter på
  • MappingProvider SPI: Gør det muligt at tilpasse bindinger mellem nodeværdier og returnerede objekttyper
  • CacheProvider SPI: Justerer de manerer, som stier caches, hvilket kan hjælpe med at øge ydeevnen

6. Et eksempel på brugssager

Nu hvor vi har en god forståelse af den funktionalitet, som JsonPath kan bruges til - lad os se på et eksempel.

Dette afsnit illustrerer håndtering af JSON-data, der returneres fra en webservice - antag, at vi har en filminformationsservice, som returnerer følgende struktur:

[{"id": 1, "title": "Casino Royale", "director": "Martin Campbell", "starring": ["Daniel Craig", "Eva Green"], "desc": "Enogtyve James Bond-film "," udgivelsesdato ": 1163466000000," billetkontor ": 594275385}, {" id ": 2," title ":" Quantum of Solace "," instruktør ":" Marc Forster "," medvirkende ": ["Daniel Craig", "Olga Kurylenko"], "desc": "Twenty-second James Bond film", "release date": 1225242000000, "box office": 591692078}, {"id": 3, "title" : "Skyfall", "director": "Sam Mendes", "starring": ["Daniel Craig", "Naomie Harris"], "desc": "Twenty-third James Bond movie", "release date": 1350954000000, "box office": 1110526981}, {"id": 4, "title": "Spectre", "director": "Sam Mendes", "starring": ["Daniel Craig", "Lea Seydoux"], "desc ":" 24ogtyve James Bond-film "," udgivelsesdato ": 1445821200000," billetkontor ": 879376275}]

Hvor værdien af udgivelses dato felt er varighed siden epoken i millisekunder og billetkontor er indtægter fra en film i biografen i amerikanske dollars.

Vi skal håndtere fem forskellige arbejdsscenarier relateret til GET-anmodninger, forudsat at ovenstående JSON-hierarki er blevet ekstraheret og lagret i en Snor variabel navngivet jsonString.

6.1. At få objektdata givet ID'er

I dette brugssag beder en klient detaljeret information om en bestemt film ved at give serveren det nøjagtige id af den ene. Dette eksempel viser, hvordan serveren ser efter anmodede data, inden han vender tilbage til klienten.

Sig, at vi skal finde en post med id svarende til 2. Nedenfor er, hvordan processen implementeres og testes.

Det første trin er at afhente det korrekte dataobjekt:

Objekt dataObject = JsonPath.parse (jsonString) .læs ("$ [? (@. Id == 2)]"); Streng dataString = dataObject.toString ();

JUnit Hævde API bekræfter eksistensen af ​​flere felter:

assertThat (dataString, containString ("2")); assertThat (dataString, containString ("Quantum of Solace")); assertThat (dataString, containString ("Twenty-second James Bond film"));

6.2. Få filmtitlen med hovedrollen

Lad os sige, at vi vil lede efter en film med en skuespillerinde, der hedder Eva Green. Serveren skal vende tilbage titel af filmen det Eva Green indgår i medvirkende array.

Den efterfølgende test illustrerer, hvordan man gør det, og validerer det returnerede resultat:

@Test offentligt ugyldigt givenStarring_whenRequestingMovieTitle_thenSucceed () {List dataList = JsonPath.parse (jsonString) .read ("$ [? ('Eva Green' i @ ['starring'])]"); String title = (String) dataList.get (0) .get ("title"); assertEquals ("Casino Royale", titel); }

6.3. Beregning af den samlede omsætning

Dette scenario bruger en kaldet JsonPath-funktion længde () for at finde ud af antallet af filmoptegnelser, for at beregne den samlede indtægt for alle filmene. Implementeringen og testningen demonstreres som følger:

@Test offentlig ugyldighed givenCompleteStructure_whenCalculatingTotalRevenue_thenSucceed () {DocumentContext context = JsonPath.parse (jsonString); int længde = context.read ("$. længde ()"); lang omsætning = 0; for (int i = 0; i <længde; i ++) {indtægter + = context.read ("$ [" + i + "] ['billetkontor']", Long.class); } assertEquals (594275385L + 591692078L + 1110526981L + 879376275L, indtægter); }

6.4. Højeste indtægtsfilm

Denne brugssag eksemplificerer brugen af ​​en ikke-standard JsonPath-konfigurationsindstilling, nemlig Option.AS_PATH_LIST, for at finde ud af filmen med den højeste indtjening. De særlige trin er beskrevet nedenfor.

Først skal vi udtrække en liste over alle filmens billetindtægter og derefter konvertere den til en matrix til sortering:

DocumentContext context = JsonPath.parse (jsonString); Liste indtægtsliste = context.read ("$ [*] ['billetkontor']"); Heltal [] provenArray = incomeList.toArray (nyt heltal [0]); Arrays.sort (indtægterArray);

Det højeste indtægter variabel kan let hentes fra indtægterArray sorteret matrix og derefter brugt til at finde ud af stien til filmoptagelsen med den højeste indtjening:

int højesteRevenue = indtægterArray [indtægterArray.længde - 1]; Configuration pathConfiguration = Configuration.builder (). Valgmuligheder (Option.AS_PATH_LIST) .build (); List pathList = JsonPath.using (pathConfiguration) .parse (jsonString) .read ("$ [? (@ ['Box office'] ==" + højeste indtægter + ")]");

Baseret på den beregnede sti, titel af den tilsvarende film kan bestemmes og returneres:

KortdataRecord = context.read (pathList.get (0)); String title = dataRecord.get ("title");

Hele processen er verificeret af Hævde API:

assertEquals ("Skyfall", titel);

6.5. Seneste film af en instruktør

Dette eksempel illustrerer måden at finde ud af den sidste film instrueret af en instruktør, der hedder Sam Mendes.

Til at begynde med en liste over alle film instrueret af Sam Mendes er oprettet:

DocumentContext context = JsonPath.parse (jsonString); Liste dataList = context.read ("$ [? (@. direktør == 'Sam Mendes')]");

Denne liste bruges til udvinding af udgivelsesdatoer. Disse datoer gemmes i en matrix og sorteres derefter:

Liste dateList = ny ArrayList (); for (Map item: dataList) {Object date = item.get ("release date"); dateList.add (dato); } Lang [] dateArray = dateList.toArray (ny lang [0]); Arrays.sort (dateArray);

Det lastestTime variabel, som er det sidste element i det sorterede array, bruges i kombination med direktør feltets værdi for at bestemme titel af den anmodede film:

long latestTime = dateArray [dateArray.length - 1]; Liste finalDataList = context.read ("$ [? (@ ['director'] == 'Sam Mendes' && @ ['release date'] ==" + latestTime + ")])); String title = (String) finalDataList.get (0) .get ("title");

Følgende påstand beviste, at alt fungerer som forventet:

assertEquals ("Spectre", titel);

7. Konklusion

Denne vejledning har dækket grundlæggende funktioner i Jayway JsonPath - et kraftfuldt værktøj til at krydse og analysere JSON-dokumenter.

Selvom JsonPath har nogle ulemper, såsom mangel på operatører til at nå forældre- eller søskendeknuder, kan det være meget nyttigt i mange scenarier.

Implementeringen af ​​alle disse eksempler og kodestykker findes i en GitHub-projekt.