Query JSON documenten in de Terminal met GROQ

0
4

JSON documenten zijn overal tegenwoordig, maar ze zijn zelden gestructureerd, op de manier die u wilt. Ze bevatten vaak te veel gegevens, hebben vreemd naam velden, of plaats de gegevens in onnodige geneste objecten. Grafiek-Object Relationele Query ‘ s (GROQ) is een query language (zoals SQL, maar verschillende) die is ontworpen om rechtstreeks te werken op JSON-documenten. In principe kunt u het schrijven van query ‘ s kunt u snel de filter en vervolgens opnieuw formatteren van JSON-documenten om ze te krijgen in de meest gunstige vorm.

GROQ werd ontwikkeld door gezond Verstand.io (waar het is gebruikt als de primaire query language). Het is open source en het geeft ons een ingebouwde manieren om het te gebruiken in JavaScript en de command lijn op een JSON bron. Samen, voegen we GROQ naar de terminal toolkit, kunt u tijd besparen wanneer u het nodig om te stoeien met een aantal JSON-gegevens op een project.

Laten we installeren GROQ

Zoals de meeste dingen, moeten we installeren de GROQ CLI tool en kan dus met met npm (of Garen) in de terminal:

$ npm installeren -g groq-cli

Om te spelen met het, we moeten een JSON-bestand beschikbaar. We gebruiken curl download een voorbeeld van een dataset van todo gegevens:

$ curl -o-taken.json https://jsonplaceholder.typicode.com/todos

Laten we eens kijken naar een voorbeeld van een item in de gegevens:

{
“userId”: 1,
“id”: 1,
“titel”: “delectus aut autem”,
“completed”: false
},

Vrij eenvoudig. We hebben een gebruikers-ID, een tedoen-item-ID, een todo titel en een boolean die aangeeft of de tedoen-item voltooid is of niet.

Laten we nu een basic GROQ query: het vinden van alle voltooide tedoen-items, maar alleen de terugkeer van de todo titels en gebruikers-Id ‘ s. Het is OK om te copy/paste deze regel, want we lopen door wat het betekent in een bit.

$ cat todos.json | groq ‘*[voltooid == true]{titel, id}’ –vrij

De groq de command-line tools accepteren van een JSON-document van de standaard invoer. Dit werkt erg mooi met de Unix filosofie van “een ding te doen en te werken aan een tekst beek.” Om te lezen JSON uit een bestand gebruiken we het cat commando. Merk ook op dat groq zal de uitgang een minimale JSON op één lijn standaard, maar bij het passeren –mooi, we krijgen een mooi ingesprongen en gemarkeerd syntaxis.

Voor het opslaan van het resultaat, kunnen we pijp naar een nieuw bestand met >:

$ cat todos.json | groq ‘*[voltooid == true]{titel, id}’ > resultaat.json

De query zelf bestaat uit drie delen:

  • * verwijst naar de dataset (d.w.z. de gegevens in de JSON-bestand).
  • [voltooid == true] is een filter dat hiermee verwijdert u de items die zijn gemarkeerd als onvolledig.
  • {titel, id} is een projectie die ervoor zorgt dat de query alleen de terugkeer van de “titel” en “userId” eigenschappen.

Laten we een warming-up met enkele oefeningen

U waarschijnlijk niet denken dat je zou moeten sporten te krijgen door middel van dit bericht! Nou, het goede nieuws is dat we alleen de uitoefening van de geest met een paar dingen uit te proberen met GROQ voordat we in meer details.

  1. Wat gebeurt er als u [voltooid == true)] en/of {titel, userId}?
  2. Hoe kunt u de query wijzigen om alle todos door de gebruiker met ID 2?
  3. Hoe kunt u het wijzigen van de query te vinden onvoltooide todos door de gebruiker met ID 2?
  4. Wat gebeurt er als het filter in de oorspronkelijke query voorbeeld swaps plaatsen met de projectie?
  5. Hoe zou je het schrijven van een enkel commando (met buizen) dat de downloads van de JSON en processen met GROQ?

We zetten de antwoorden aan het eind van de post voor je referentie.

Het opvragen van de Nobel prijs winnaars

De todo gegevens is leuk voor een warming-up, maar laten we eerlijk zijn: Het is niet erg motiverend om te kijken naar een lijst die maakt gebruik van het latijn als tijdelijke aanduiding voor inhoud. Echter, de Nobelprijs heeft een dataset van alle eerdere laureaten beschikbaar voor gebruik in het openbaar.

Hier is een voorbeeld terug te keren:

{
“laureaten”: [
{
“id”: “1”,
“firstname”: “Wilhelm Conrad”,
“achternaam”: “Röntgen”,
“geboren”: “1845-03-27”,
“overleden”: “1923-02-10”,
“bornCountry”: “Pruisen (nu Duitsland)”,
“bornCountryCode”: “NL”,
“bornCity”: “Lennep (nu Remscheid)”,
“diedCountry”: “Duitsland”,
“diedCountryCode”: “NL”,
“diedCity”: “München”,
“geslacht”: “mannelijke”,
“prijzen”: […],
},
// …
]
}

