Inleiding tot JsonPath

1. Overzicht

Een van de voordelen van XML is de beschikbaarheid van verwerking - inclusief XPath - die is gedefinieerd als een W3C-standaard. Voor JSON is een vergelijkbare tool genaamd JSONPath ontstaan.

Dit artikel geeft een inleiding tot Jayway JsonPath, een Java-implementatie van de JSONPath-specificatie. Het beschrijft de installatie, syntaxis, veelgebruikte API's en een demonstratie van gebruiksscenario's.

2. Installatie

Om JsonPath te gebruiken, hoeven we alleen maar een afhankelijkheid in de Maven pom op te nemen:

 com.jayway.jsonpath json-path 2.4.0 

3. Syntaxis

De volgende JSON-structuur wordt in deze sectie gebruikt om de syntaxis en API's van JsonPath te demonstreren:

{"tool": {"jsonpath": {"creator": {"name": "Jayway Inc.", "location": ["Malmo", "San Francisco", "Helsingborg"]}}}, "boek ": [{" title ":" Beginning JSON "," price ": 49,99}, {" title ":" JSON at Work "," price ": 29,99}]}

3.1. Notatie

JsonPath gebruikt een speciale notatie om knooppunten en hun verbindingen met aangrenzende knooppunten in een JsonPath-pad weer te geven. Er zijn twee notatiestijlen, namelijk punt en haakje.

Beide volgende paden verwijzen naar hetzelfde knooppunt uit het bovenstaande JSON-document, het derde element binnen het plaats gebied van Schepper knooppunt, dat is een kind van de jsonpath object behorend tot gereedschap onder het hoofdknooppunt.

Met puntnotatie:

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

Met beugel-notatie:

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

Het dollarteken ($) staat voor het rootlid-object.

3.2. Operatoren

We hebben verschillende handige operators in JsonPath:

Hoofdknooppunt ($): Dit symbool geeft het rootlid van een JSON-structuur aan, ongeacht of het een object of array is. De gebruiksvoorbeelden zijn opgenomen in de vorige onderafdeling.

Huidig ​​knooppunt (@): Vertegenwoordigt het knooppunt dat wordt verwerkt, meestal gebruikt als onderdeel van invoeruitdrukkingen voor predikaten. Stel dat we te maken hebben met boek array in het bovenstaande JSON-document, de uitdrukking boek [? (@. price == 49,99)] verwijst naar de eerste boek in die reeks.

Jokerteken (*): Drukt alle elementen binnen het opgegeven bereik uit. Bijvoorbeeld, boek[*] geeft alle knooppunten binnen een boek array.

3.3. Functies en filters

JsonPath heeft ook functies die aan het einde van een pad kunnen worden gebruikt om de uitvoeruitdrukkingen van dat pad samen te stellen: min (), max (), gem. (), stddev (), lengte().

Eindelijk - we hebben filters; dit zijn booleaanse expressies om geretourneerde lijsten met knooppunten te beperken tot alleen die welke aanroepmethoden nodig hebben.

Een paar voorbeelden zijn gelijkheid (==), reguliere expressie-overeenkomsten (=~), inclusie (in), controleer op leegte (leeg). Filters worden voornamelijk gebruikt voor predikaten.

Raadpleeg het JsonPath GitHub-project voor een volledige lijst en gedetailleerde uitleg van verschillende operators, functies en filters.

4. Operaties

Voordat we met bewerkingen beginnen, een korte kanttekening: deze sectie maakt gebruik van de JSON-voorbeeldstructuur die we eerder hebben gedefinieerd.

4.1. Toegang tot documenten

JsonPath heeft een handige manier om toegang te krijgen tot JSON-documenten, namelijk via statische lezen API's:

 T JsonPath.read (String jsonString, String jsonPath, Predicaat ... filters);

De lezen API's kunnen werken met statische, vloeiende API's om meer flexibiliteit te bieden:

 T JsonPath.parse (String jsonString) .read (String jsonPath, Predicaat ... filters);

Andere overbelaste varianten van lezen kan worden gebruikt voor verschillende soorten JSON-bronnen, inclusief Voorwerp, InputStream, URL, en het dossier.

