MongoDB
 sql >> Base de données >  >> NoSQL >> MongoDB

Comment convertir JSON imbriqué arbitrairement en CSV avec jq - afin que vous puissiez le reconvertir ?

Le tocsv suivant et fromcsv fournissent une solution au problème énoncé à l'exception d'une complication concernant l'exigence (6) concernant les en-têtes. Essentiellement, cette exigence peut être satisfaite en utilisant les fonctions données ici en ajoutant une étape de transposition de matrice.

Qu'une étape de transposition soit ajoutée ou non, l'avantage de l'approche adoptée ici est qu'il n'y a aucune restriction sur les clés ou les valeurs JSON. En particulier, ils peuvent contenir des points (points), des retours à la ligne et/ou des caractères NUL.

Dans l'exemple, un tableau d'objets est donné, mais en fait n'importe quel flux de documents JSON valides pourrait être utilisé comme entrée pour tocsv; grâce à la magie de jq, le flux original sera recréé par fromcsv (au sens de l'égalité entité par entité).

Bien sûr, puisqu'il n'y a pas de norme CSV, le CSV produit par le tocsv fonction peut ne pas être comprise par tous les processeurs CSV. En particulier, veuillez noter que le tocsv la fonction définie ici mappe les retours à la ligne intégrés dans les chaînes JSON ou les noms de clé à la chaîne à deux caractères "\n" (c'est-à-dire une barre oblique inverse littérale suivie de la lettre "n") ; l'opération inverse effectue la traduction inverse pour répondre à l'"aller-retour" exigence.

(L'utilisation de tail est juste pour simplifier la présentation ; il serait inutile de modifier la solution pour en faire une solution uniquement jq.)

Le CSV est généré en supposant que n'importe quelle valeur peut être incluse dans un champ tant que (a) le champ est entre guillemets et (b) les guillemets doubles dans le champ sont doublés.

Toute solution générique prenant en charge les "allers-retours" est forcément quelque peu compliquée. La principale raison pour laquelle la solution présentée ici est plus complexe qu'on pourrait s'y attendre est qu'une troisième colonne est ajoutée, en partie pour faciliter la distinction entre les entiers et les chaînes à valeurs entières, mais principalement parce qu'elle permet de distinguer facilement la taille-1 et la taille -2 tableaux produits par --stream de jq option. Inutile de dire qu'il existe d'autres façons d'aborder ces problèmes; le nombre d'appels vers jq pourrait également être réduit.

La solution est présentée sous la forme d'un script de test qui vérifie l'exigence d'aller-retour sur un cas de test révélateur :

#!/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)

Voici le CSV qui serait produit par json | tocsv , sauf que SO semble interdire les NUL littéraux, j'ai donc remplacé cela par \0 :

"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