Ah! Dit is veel interessanter! Laten we het downloaden van de dataset en de eerste naam van alle noorse laureaten. Hier gaan we gebruiken –output vlag voor het krullen van de gegevens op te slaan in een bestand.

$ curl –output laureaat.json http://api.nobelprize.org/v1/laureate.json
$ cat laureaat.json | groq ‘*.laureaten[bornCountryCode == “NEE”]{firstname}’ –vrij

Wat krijg je terug? Ik kreeg 12 noorse Nobelprijswinnaars. Niet slecht!

Merk op dat deze query niet de eerste query die we schreven. We hebben een extra .laureaten worden in dit ene. Wanneer we gebruikt * in de tedoen-dataset, het is de hele JSON-document dat is opgenomen in een matrix op het top-niveau van het todo-dataset. Aan de andere kant, de laureaat bestand maakt gebruik van een object op het hoogste niveau waar de lijst van de laureaten is opgeslagen in de “laureaten” eigenschap.

Om toegang te krijgen tot een specifiek item, kunnen we gebruik maken van de filter [0] en alleen de eerste naam. Dat moet ons vertellen wie de eerste noorse was om te winnen van een Nobelprijs.

$ cat laureaat.json | groq ‘*.laureaten[bornCountryCode == “NEE”]{firstname}[0]’ –vrij

// Terug object
{
“firstname”: “Ivar”
}
Meer oefeningen!

We zouden nalatig zijn niet te spelen met deze nieuwe dataset een beetje om te zien hoe de query ‘ s werk.

  1. Schrijf een query te vinden van alle Nobelprijswinnaars van uw eigen land.
  2. Schrijf een query te retourneren en de laatste noorse laureaat. Hint: -1 verwijst naar het laatste item.
  3. Wat gebeurt er als u probeert om het filter direct op de root object? *[bornCountryCode == “NEE”]?
  4. Wat is het verschil tussen *.laureaten[bornCountryCode == “NEE”][0] en *.laureaten[0][bornCountryCode == “NEE”]?

Net als de vorige keer, de antwoorden worden aan het einde van deze post.

Werken met filters

Nu weten we dat we in totaal zijn er 12 noorse Nobelprijswinnaars, hoe velen van hen waren geboren na 1950? Dat is geen probleem, het uitzoeken met GROQ:

$ cat laureaat.json | groq ‘*.laureaten[bornCountryCode == “GEEN” && geboren >= “1950-01-01”]{firstname}’ –vrij

// Sample return
[
{
“firstname”: “May-Britt”
},
{
“firstname”: “Edvard I.”
}
]

In feite, GROQ heeft een rijke set van operatoren die we kunnen gebruiken in een filter. We kunnen het vergelijken van getallen en strings met gelijke (==), niet gelijk aan (!=), groter dan (>), groter dan of gelijk aan ( >=), kleiner dan (<), en minder dan of gelijk aan (<=). Plus, vergelijkingen kunnen worden gecombineerd met AND (&&), OR (||) en NIET (!). De operator maakt het zelfs mogelijk om te controleren voor veel zaken tegelijk (bijv. bornCountryCode in [“GEEN”, “SE”, “DK”]). En de gedefinieerde functie kunt ons zien als een veld bestaat (bv. gedefinieerd(diedCountry)).

Nog meer oefeningen!

Je kent het wel: probeer te spelen met filters een beetje om te zien hoe ze werken met de dataset. De antwoorden staan aan het einde van de cursus.

  1. Schrijf een query retourneert woon-laureaten.
  2. Is er een verschil tussen de filters [bornCountryCode == “NEE”][geboren >= “1950-01-01”] en [bornCountryCode == “GEEN” && geboren >= “1950-01-01”]?
  3. Vindt u alle laureaten die een prijs won in 1973?

Werken met prognoses

De Nobelprijs voor de dataset scheidt de voornaam en de achternaam voor elke laureaat, maar wat als we willen deze combineren in één veld? Projecties in GROQ kan precies dat doen!

*.laureaten[bornCountryCode == “GEEN” && geboren >= “1950-01-01”]{
“naam”: voornaam + “” + achternaam,
geboren,
“prizeCount”: graaf(prijzen),
}

Het uitvoeren van deze query vertelt ons dat May-Britt Moser en Edvard Moser ontvangen een prijs (die was, in feite, dezelfde prijs):

[
{
“naam”: “May-Britt Moser”,
“geboren”: “1963-01-04”,
“prizeCount”: 1
},
{
“name”: “van Edvard I. Moser”,
“geboren”: “1962-04-27”,
“prizeCount”: 1
}
]

Wat is hier gebeurd? Goed, toen we schrijven een projectie in GROQ wat we echt schrijven is een JSON-object. Voorheen hadden we eenvoudige projecties (zoals {firstname}), maar dit is een snellere manier van schrijven, {“firstname”: firstname}. Met behulp van de uitgebreide object-syntaxis, kunnen we zowel de naam van de toetsen en het transformeren van de waarden.