Om het eenvoudig te maken, bevat de test voor dit onderdeel geen predikaten in de parameterlijst (leeg varargs); predikaten zal in latere onderafdelingen worden besproken.

Laten we beginnen met het definiëren van twee voorbeeldpaden om aan te werken:

String jsonpathCreatorNamePath = "$ ['tool'] ['jsonpath'] ['creator'] ['naam']"; String jsonpathCreatorLocationPath = "$ ['tool'] ['jsonpath'] ['creator'] ['locatie'] [*]";

Vervolgens maken we een DocumentContext object door de opgegeven JSON-bron te ontleden jsonDataSourceString. Het nieuw gemaakte object wordt vervolgens gebruikt om inhoud te lezen met behulp van de hierboven gedefinieerde paden:

DocumentContext jsonContext = JsonPath.parse (jsonDataSourceString); String jsonpathCreatorName = jsonContext.read (jsonpathCreatorNamePath); Lijst jsonpathCreatorLocation = jsonContext.read (jsonpathCreatorLocationPath);

De eerste lezen API retourneert een Draad met de naam van de JsonPath-maker, terwijl de tweede een lijst met zijn adressen retourneert. En we zullen de JUnit gebruiken Beweren API om te bevestigen dat de methoden werken zoals verwacht:

assertEquals ("Jayway Inc.", jsonpathCreatorName); assertThat (jsonpathCreatorLocation.toString (), containsString ("Malmo")); assertThat (jsonpathCreatorLocation.toString (), containsString ("San Francisco")); assertThat (jsonpathCreatorLocation.toString (), containsString ("Helsingborg"));

4.2. Predikaten

Nu we klaar zijn met de basis, laten we een nieuw JSON-voorbeeld definiëren om aan te werken en het maken en gebruiken van predikaten illustreren:

{"book": [{"title": "Beginning JSON", "author": "Ben Smith", "price": 49,99}, {"title": "JSON at Work", "author": "Tom Marrs "," price ": 29.99}, {" title ":" Leer JSON in een DAG "," author ":" Acodemy "," price ": 8,99}, {" title ":" JSON: Vragen en antwoorden ", "author": "George Duckett", "price": 6.00}], "price range": {"cheap": 10.00, "medium": 20.00}}

Predikaten bepalen waar of onwaar invoerwaarden voor filters om geretourneerde lijsten te verfijnen tot alleen overeenkomende objecten of arrays. EEN Predikaat kan gemakkelijk worden geïntegreerd in een Filter door als argument te gebruiken voor de statische fabrieksmethode. De gevraagde inhoud kan vervolgens worden uitgelezen uit een JSON-string Filter:

Filter dureFilter = Filter.filter (Criteria.where ("prijs"). Gt (20.00)); Lijst dure = JsonPath.parse (jsonDataSourceString) .read ("$ ['boek'] [?]", dureFilter); predicateUsageAssertionHelper (duur);

We kunnen ook onze maatwerk definiëren Predikaat en gebruik het als een argument voor de lezen API:

Predicaat durePredicate = nieuw Predicaat () {openbare boolean toepassen (PredicateContext-context) {Stringwaarde = context.item (Map.class) .get ("prijs"). ToString (); retourneer Float.valueOf (waarde)> 20,00; }}; Lijst dure = JsonPath.parse (jsonDataSourceString) .read ("$ ['boek'] [?]", durePredicate); predicateUsageAssertionHelper (duur);

Ten slotte kan een predikaat rechtstreeks worden toegepast lezen API zonder het maken van objecten, dit wordt inline-predikaat genoemd:

Lijst dure = JsonPath.parse (jsonDataSourceString) .read ("$ ['boek'] [? (@ ['prijs']> $ ['prijsbereik'] ['gemiddeld'])]"); predicateUsageAssertionHelper (duur);

Alle drie de Predikaat bovenstaande voorbeelden worden geverifieerd met behulp van de volgende assertion helper-methode:

