Programming teenager

Blog

Het ontwikkelen van analytische apps is een gewaagde nieuwe richting voor productteams. De Toolbox is waar we praktische tips, tips, trucs en succesverhalen over ontwikkeling bespreken om u te helpen de toekomst van analytics op te bouwen en uw gebruikers de inzichten en acties te geven die ze nodig hebben.

JSON is tegenwoordig waarschijnlijk het meest gebruikte formaat voor open data-uitwisseling. Hoewel het is ontworpen als een lichtgewicht JavaScript-objectachtige indeling, kunnen JSON-documenten behoorlijk groot worden, vooral als ze diep geneste objecten en arrays bevatten.

Er is een reële behoefte om algemene verwerkingsquery’s op JSON-documenten uit te kunnen voeren voor het filteren, vormgeven en transformeren van JSON-gegevens.

Sommige documentdatabases zoals MongoDB en PostgreSQL hebben hun eigen querytaal waarmee complexe query’s op JSON kunnen worden uitgevoerd, maar dat is meestal niet relevant wanneer onze JSON-gegevens buiten de context van databaserecords vallen (hoewel u wel MingoJS, een JavaScript-implementatie van MongoDB-querytaal).

Wat als we gebruikers ingebouwde ondersteuning voor dergelijke vragen zouden kunnen bieden?

De use case:

  1. Uw software voert wat JSON uit en u wilt de gebruiker een gemakkelijke, gestandaardiseerde manier bieden om de gegevens te filteren of te manipuleren (voorbeelden: API’s, SDK’s, CLI’s, online speeltuinen).
  2. Uw software neemt JSON als invoer en u wilt de gegevens programmatisch filteren of manipuleren op een uniforme en herbruikbare manier. (Denk aan JSON-documenten met arrays van objecten die meer arrays en objecten nesten.) Ja, je kunt je eigen logica schrijven met behulp van kaart-, filter- en reduceerfuncties, maar misschien is er een betere, meer declaratieve manier.

Dit bericht richt zich voornamelijk alleen op de eerste use-case, waar u maar wilt geef uw gebruikers een ingebouwde optie om de output te verwerken (maar in feite zijn de twee gevallen vergelijkbaar).

Kandidaten voor JSON-querytalen

XML heeft XPath voor het opvragen en doorlopen van XML-knooppunten. Wat is het equivalent voor JSON?

JSONPath

JSONPath – “XPath voor JSON” – is de eerste zoektaal dat in je opkomt. Het is een van de eerste implementaties van een JSON-querytaal en het klaart de klus. Een leuke bijkomstigheid is dat het de hele JSON-boom doorkruist (bijvoorbeeld toegang tot bovenliggende knooppunten) en padlocaties in de boomstructuur kan uitvoeren als JSON-pointers (sleutels).

Het probleem is dat de syntaxis van JSONPath niet erg intuïtief is, als we een daarop gebaseerde interface aan onze gebruikers willen bieden. Een groter probleem is echter dat het geen strakke specificatie heeft, wat betekent dat er talloze implementaties van JSONPath zijn die verschillende resultaten kunnen geven. Dit maakt het moeilijk om voor veel scenario’s op JSONPath te vertrouwen.

jq

jq is opdrachtregel-JSON-processor die een krachtige JSON-querytaal gebruikt. Je kunt bijna alles doen met jq! Deze taal voor algemeen gebruik is populair onder DevOps-mensen, waarschijnlijk omdat de CLI het gemakkelijk maakt om de output van een ander proces door te sluizen, en vervolgens het pijpsysteem van jq te gebruiken om die geformatteerde output via extra pijpleidingen te verwerken.

Twee problemen echter: jq is een opdrachtregelprogramma, geen interface die we gebruikers kunnen bieden in onze eigen API’s, SDK’s, enzovoort. Bibliotheken zoals knooppunt-jq en java-jq zijn gewoon wrappers rond het jq-binaire bestand en kunnen prestatieproblemen hebben, die cruciaal kunnen zijn voor real-time prestaties. Ten tweede brengt grote kracht een grote complexiteit met zich mee – en sommige gebruikers beweren dat jq een te complexe taal is en misschien overdreven is voor ons gebruik, omdat het veel meer mogelijk maakt dan alleen het manipuleren van bestaande JSON.

