MongoDB
 sql >> Datenbank >  >> NoSQL >> MongoDB

Wie konvertiert man beliebiges verschachteltes JSON mit jq in CSV – damit man es zurückkonvertieren kann?

Die folgende tocsv und fromcsv Funktionen bieten eine Lösung für das angegebene Problem, abgesehen von einer Komplikation in Bezug auf Anforderung (6), die die Header betrifft. Im Wesentlichen kann diese Anforderung mit den hier angegebenen Funktionen erfüllt werden, indem ein Matrixtranspositionsschritt hinzugefügt wird.

Unabhängig davon, ob ein Transpositionsschritt hinzugefügt wird oder nicht, besteht der Vorteil des hier gewählten Ansatzes darin, dass es keine Einschränkungen für die JSON-Schlüssel oder -Werte gibt. Sie können insbesondere Punkte (Punkte), Zeilenumbrüche und/oder NUL-Zeichen enthalten.

In dem Beispiel wird ein Array von Objekten angegeben, aber tatsächlich könnte jeder Strom gültiger JSON-Dokumente als Eingabe für tocsv verwendet werden; Dank der Magie von jq wird der ursprüngliche Stream von fromcsv neu erstellt (im Sinne der Entitätsgleichheit).

Da es natürlich keinen CSV-Standard gibt, wird die von tocsv erzeugte CSV Die Funktion wird möglicherweise nicht von allen CSV-Prozessoren verstanden. Bitte beachten Sie insbesondere, dass die Datei tocsv Die hier definierte Funktion ordnet eingebettete Zeilenumbrüche in JSON-Strings oder Schlüsselnamen der aus zwei Zeichen bestehenden Zeichenfolge „\n“ zu (d. h. einem wörtlichen umgekehrten Schrägstrich gefolgt von dem Buchstaben „n“). Anforderung.

(Die Verwendung von tail dient lediglich der Vereinfachung der Darstellung; es wäre trivial, die Lösung zu modifizieren, um sie zu einer Nur-jq-Lösung zu machen.)

Die CSV-Datei wird unter der Annahme generiert, dass jeder Wert in ein Feld aufgenommen werden kann, solange (a) das Feld in Anführungszeichen gesetzt wird und (b) doppelte Anführungszeichen innerhalb des Felds verdoppelt werden.

Jede generische Lösung, die "Round-Trips" unterstützt, ist zwangsläufig etwas kompliziert. Der Hauptgrund, warum die hier vorgestellte Lösung komplexer ist, als man erwarten könnte, liegt darin, dass eine dritte Spalte hinzugefügt wurde, teilweise um die Unterscheidung zwischen ganzen Zahlen und ganzzahligen Strings zu erleichtern, aber hauptsächlich, weil es einfach ist, zwischen size-1 und size zu unterscheiden -2 Arrays, die von --stream von jq erzeugt wurden Möglichkeit. Unnötig zu erwähnen, dass es andere Möglichkeiten gibt, diese Probleme anzugehen; auch die anzahl der anrufe bei jq konnte reduziert werden.

Die Lösung wird als Testskript dargestellt, das die Roundtrip-Anforderung anhand eines aussagekräftigen Testfalls überprüft:

#!/bin/bash

function json {
    cat<<EOF
[
  {
    "a": 1,
    "b": [
      1,
      2,
      "1"
    ],
    "c": "d\",ef",
    "embed\"ed": "quote",
    "null": null,
    "string": "null",
    "control characters": "a\u0000c",
    "newline": "a\nb"
  },
  {
    "x": 1
  }
]
EOF
}

function tocsv {
 jq -ncr --stream '
   (["path", "value", "stringp"],
    (inputs | . + [.[1]|type=="string"]))
   | map( tostring|gsub("\"";"\"\"") | gsub("\n"; "\\n"))
   | "\"\(.[0])\",\"\(.[1])\",\(.[2])" 
'
}

function fromcsv { 
    tail -n +2 | # first duplicate backslashes and deduplicate double-quotes
    jq -rR '"[\(gsub("\\\\";"\\\\") | gsub("\"\"";"\\\"") ) ]"' |
    jq -c '.[2] as $s 
           | .[0] |= fromjson 
           | .[1] |= if $s then . else fromjson end 
           | if $s == null then [.[0]] else .[:-1] end
             # handle newlines
           | map(if type == "string" then gsub("\\\\n";"\n") else . end)' |
    jq -n 'fromstream(inputs)'
}    

# Check the roundtrip:
json | tocsv | fromcsv | jq -s '.[0] == .[1]' - <(json)

Hier ist die CSV-Datei, die von json | tocsv , außer dass SO wörtliche NULs nicht zulässt, also habe ich das durch \0 ersetzt :

"path","value",stringp
"[0,""a""]","1",false
"[0,""b"",0]","1",false
"[0,""b"",1]","2",false
"[0,""b"",2]","1",true
"[0,""b"",2]","false",null
"[0,""c""]","d"",ef",true
"[0,""embed\""ed""]","quote",true
"[0,""null""]","null",false
"[0,""string""]","null",true
"[0,""control characters""]","a\0c",true
"[0,""newline""]","a\nb",true
"[0,""newline""]","false",null
"[1,""x""]","1",false
"[1,""x""]","false",null
"[1]","false",null