private void predicateUsageAssertionHelper (lijstpredikaat) {assertThat (predicate.toString (), containsString ("Beginning JSON")); assertThat (predicate.toString (), containsString ("JSON at Work")); assertThat (predicate.toString (), not (containsString ("Leer JSON in een DAG"))); assertThat (predicate.toString (), not (containsString ("JSON: vragen en antwoorden"))); }

5. Configuratie

5.1. Opties

Jayway JsonPath biedt verschillende opties om de standaardconfiguratie aan te passen:

  • Optie.AS_PATH_LIST: Retourneert paden van de evaluatiehits in plaats van hun waarden.
  • Optie.DEFAULT_PATH_LEAF_TO_NULL: Retourneert null voor ontbrekende bladeren.
  • Optie.ALWAYS_RETURN_LIST: Retourneert een lijst, zelfs als het pad definitief is.
  • Optie.SUPPRESS_EXCEPTIONS: Zorgt ervoor dat er geen uitzonderingen worden gepropageerd uit padevaluatie.
  • Optie.REQUIRE_PROPERTIES: Vereist eigenschappen die in het pad zijn gedefinieerd wanneer een onbepaald pad wordt geëvalueerd.

Hier is hoe Keuze wordt vanaf nul toegepast:

Configuratieconfiguratie = Configuration.builder (). Options (Option.). Build ();

en hoe u het aan een bestaande configuratie toevoegt:

Configuratie newConfiguration = configuration.addOptions (Option.);

5.2. SPI's

De standaardconfiguratie van JsonPath met behulp van Keuze zou voldoende moeten zijn voor de meeste taken. Gebruikers met complexere use-cases kunnen het gedrag van JsonPath echter aanpassen aan hun specifieke vereisten - met behulp van drie verschillende SPI's:

  • JsonProvider SPI: Hiermee kunnen we de manieren wijzigen waarop JsonPath JSON-documenten parseert en verwerkt
  • MappingProvider SPI: maakt aanpassing van bindingen tussen knooppuntwaarden en geretourneerde objecttypen mogelijk
  • CacheProvider SPI: past de manieren aan waarop paden in het cachegeheugen worden opgeslagen, wat kan helpen om de prestaties te verbeteren

6. Een voorbeeld van gebruiksgevallen

Nu we een goed begrip hebben van de functionaliteit waarvoor JsonPath kan worden gebruikt, laten we eens kijken naar een voorbeeld.

Deze sectie illustreert het omgaan met JSON-gegevens die worden geretourneerd door een webservice - neem aan dat we een filminformatiedienst hebben, die de volgende structuur retourneert:

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

Waar de waarde van Publicatiedatum veld is de duur sinds de Epoch in milliseconden en theaterkassa is de opbrengst van een film in de bioscoop in Amerikaanse dollars.

We gaan vijf verschillende werkscenario's behandelen met betrekking tot GET-verzoeken, ervan uitgaande dat de bovenstaande JSON-hiërarchie is geëxtraheerd en opgeslagen in een Draad variabele met de naam jsonString.

6.1. Gegeven ID's van objectgegevens ophalen

In dit geval vraagt ​​een klant om gedetailleerde informatie over een specifieke film door de server het exacte te verstrekken ID kaart van die. Dit voorbeeld laat zien hoe de server naar opgevraagde gegevens zoekt voordat hij terugkeert naar de client.

Stel dat we een record moeten vinden met ID kaart gelijk aan 2. Hieronder ziet u hoe het proces wordt geïmplementeerd en getest.

De eerste stap is om het juiste data-object op te halen:

Object dataObject = JsonPath.parse (jsonString) .read ("$ [? (@. Id == 2)]"); String dataString = dataObject.toString ();

De JUnit Beweren API bevestigt het bestaan ​​van verschillende velden:

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

6.2. De filmtitel krijgen met de hoofdrol

Laten we zeggen dat we op zoek willen gaan naar een film met een actrice die heet Eva Green. De server moet terugkeren titel van de film dat Eva Green is opgenomen in de met in de hoofdrol array.

De volgende test laat zien hoe je dat doet en valideert het geretourneerde resultaat:

