Utilisation de jq pour extraire des valeurs et un format au format CSV

58

J'ai le fichier JSON ci-dessous:

{
"data": [
    {
        "displayName": "First Name",
        "rank": 1,
        "value": "VALUE"
    },
    {
        "displayName": "Last Name",
        "rank": 2,
        "value": "VALUE"
    },
    {
        "displayName": "Position",
        "rank": 3,
        "value": "VALUE"
    },
    {
        "displayName": "Company Name",
        "rank": 4,
        "value": "VALUE"
    },
    {
        "displayName": "Country",
        "rank": 5,
        "value": "VALUE"
    },
]
}

J'aimerais avoir un fichier CSV dans ce format:

First Name, Last Name, Position, Company Name, Country
VALUE, VALUE, VALUE, VALUE, VALUE, VALUE

Est-ce possible en utilisant seulement jq? Je n'ai aucune compétence en programmation.

Kerim
la source
1
J'ai fourni une réponse ci-dessous, mais je regarde maintenant de plus près votre question et je ne peux pas m'empêcher de me demander: d'où est censée provenir la 6ème VALEUR ?
mikeserv
1
En relation avec SO: stackoverflow.com/questions/25558456/…
Anton Tarasenko
Également lié à stackoverflow.com/q/32960857/168034
phunehehe

Réponses:

50

jq a un filtre, @csv, pour convertir un tableau en chaîne CSV. Ce filtre prend en compte la plupart des complexités associées au format CSV, à commencer par des virgules incorporées dans les champs. (jq 1.5 a un filtre similaire, @tsv, pour générer des fichiers de valeurs séparées par des tabulations.)

Bien sûr, s'il est garanti que les en-têtes et les valeurs ne contiennent pas de virgules ni de guillemets, il n'est peut-être pas nécessaire d'utiliser le filtre @csv. Sinon, il serait probablement préférable de l'utiliser.

Par exemple, si le "Nom de la société" était "Smith, Smith et Smith" et si les autres valeurs étaient comme indiqué ci-dessous, l'appel de jq avec l'option "-r" produirait un fichier CSV valide:

$ jq -r '.data | map(.displayName), map(.value) | @csv' so.json2csv.json
"First Name","Last Name","Position","Company Name","Country"
"John (""Johnnie"")","Doe","Director, Planning and Posterity","Smith, Smith and Smith","Transylvania"
de pointe
la source
3
J'ai pu 'jq somestuff | carte (.) | @csv ', très pratique! Merci
flickerfly
3
Votre exemple va mettre tous les noms d'affichage sur la première ligne et toutes les valeurs sur la deuxième ligne, au lieu d'avoir une ligne par enregistrement.
Brian Gordon
33

Je préfère que chaque enregistrement soit rangé dans mon fichier CSV.

jq '.data | map([.displayName, .rank, .value] | join(", ")) | join("\n")'
Silas Paul
la source
2
Et si .value est un nombre? Je reçois le message d'erreur "la chaîne et le nombre ne peuvent pas être ajoutés"
Cos
2
@Cos quelque chose comme .value|tostringau lieu de .valuedans l'exemple ci-dessus
matheeeny
4
@Cos, j'ai trouvé que les parenthèses sont obligatoires. (.value|tostring)
ciscogambo
Aussi, utilisez jq -rpour dépouiller les citations
Clay
30

Avec juste ce fichier, vous pouvez faire quelque chose comme:

<testfile jq -r '.data | map(.displayName), map(.value) | join(", ")'

L' .opérateur sélectionne un champ à partir d'un objet / hachage. Ainsi, nous commençons avec .data, qui retourne le tableau avec les données qu'il contient. Nous mappons ensuite deux fois sur le tableau, en sélectionnant d'abord le nom d'affichage, puis en sélectionnant la valeur, ce qui nous donne deux tableaux contenant uniquement les valeurs de ces clés. Pour chaque tableau, nous joignons les éléments avec "," formant deux lignes. L' -rargument dit jqde ne pas citer les chaînes résultantes.

Si votre fichier est plus long (c.-à-d. Qu'il contient des entrées pour plus d'une personne), vous aurez probablement besoin de quelque chose d'un peu plus compliqué.

Steven D
la source
Cela ne fonctionne pas pour moi. Dans un sujet connexe, la réponse stackoverflow.com/questions/32960857/… fonctionne et est très bien expliquée!
Herve
10

J'ai eu du jqmal à comprendre. Voici quelques rubis:

ruby -rjson -rcsv -e '
  data = JSON.parse(File.read "file.json")
  data["data"].collect {|item| [item["displayName"], item["value"]]}
              .transpose
              .each {|row| puts row.to_csv}
'
First Name,Last Name,Position,Company Name,Country
VALUE,VALUE,VALUE,VALUE,VALUE

L’analyseur JSON ruby ​​a commenté la virgule avant la parenthèse fermée.

Glenn Jackman
la source
2

Puisque vous avez marqué ceci pythonet en supposant que le nom du jsonfichier estx.json

import os, json
with open('x.json') as f:
    x  = json.load(f)
    print '{}{}{}'.format(', '.join(y['displayName'] for y in x['data']), os.linesep,
             ', '.join(y['value'] for y in x['data']))
First Name, Last Name, Position, Company Name, Country
VALUE, VALUE, VALUE, VALUE, VALUE
iruvar
la source
1

Bien que j'ai dû supprimer la dernière virgule dans votre exemple d'entrée pour que cela fonctionne, car je me suis jqplaint d'attendre un autre élément de tableau, ceci:

INPUT | jq -r '[.[][].displayName], [.[][].value]| join(", ")'

... m'a eu ...

First Name, Last Name, Position, Company Name, Country
VALUE, VALUE, VALUE, VALUE, VALUE

Comment ça marche en quelques mots:

  1. J'ai traversé le troisième niveau d'objets de données en utilisant la []forme et la .dotnotation du champ d'index vide .
  2. Une fois assez profond, j'ai spécifié les champs de données que je voulais par nom, comme .[][].displayName.
  3. J'ai assuré que mes champs souhaités étaient auto-associés en les renvoyant sous forme d'objets de tableau distincts, comme [.[][].displayName], [.[][].value]
  4. Et puis canalisé ces objets vers la join(", ")fonction à associer en tant qu'entités séparées.

En réalité, cela [.field]n’est qu’une autre façon de procéder, map(.field)mais c’est un peu plus spécifique en ce sens qu’il spécifie le niveau de profondeur pour récupérer les données souhaitées.

Mikeserv
la source