GROQ heeft een rijke set van operators en functies voor het transformeren van gegevens, inclusief string concatenatie, rekenkundige operatoren (+, -, *, /, %, **), het tellen van arrays (count(prijzen)), en afronden van getallen (round(getal, <hoeveelheid decimalen>).

Oefeningen

Hopelijk bent u het krijgen van een goed gevoel voor de dingen op dit punt, maar hier zijn een aantal manieren om te oefenen met het werken met projecties:

  1. Vind alle laureaten die gewonnen hebben twee of meer prijzen.
  2. Vindt u hoe vele prijzen zijn gewonnen door de vrouwen.
  3. Het opmaken van een volledige toets die een combinatie van achternaam en voornaam in het resultaat.

Meer doen in een keer

Bekijk deze:

$ cat laureaat.json | groq –pretty’
{
“count”: count(*.laureaten),
“noren”: *.laureaten[bornCountryCode == “NEE”]{firstname},
}

Het resultaat:

{
“count”: 928,
“noren”: [
{
“firstname”: “Ivar”
},
{
“firstname”: “Lars”
},

]
}

Vang? Een GROQ query hoeft niet te starten met een *. In deze query maken we een JSON-object waar de waarden zijn resultaten van afzonderlijke query ‘ s. Dit biedt veel flexibiliteit in wat we kunnen maken met GROQ. Misschien wilt u het totaal aantal onvolledige todos, samen met een lijst van de vijf laatste. Of misschien wilt u de opdeling van de taken in twee afzonderlijke lijsten: één voor voltooide en één voor onvolledig. Of misschien moet u wikkel alles in een object, want dat is wat ander gereedschap/library/framework verwacht. Wat ook het geval, GROQ heeft u gedekt.

Laten we proberen nog een laatste oefening. Kan je project een object waar laureaten bevat een array met een afgerond percentage van het totaal aantal prijzen dat elke laureaat heeft uitgevoerd, de terugkeer van de laureaten’ voornaam? Probeer het uitvoeren van het totaal aantal uitgedeeld.

Samenvatting

Er is niet veel dat je moet leren voor het krijgen van een paar goede uit het gebruik van GROQ. Als u hebt gevolgd en de oefeningen, je bent op een grote pad om een GROQ guru. Natuurlijk is deze inleiding niet raakt aan de verschillende functies en aspecten van GROQ, dus voel je vrij om te verkennen van de specificatie en het project zelf op GitHub. En voel je vrij om uit te reiken naar het team van gezond Verstand.io als u vragen hebt over de gegevens die ruzie met GROQ.

Oefening antwoorden

Oefening 1
Vraag 1

Als u [voltooid == true] krijgt u alle todos, niet alleen degenen die zijn ingevuld. Als u {titel, userId} krijgt u alle eigenschappen.

Vraag 2
*[userId == 2]
Vraag 3
*[userId == 2 && voltooid == false] of *[userId == 2 && !voltooid]
Vraag 4

Als u de volgorde wijzigen van de filter en de projectie die je zal doen in de projectie voor het eerst en dan pas het filter toe. Dit betekent dat je filteren op een lijst van taken die bevatten alleen de titel en de userId en afgerond == true kan nooit waar zijn.

Vraag 5
krul https://jsonplaceholder.typicode.com/todos | groq ‘*[voltooid == true]{titel, id}’ > resultaat.json

Oefening 2
Vraag 1
*.laureaten[bornCountryCode == “STEEK-UW-LAND-HIER”]
Vraag 2
*.laureaten[bornCountryCode == “NEE”][-1]
Vraag 3

*[bornCountryCode == “NEE”] zullen proberen om te filteren op een object. Dit heeft geen enkele zin, dus krijg je null als het antwoord.

Vraag 4

*.laureaten[0][bornCountryCode == “NEE”] werkt niet als je zou denken. Dit vinden de eerste laureaat (die toevallig Wilhelm Conrad) en dan zal het proberen te “filteren” het object. Dit heeft geen zin dus het antwoord is null.

Oefening 3
Vraag 1
*.laureaten[overleden == “0000-00-00”]
Vraag 2

Er is geen verschil tussen de filters [bornCountryCode == “NEE”][geboren >= “1950-01-01”] en [bornCountryCode == “GEEN” && geboren >= “1950-01-01”]. De eerste is de filtering in twee “passen”, maar het eind resultaat is hetzelfde.

Vraag 3
*.laureaten[“1973” in de prijzen[].jaar]

Oefening 4
Vraag 1
*.laureaten[count(prijzen) >= 2]
Vraag 2
count(*.laureaten[geslacht == “vrouw”])
Vraag 3
*.laureaten{“fullname”: naam + “, ” + firstname}

Oefening 5
*.laureaten{“laureaten”: {firstname, “percentage”: round(count(prijzen) / count(*.laureaten[].de prijzen), 3) * 100}, “total”: count(*.laureaten[].prijzen)}