@Test openbare ongeldig gegevenStarring_whenRequestingMovieTitle_thenSucceed () {Lijst dataList = JsonPath.parse (jsonString) .read ("$ [? ('Eva Green' in @ ['starring'])]"); String title = (String) dataList.get (0) .get ("title"); assertEquals ("Casino Royale", titel); }

6.3. Berekening van de totale inkomsten

Dit scenario maakt gebruik van een JsonPath-functie met de naam lengte() om het aantal filmrecords te berekenen, om de totale opbrengst van alle films te berekenen. De implementatie en het testen worden als volgt gedemonstreerd:

@Test openbare leegte gegevenCompleteStructure_whenCalculatingTotalRevenue_thenSucceed () {DocumentContext context = JsonPath.parse (jsonString); int length = context.read ("$. length ()"); lange opbrengst = 0; for (int i = 0; i <length; i ++) {omzet + = context.read ("$ [" + i + "] ['box office']", Long.class); } assertEquals (594275385L + 591692078L + 1110526981L + 879376275L, inkomsten); }

6.4. Film met de hoogste inkomsten

Deze use case is een voorbeeld van het gebruik van een niet-standaard JsonPath-configuratieoptie, namelijk Optie.AS_PATH_LIST, om de film met de hoogste inkomsten te ontdekken. De specifieke stappen worden hieronder beschreven.

Eerst moeten we een lijst met de inkomsten van alle films uitpakken en deze vervolgens converteren naar een array om te sorteren:

DocumentContext context = JsonPath.parse (jsonString); List incomeList = context.read ("$ [*] ['box office']"); Geheel getal [] inkomstenArray = inkomstenList.toArray (nieuw geheel getal [0]); Arrays.sort (inkomstenArray);

De hoogste omzet variabele kan gemakkelijk worden opgehaald uit de inkomstenArray gesorteerde array, vervolgens gebruikt om het pad naar het filmrecord met de hoogste opbrengst te bepalen:

int hoogsteRevenue = incomeArray [incomeArray.length - 1]; Configuratie pathConfiguration = Configuration.builder (). Opties (Option.AS_PATH_LIST) .build (); Lijst pathList = JsonPath.using (pathConfiguration) .parse (jsonString) .read ("$ [? (@ ['Box office'] ==" + hoogste omzet + ")]");

Op basis van dat berekende pad, titel van de bijbehorende film kan worden bepaald en geretourneerd:

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

Het hele proces wordt geverifieerd door de Beweren API:

assertEquals ("Skyfall", titel);

6.5. Laatste film van een regisseur

Dit voorbeeld illustreert de manier om de laatste film te achterhalen, geregisseerd door een genoemde regisseur Sam Mendes.

Om te beginnen een lijst met alle films geregisseerd door Sam Mendes is gecreëerd:

DocumentContext context = JsonPath.parse (jsonString); Lijst dataList = context.read ("$ [? (@. director == 'Sam Mendes')]");

Die lijst wordt gebruikt voor het opvragen van releasedata. Die datums worden in een array opgeslagen en vervolgens gesorteerd:

Lijst dateList = nieuwe ArrayList (); voor (Kaartitem: dataList) {Object date = item.get ("releasedatum"); dateList.add (datum); } Long [] dateArray = dateList.toArray (new Long [0]); Arrays.sort (dateArray);

De lastestTime variabele, het laatste element van de gesorteerde array, wordt gebruikt in combinatie met de regisseur veldwaarde om de titel van de aangevraagde film:

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

De volgende bewering bewees dat alles werkt zoals verwacht:

assertEquals ("Spectre", titel);

7. Conclusie

Deze tutorial behandelt de fundamentele kenmerken van Jayway JsonPath - een krachtig hulpmiddel om JSON-documenten te doorlopen en te parseren.

Hoewel JsonPath enkele nadelen heeft, zoals een gebrek aan operators voor het bereiken van bovenliggende knooppunten of knooppunten op hetzelfde niveau, kan het in veel scenario's zeer nuttig zijn.

De implementatie van al deze voorbeelden en codefragmenten is te vinden in een GitHub-project.