Wie analysiere ich Elemente aus einem großen JSON-Stream in JavaScript?
Also habe ich den Wikidata JSON-Dump heruntergeladen und er ist ungefähr 90 GB groß, zu groß, um ihn in den Speicher zu laden. Es besteht aus einer einfachen JSON-Struktur wie dieser:
[
item,
item,
item,
...
]
Jeder "Gegenstand" sieht ungefähr so aus:
{
"type": "item",
"id": "Q23",
"labels": {
"<lang>": obj
},
"descriptions": {
"<lang>": {
"language": "<lang>",
"value": "<string>"
},
},
"aliases": {
"<key>": [
obj,
obj,
],
},
"claims": {
"<keyID>": [
{
"mainsnak": {
"snaktype": "value",
"property": "<keyID>",
"datavalue": {
"value": {
"entity-type": "<type>",
"numeric-id": <num>,
"id": "<id>"
},
"type": "wikibase-entityid"
},
"datatype": "wikibase-item"
},
"type": "statement",
"id": "<anotherId>",
"rank": "preferred",
"references": [
{
"hash": "<hash>",
"snaks": {
"<keyIDX>": [
{
"snaktype": "value",
"property": "P854",
"datavalue": obj,
"datatype": "url"
}
]
},
"snaks-order": [
"<propID>"
]
}
]
}
]
},
"sitelinks": {
"<lang>wiki": {
"site": "<lang>wiki",
"title": "<string>",
"badges": []
}
}
}
Der JSON-Stream ist folgendermaßen konfiguriert:
const fs = require('fs')
const zlib = require('zlib')
const { parser } = require('stream-json')
let stream = fs.createReadStream('./wikidata/latest-all.json.gz')
stream
.pipe(zlib.createGunzip())
.pipe(parser())
.on('data', buildItem)
function buildItem(data) {
switch (data.name) {
case `startArray`:
break
case `startObject`:
break
case `startKey`:
break
case `stringChunk`:
break
case `endKey`:
break
case `keyValue`:
break
case `startString`:
break
case `endString`:
break
case `stringValue`:
break
case `endObject`:
break
case `endArray`:
break
}
}
Beachten buildItemSie, dass das die Schlüsselinformationen hat, es zeigt, dass der JSON-Stream Objekte wie dieses ausgibt (dies sind die Protokolle):
{ name: 'startArray' }
{ name: 'startObject' }
{ name: 'startKey' }
{ name: 'startString' }
{ name: 'stringValue', value: 'type' }
{ name: 'endString' }
...
Wie analysieren Sie dies in itemObjekte wie das oben genannte? Das Parsen dieses linearen Stroms in einen Baum ist sehr schwer zu verstehen.
Eine Probe von Ausgabe aus dem JSON - Stream ist hier , was Sie einen Parser zu testen , verwenden könnten , wenn es hilft.
Antworten
Verwenden integrierter Funktionen (StreamArray)
stream-jsonhat bereits integrierte Funktionen, die Streams in Objekte konvertieren (in diesem Fall suchen Sie nach StreamArray ). Möglicherweise möchten Sie die integrierten Funktionen verwenden, da sie im Hinblick auf die Leistung codiert wurden.
Um es zu benutzen, würde es ungefähr so aussehen:
const fs = require('fs')
const zlib = require('zlib')
const { parser } = require('stream-json')
const { streamArray } = require('stream-json/streamers/StreamArray')
let stream = fs.createReadStream('./wikidata/latest-all.json.gz')
stream
.pipe(zlib.createGunzip())
.pipe(parser())
.pipe(streamArray())
.on('data', d => processData(d.value))
function processData(data) {
console.log(data)
}
Ich empfehle einen Blick in das Wiki unter https://github.com/uhop/stream-json/wiki Weitere Informationen, da es zusätzliche Funktionen hat, insbesondere zum Filtern oder Transformieren, die wahrscheinlich für Sie nützlich sind, insbesondere wenn Geschwindigkeit ein Problem darstellt.
Wenn ich dich richtig verstanden habe, willst du so etwas. Ich habe eine ObjectBuilderKlasse verwendet, die alle Methoden kombiniert, um ein JSON-Objekt zu erstellen. Es wird verwendet parentStack, um alle Objekte und Arrays zu verfolgen. Wenn das Objekt / Array mit startObject/startArrayeinem neuen JSON-Objekt / Array gestartet wird, wird es auf den Stapel verschoben. Sobald dieses Objekt / Array fertig ist, wird es vom Stapel genommen. Das letzte Objekt, das vom Stapel entfernt wird, ist das gesamte Objektobjekt und kann weiter verarbeitet werden (im folgenden Beispiel drucke ich es einfach aus).
Das aktuelle Objekt oder Array, das gerade erstellt wird, befindet sich immer oben auf dem Stapel.
Ich musste eine Teilmenge des von Ihnen bereitgestellten Beispiels verwenden, da es keine übereinstimmende Anzahl von startObjectund endObjectElementen enthielt , was zu einem ungültigen JSON führte. Ich habe diese Teilmenge unter dem Code eingefügt.
Hoffentlich haben Sie danach gesucht :)
(Beachten Sie, dass ich die buildItem()Funktion nur in die runSample()Funktion eingeschlossen habe, damit ich das Beispiel-JSON unten einfügen kann, damit es in diesem Online-Editor übersichtlicher aussieht. Sie können die buildItem()Funktion nach außen verschieben.)
class ObjectBuilder {
constructor() {
this.finalObject = undefined;
this.parentStack = [];
this.currentKey = undefined;
}
hasFinished() {
return this.finalObject !== undefined;
}
getFinalObject() {
return this.finalObject;
}
currentObject() {
return this.parentStack[this.parentStack.length - 1];
}
addValue(val) {
if (Array.isArray(this.currentObject())) {
this.currentObject().push(val);
}
else {
this.currentObject()[this.currentKey] = val;
this.currentKey = undefined;
}
}
processData(data) {
switch (data.name) {
case `startKey`:
case `endKey`:
case `startString`:
case `endString`:
case `stringChunk`:
// ignore, always followed by [something]Value
break;
case `keyValue`:
this.currentObject()[data.value] = undefined;
this.currentKey = data.value;
break;
case `numberValue`:
this.addValue(Number(data.value))
break
case `stringValue`:
this.addValue(data.value);
break;
case `startObject`:
let newObject = {};
if (this.parentStack.length === 0) {
// do nothing else, initialises first parent
}
else if (Array.isArray(this.currentObject())) {
this.currentObject().push(newObject);
}
else {
this.currentObject()[this.currentKey] = newObject;
}
this.parentStack.push(newObject);
this.currentKey = undefined;
break;
case `endObject`:
let parent = this.parentStack.pop();
if (this.parentStack.length === 0) {
this.finalObject = parent;
}
break;
case `startArray`:
let newArray = [];
if (Array.isArray(this.currentObject())) {
this.currentObject().push(newArray);
}
else {
this.currentObject()[this.currentKey] = newArray;
}
this.parentStack.push(newArray);
this.currentKey = undefined;
break;
case `endArray`:
this.parentStack.pop();
this.currentKey = undefined;
break;
}
}
}
function runSample(streamData) {
let currentlyProcessing = undefined;
function buildItem(data) {
if (currentlyProcessing === undefined && data.name === "endArray") {
return; // stream ended
}
if (currentlyProcessing === undefined) {
currentlyProcessing = new ObjectBuilder();
}
currentlyProcessing.processData(data);
if (currentlyProcessing.hasFinished()) {
// Finished building project; do something with it
let niceOutput = JSON.stringify(currentlyProcessing.getFinalObject(), null, 4);
console.log(niceOutput);
currentlyProcessing = undefined;
}
}
// simulate reading stream
for (let i = 0; i < streamData.length; ++i) {
if (i === 0) {
// Skip first chunk as it starts the array of items
continue;
}
buildItem(streamData[i]);
}
}
const streamData = [{"name": "startArray"},{"name": "startObject"},{"name": "startKey"},{"name": "stringChunk","value": "type"},{"name": "endKey"},{"name": "keyValue","value": "type"},{"name": "startString"},{"name": "stringChunk","value": "item"},{"name": "endString"},{"name": "stringValue","value": "item"},{"name": "startKey"},{"name": "stringChunk","value": "id"},{"name": "endKey"},{"name": "keyValue","value": "id"},{"name": "startString"},{"name": "stringChunk","value": "Q31"},{"name": "endString"},{"name": "stringValue","value": "Q31"},{"name": "startKey"},{"name": "stringChunk","value": "labels"},{"name": "endKey"},{"name": "keyValue","value": "labels"},{"name": "startObject"},{"name": "startKey"},{"name": "stringChunk","value": "el"},{"name": "endKey"},{"name": "keyValue","value": "el"},{"name": "startObject"},{"name": "startKey"},{"name": "stringChunk","value": "language"},{"name": "endKey"},{"name": "keyValue","value": "language"},{"name": "startString"},{"name": "stringChunk","value": "el"},{"name": "endString"},{"name": "stringValue","value": "el"},{"name": "startKey"},{"name": "stringChunk","value": "value"},{"name": "endKey"},{"name": "keyValue","value": "value"},{"name": "startString"},{"name": "stringChunk","value": "Β"},{"name": "stringChunk","value": "έ"},{"name": "stringChunk","value": "λ"},{"name": "stringChunk","value": "γ"},{"name": "stringChunk","value": "ι"},{"name": "stringChunk","value": "ο"},{"name": "endString"},{"name": "stringValue","value": "Βέλγιο"},{"name": "endObject"},{"name": "startKey"},{"name": "stringChunk","value": "ay"},{"name": "endKey"},{"name": "keyValue","value": "ay"},{"name": "startObject"},{"name": "startKey"},{"name": "stringChunk","value": "language"},{"name": "endKey"},{"name": "keyValue","value": "language"},{"name": "startString"},{"name": "stringChunk","value": "ay"},{"name": "endString"},{"name": "stringValue","value": "ay"},{"name": "startKey"},{"name": "stringChunk","value": "value"},{"name": "endKey"},{"name": "keyValue","value": "value"},{"name": "startString"},{"name": "stringChunk","value": "Bilkiya"},{"name": "endString"},{"name": "stringValue","value": "Bilkiya"},{"name": "endObject"},{"name": "startKey"},{"name": "stringChunk","value": "pnb"},{"name": "endKey"},{"name": "keyValue","value": "pnb"},{"name": "startObject"},{"name": "startKey"},{"name": "stringChunk","value": "language"},{"name": "endKey"},{"name": "keyValue","value": "language"},{"name": "startString"},{"name": "stringChunk","value": "pnb"},{"name": "endString"},{"name": "stringValue","value": "pnb"},{"name": "startKey"},{"name": "stringChunk","value": "value"},{"name": "endKey"},{"name": "keyValue","value": "value"},{"name": "startString"},{"name": "stringChunk","value": "ب"},{"name": "stringChunk","value": "ی"},{"name": "stringChunk","value": "ل"},{"name": "stringChunk","value": "ج"},{"name": "stringChunk","value": "ی"},{"name": "stringChunk","value": "م"},{"name": "endString"},{"name": "stringValue","value": "بیلجیم"},{"name": "endObject"},{"name": "endObject"},{"name": "endObject"},{"name": "startObject"},{"name": "startKey"},{"name": "stringChunk","value": "type"},{"name": "endKey"},{"name": "keyValue","value": "type"},{"name": "startString"},{"name": "stringChunk","value": "item"},{"name": "endString"},{"name": "stringValue","value": "item"},{"name": "startKey"},{"name": "stringChunk","value": "id"},{"name": "endKey"},{"name": "keyValue","value": "id"},{"name": "startString"},{"name": "stringChunk","value": "Q31"},{"name": "endString"},{"name": "stringValue","value": "Q31"},{"name": "startKey"},{"name": "stringChunk","value": "labels"},{"name": "endKey"},{"name": "keyValue","value": "labels"},{"name": "startObject"},{"name": "startKey"},{"name": "stringChunk","value": "el"},{"name": "endKey"},{"name": "keyValue","value": "el"},{"name": "startObject"},{"name": "startKey"},{"name": "stringChunk","value": "language"},{"name": "endKey"},{"name": "keyValue","value": "language"},{"name": "startString"},{"name": "stringChunk","value": "el"},{"name": "endString"},{"name": "stringValue","value": "el"},{"name": "startKey"},{"name": "stringChunk","value": "value"},{"name": "endKey"},{"name": "keyValue","value": "value"},{"name": "startString"},{"name": "stringChunk","value": "Β"},{"name": "stringChunk","value": "έ"},{"name": "stringChunk","value": "λ"},{"name": "stringChunk","value": "γ"},{"name": "stringChunk","value": "ι"},{"name": "stringChunk","value": "ο"},{"name": "endString"},{"name": "stringValue","value": "Βέλγιο"},{"name": "endObject"},{"name": "startKey"},{"name": "stringChunk","value": "ay"},{"name": "endKey"},{"name": "keyValue","value": "ay"},{"name": "startObject"},{"name": "startKey"},{"name": "stringChunk","value": "language"},{"name": "endKey"},{"name": "keyValue","value": "language"},{"name": "startString"},{"name": "stringChunk","value": "ay"},{"name": "endString"},{"name": "stringValue","value": "ay"},{"name": "startKey"},{"name": "stringChunk","value": "value"},{"name": "endKey"},{"name": "keyValue","value": "value"},{"name": "startString"},{"name": "stringChunk","value": "Bilkiya"},{"name": "endString"},{"name": "stringValue","value": "Bilkiya"},{"name": "endObject"},{"name": "startKey"},{"name": "stringChunk","value": "pnb"},{"name": "endKey"},{"name": "keyValue","value": "pnb"},{"name": "startObject"},{"name": "startKey"},{"name": "stringChunk","value": "language"},{"name": "endKey"},{"name": "keyValue","value": "language"},{"name": "startString"},{"name": "stringChunk","value": "pnb"},{"name": "endString"},{"name": "stringValue","value": "pnb"},{"name": "startKey"},{"name": "stringChunk","value": "value"},{"name": "endKey"},{"name": "keyValue","value": "value"},{"name": "startString"},{"name": "stringChunk","value": "ب"},{"name": "stringChunk","value": "ی"},{"name": "stringChunk","value": "ل"},{"name": "stringChunk","value": "ج"},{"name": "stringChunk","value": "ی"},{"name": "stringChunk","value": "م"},{"name": "endString"},{"name": "stringValue","value": "بیلجیم"},{"name": "endObject"},{"name": "startKey"},{"name": "stringChunk","value": "nestedArray"},{"name": "endKey"},{"name": "keyValue","value": "nestedArray"},{"name": "startArray"},{"name": "stringValue","value": "a"},{"name": "stringValue","value": "b"},{"name": "startArray"},{"name": "stringValue","value": "c"},{"name": "startObject"},{"name": "keyValue","value": "another object"},{"name": "stringValue","value": "d"},{"name": "endObject"},{"name": "stringValue","value": "e"},{"name": "endArray"},{"name": "stringValue","value": "b"},{"name": "endArray"},{"name": "endObject"},{"name": "endObject"},{"name": "endArray"}];
runSample(streamData);