JMESPath

JMESPath is een querytaal voor JSON die complexe filtering, extractie en transformatie van JSON-documenten mogelijk maakt. In tegenstelling tot JSONPath en jq, heeft het een volledige specificatie met nauwkeurige grammatica, dus de syntaxis is goed gedefinieerd in alle implementaties. Het heeft volledig compatibele bibliotheken in een groot aantal talen (Java, Python, JavaScript, Go, PHP en meer). Het is een krachtige zoektaal en toch is de syntaxis vrij eenvoudig.

Om deze redenen is JMESPath onze voorkeurstaal als het gaat om het integreren in onze eigen services, als een gebruikersinterface. Het heeft een vriendschappelijk speelplaats en tutorial die we als referentie aan gebruikers kunnen verstrekken.

De voorbeelden op jmespath.org/examples.html kan u een idee geven van de kracht en eenvoud van de taal.

jmespath

Macht aan de gebruiker

Dankzij bibliotheken is het heel eenvoudig om JMESPath toe te voegen aan de code van onze dienst. Door JMESPath te integreren, kunnen we onze API-, SDK- of CLI-gebruikers nu een krachtige, eenvoudige, gestandaardiseerde methode bieden om de JSON-respons van onze service vorm te geven zonder dat hiervoor externe tooling nodig is.

Als voorbeeld gaan we ervan uit dat onze service een openbare REST API is. Door de gebruiker de mogelijkheid te geven om een ​​JMESPath-query op de gegevens te specificeren, besparen ze uiteindelijk tijd, bytes op de draad en coderegels die naverwerking worden uitgevoerd, aangezien de verwerking plaatsvindt op de server en het antwoord precies bevat wat de gebruiker behoeften.

Het integreren van JMESPath in onze API-handlercode is vrij eenvoudig, aangezien de bibliotheek maar één functie heeft (“zoeken”). Hier, als voorbeeld in Node.js / JavaScript:

const jmespath = require(‘jmespath’);


const requestHandler = (req, res, next) => {
   // response res was passed here from previous handler
   if (req.query && req.query.filter) {
       res = applyJmesPathFilter(res, req.query.filter);
   } // ...more logic and eventually res.send()
}

const applyJmesPathFilter = (res, jmespathExpression) => {
   try {
       res = jmespath.search(res, jmespathExpression) || {};
   }
   catch (e) {
       res = {};
       log.error(e);
   }
   return res;
}

Op deze manier kunnen gebruikers de filter queryparameter om een ​​JMESPath-expressie op te geven om het eerste JSON-resultaat te verwerken:

GET /api/players?filter=[JMESPATH_EXPRESSION]

Laten we aannemen dat de output van onze service de API voor NBA-spelers is, waar informatie over enkele legendarische basketballers te vinden is. Een GET / api / players-verzoek retourneert de volgende JSON-array met objecten:

[
 {
   "playerId": "7c3f73dd-0b38-48dc-9347-c78811bd80c4",
   "playerName": "Scottie Pippen",
   "yearOfBirth": "1965",
   "collegeId": "77302082-2758-48cc-ab3a-7b811a8bdf80",
   "jerseyNumber": "33",
   "playerStats": {
       "points": 18940,
       "rebounds": 7494,
       "assists": 6135
   },
   "teamNames": [
      "Chicago Bulls",
      "Portland Trail Blazers",
      "Houston Rockets",
      "Chicago Bulls"
   ]
  },
  {
   "playerId": "8d75bb0f-a444-4264-a583-4ca5799169cf",
   "playerName": "Patrick Ewing",
   "yearOfBirth": "1962",
   "collegeId": "0456a17b-320d-4ddc-bfc2-011670af2b77",
   "jerseyNumber": "33",
   "playerStats": {
      "points": 24815,
      "rebounds": 11617,
      "blocks": 2894
   },
   "teamNames": [
      "New York Knicks",
      "Seattle SuperSonics",
      "Orlando Magic"
   ]
  }
]

