web-dev-qa-db-fra.com

Utilisation de jq pour extraire des valeurs et formater en CSV

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 uniquement jq? Je n'ai aucune compétence en programmation.

62
Kerim

jq possède 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 les virgules intégré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, si les en-têtes et les valeurs sont tous garantis exempts de virgules et de guillemets doubles, il peut ne pas être nécessaire d'utiliser le filtre @csv. Sinon, il serait probablement préférable de l'utiliser.

Par exemple, si le "Nom de l'entreprise" é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 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"
55
peak

Je préfère faire de chaque enregistrement une ligne dans mon CSV.

jq '.data | map([.displayName, .rank, .value] | join(", ")) | join("\n")'
36
Silas Paul

Étant donné uniquement ce fichier, vous pouvez faire quelque chose comme:

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

L'opérateur . Sélectionne un champ dans un objet/hachage. Ainsi, nous commençons par .data, Qui renvoie le tableau avec les données qu'il contient. Nous mappons ensuite le tableau deux fois, en sélectionnant d'abord displayName, puis en sélectionnant la valeur, en nous donnant deux tableaux avec juste les valeurs de ces clés. Pour chaque tableau, nous joignons les éléments avec "," formant deux lignes. L'argument -r Indique à jq de ne pas citer les chaînes résultantes.

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

31
Steven D

J'ai trouvé jq difficile d'envelopper ma tête. 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 JSON a échangé sur la virgule de fin avant le crochet de fermeture.

11
glenn jackman

Puisque vous avez marqué ce python et que le nom du fichier json est x.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
2
iruvar

Bien que j'ai dû supprimer la dernière virgule dans votre exemple d'entrée pour le faire fonctionner parce que jq se plaignait d'attendre un autre élément du 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 cela fonctionne en un mot:

  1. J'ai parcouru le troisième niveau d'objets de données en utilisant le formulaire de champ d'index [] Vide et la notation .dot.
  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 en tant qu'objets de tableau séparés comme [.[][].displayName], [.[][].value]
  4. Et puis redirigé ces objets vers la fonction join(", ") à joindre en tant qu'entités distinctes.

En vérité, faire [.field] N'est qu'une autre façon de map(.field) mais c'est un peu plus spécifique en ce qu'il spécifie le niveau de profondeur pour récupérer les données souhaitées.

1
mikeserv