En utilisant jq , comment le codage JSON arbitraire d'un tableau d'objets peu profonds peut-il être converti en CSV?
Il y a beaucoup de questions et réponses sur ce site qui couvrent des modèles de données spécifiques qui codent en dur les champs, mais les réponses à cette question devraient fonctionner avec n'importe quel JSON, avec la seule restriction qu'il s'agit d'un tableau d'objets avec des propriétés scalaires (pas de deep / complex / sous-objets, car les aplatir est une autre question). Le résultat doit contenir une ligne d'en-tête donnant les noms de champ. La préférence sera donnée aux réponses qui préservent l'ordre des champs du premier objet, mais ce n'est pas une exigence. Les résultats peuvent entourer toutes les cellules avec des guillemets doubles, ou ne contenir que celles qui nécessitent des guillemets (par exemple «a, b»).
Exemples
Contribution:
[ {"code": "NSW", "name": "New South Wales", "level":"state", "country": "AU"}, {"code": "AB", "name": "Alberta", "level":"province", "country": "CA"}, {"code": "ABD", "name": "Aberdeenshire", "level":"council area", "country": "GB"}, {"code": "AK", "name": "Alaska", "level":"state", "country": "US"} ]
Sortie possible:
code,name,level,country NSW,New South Wales,state,AU AB,Alberta,province,CA ABD,Aberdeenshire,council area,GB AK,Alaska,state,US
Sortie possible:
"code","name","level","country" "NSW","New South Wales","state","AU" "AB","Alberta","province","CA" "ABD","Aberdeenshire","council area","GB" "AK","Alaska","state","US"
Contribution:
[ {"name": "bang", "value": "!", "level": 0}, {"name": "letters", "value": "a,b,c", "level": 0}, {"name": "letters", "value": "x,y,z", "level": 1}, {"name": "bang", "value": "\"!\"", "level": 1} ]
Sortie possible:
name,value,level bang,!,0 letters,"a,b,c",0 letters,"x,y,z",1 bang,"""!""",0
Sortie possible:
"name","value","level" "bang","!","0" "letters","a,b,c","0" "letters","x,y,z","1" "bang","""!""","1"
json2csv
est à stackoverflow.com/questions/57242240/…Réponses:
Tout d'abord, obtenez un tableau contenant tous les différents noms de propriétés d'objet dans votre entrée de tableau d'objets. Ce seront les colonnes de votre CSV:
Ensuite, pour chaque objet de l'entrée du tableau d'objets, mappez les noms de colonne que vous avez obtenus aux propriétés correspondantes de l'objet. Ce seront les lignes de votre CSV.
Enfin, placez les noms de colonnes avant les lignes, en tant qu'en-tête pour le CSV, et transmettez le flux de lignes résultant au
@csv
filtre.Tous ensemble maintenant. N'oubliez pas d'utiliser l'
-r
indicateur pour obtenir le résultat sous forme de chaîne brute:la source
$rows
affectation de variable en l'incrustant simplement:(map(keys) | add | unique) as $cols | $cols, map(. as $row | $cols | map($row[.]))[] | @csv
$rows
cela ne doit pas être attribué à une variable; Je pensais juste que l'assigner à une variable rendait l'explication plus agréable.Le maigre
ou:
Les détails
De côté
Décrire les détails est délicat car jq est orienté flux, ce qui signifie qu'il fonctionne sur une séquence de données JSON, plutôt que sur une seule valeur. Le flux JSON d'entrée est converti en un type interne qui est passé à travers les filtres, puis codé dans un flux de sortie à la fin du programme. Le type interne n'est pas modélisé par JSON et n'existe pas en tant que type nommé. Il est plus facile de le démontrer en examinant la sortie d'un index nu (
.[]
) ou de l'opérateur virgule (l'examiner directement pourrait être fait avec un débogueur, mais ce serait en termes de types de données internes de jq, plutôt que de types de données conceptuels derrière JSON) .Notez que la sortie n'est pas un tableau (ce qui serait
["a", "b"]
). La sortie compacte (l'-c
option) montre que chaque élément du tableau (ou argument du,
filtre) devient un objet distinct dans la sortie (chacun est sur une ligne distincte).Un flux est comme un JSON-seq , mais utilise des nouvelles lignes plutôt que RS comme séparateur de sortie lorsqu'il est encodé. Par conséquent, ce type interne est désigné par le terme générique «séquence» dans cette réponse, «flux» étant réservé pour l'entrée et la sortie codées.
Construire le filtre
Les clés du premier objet peuvent être extraites avec:
Les clés seront généralement conservées dans leur ordre d'origine, mais la préservation de l'ordre exact n'est pas garantie. Par conséquent, ils devront être utilisés pour indexer les objets pour obtenir les valeurs dans le même ordre. Cela empêchera également les valeurs d'être dans les mauvaises colonnes si certains objets ont un ordre de clé différent.
Pour afficher les clés dans la première ligne et les rendre disponibles pour l'indexation, elles sont stockées dans une variable. L'étape suivante du pipeline fait alors référence à cette variable et utilise l'opérateur virgule pour ajouter l'en-tête au flux de sortie.
L'expression après la virgule est un peu compliquée. L'opérateur d'index sur un objet peut prendre une séquence de chaînes (par exemple
"name", "value"
), renvoyant une séquence de valeurs de propriété pour ces chaînes.$keys
est un tableau, pas une séquence,[]
est donc appliqué pour le convertir en séquence,qui peut ensuite être passé à
.[]
Cela produit également une séquence, de sorte que le constructeur de tableau est utilisé pour le convertir en tableau.
Cette expression doit être appliquée à un seul objet.
map()
est utilisé pour l'appliquer à tous les objets du tableau externe:Enfin, pour cette étape, cela est converti en une séquence afin que chaque élément devienne une ligne distincte dans la sortie.
Pourquoi regrouper la séquence dans un tableau au sein du
map
seul pour la dégrouper à l'extérieur?map
produit un tableau;.[ $keys[] ]
produit une séquence. Appliquermap
à la séquence from.[ $keys[] ]
produirait un tableau de séquences de valeurs, mais comme les séquences ne sont pas de type JSON, vous obtenez à la place un tableau aplati contenant toutes les valeurs.Les valeurs de chaque objet doivent être conservées séparément, afin qu'elles deviennent des lignes distinctes dans la sortie finale.
Enfin, la séquence est passée par le
@csv
formateur.Alterner
Les articles peuvent être séparés tardivement plutôt que tôt. Au lieu d'utiliser l'opérateur virgule pour obtenir une séquence (en passant une séquence comme opérande de droite), la séquence d'en-tête (
$keys
) peut être enveloppée dans un tableau et+
utilisée pour ajouter le tableau de valeurs. Celui-ci doit encore être converti en séquence avant d'être transmis à@csv
.la source
keys_unsorted
place dekeys
pour conserver l'ordre des clés du premier objet?[{"a":1,"b":2,"c":3}]
.J'ai créé une fonction qui génère un tableau d'objets ou de tableaux en csv avec des en-têtes. Les colonnes seraient dans l'ordre des en-têtes.
Vous pouvez donc l'utiliser comme ceci:
la source
Le filtre suivant est légèrement différent en ce qu'il garantira que chaque valeur est convertie en chaîne. (Remarque: utilisez jq 1.5+)
Filtre:
filter.jq
la source
unique
est triée de toute façon, elleunique|sort
peut donc être simplifiée enunique
.-r
option. Sinon, tous les guillemets"
deviennent extra-échappés, ce qui n'est pas un CSV valide.Cette variante du programme de Santiago est également sûre, mais garantit que les noms de clé du premier objet sont utilisés comme en-têtes de première colonne, dans le même ordre qu'ils apparaissent dans cet objet:
la source