Aangezien het oorspronkelijke resultaat een array is, beginnen de onderstaande JMESPath-expressies met ‘[]”Om naar die array te verwijzen.

GET / api / players? Filter =[].playerName retourneert de volgende array:

[
 "Scottie Pippen",
 "Patrick Ewing"
]

We kunnen in plaats daarvan ook om objecten vragen en de originele sleutelnamen wijzigen:

GET / api / players? Filter =[]. {name: playerName, number: jerseyNumber} geeft als resultaat:


[
 {
   "name": "Scottie Pippen",
   "number": "33"
 },
 {
   "name": "Patrick Ewing",
   "number": "33"
 }
]

We kunnen filterquery’s uitvoeren op basis van een of meer sleutels, en het resultaat pipen om het op te maken (hier als gewone string):

GET / api / players? Filter =[?yearOfBirth > `1964`].playerName | [0] geeft terug:

“Scottie Pippen”

En een meer geavanceerde logica, die de sort () en join () functies gebruikt:

GET /api/players?filter=[].playerName | sort(@) | { nbaLegends: join(‘, ‘, @) }

{
  "nbaLegends": "Patrick Ewing, Scottie Pippen"
}

De gebruiker kan het resultaat zo vormgeven dat het op zijn exacte behoeften is afgestemd. Bijvoorbeeld:

GET /api/players?filter=[].{name: playerName, firstTeam: teamNames | [0], points: playerStats.points }

[
 {
   "name": "Scottie Pippen",
   "firstTeam": "Chicago Bulls",
   "points": 18940
 },
 {
   "name": "Patrick Ewing",
   "firstTeam": "New York Knicks",
   "points": 24815
 }
]

En dit zijn slechts enkele van de JSON-verwerkingsopties die beschikbaar zijn via JMESPath.

Sommige lezers zullen misschien opmerken dat het mogelijk is om de gebruiker in staat te stellen “precies de gegevens te krijgen die ze willen” door GraphQL te gebruiken. Klopt, maar deze specifieke optie beschrijft een REST-API (en het scenario kan worden vervangen door een SDK of CLI). In feite geeft GraphQL u niet de verwerkingskracht die JSON-querytalen zoals JMESPath bieden. Natuurlijk wordt de JSON-verwerking uitgevoerd nadat de initiële dataset volledig is opgehaald, wat niet zo efficiënt is als vroeg filteren met resolvers op de Data Access Layer, zoals in GraphQL API’s.

Ik heb JMESPath geïntegreerd in een aantal van Sisense openbare REST-API’sen als een functie in de GraphQL2REST-pakket.

Beperkingen

JMESPath heeft enkele beperkingen in vergelijking met jq en JSONPath. De grootste beperking voor mij is dat er geen gemakkelijke manier is om de lijst met paden van een bepaald JSON-document (ook wel knooppuntnamen of JSON-pointers genoemd) te krijgen, en je kunt geen sleutels uitdrukken als JMESPath-expressies (om bewerkingen uit te voeren op een set sleutels die aan een test voldoen). Recursieve traversal is niet mogelijk – u moet het volledige pad naar de JSON-sleutel specificeren. Een ander probleem is dat JMESPath niet toestaat dat naar bovenliggende knooppunten wordt verwezen tijdens het itereren. Dit zijn beperkingen die soms het gebruik voor ontwikkelaars beperken, maar hebben meestal geen invloed op de algemene gebruikssituaties die hier worden besproken.

Aanvullende bronnen

Voor avonturiers – meer zoektalen:

Roy Mor is een technische leider in de R & D-groep van Sisense. Voordat hij bij Sisense werkte, werkte hij als full-stack ontwikkelaar bij startups, maar ook bij Microsoft en Intel, en als onafhankelijk consultant. Hij heeft meer dan 15 jaar professionele ervaring met het schrijven van software.

Trefwoorden: Gegevensmodellen API | json