Usando jq para extraer valores y formatear en CSV

58

Tengo el siguiente archivo JSON:

{
"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"
    },
]
}

Me gustaría tener un archivo CSV en este formato:

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

¿Es esto posible usando solo jq? No tengo ninguna habilidad de programación.

Kerim
fuente
1
Le proporcioné una respuesta a continuación, pero ahora estoy mirando más de cerca su pregunta y no puedo evitar preguntarme: ¿de dónde se supone que proviene el 6 ° VALOR ?
mikeserv
1
Relacionado desde SO: stackoverflow.com/questions/25558456/…
Anton Tarasenko
También relacionado stackoverflow.com/q/32960857/168034
phunehehe

Respuestas:

50

jq tiene un filtro, @csv, para convertir una matriz en una cadena CSV. Este filtro tiene en cuenta la mayoría de las complejidades asociadas con el formato CSV, comenzando con comas incrustadas en los campos. (jq 1.5 tiene un filtro similar, @tsv, para generar archivos de valores separados por tabulaciones).

Por supuesto, si los encabezados y los valores están libres de comas y comillas dobles, entonces puede que no sea necesario usar el filtro @csv. De lo contrario, probablemente sería mejor usarlo.

Por ejemplo, si el 'Nombre de la compañía' fuera 'Smith, Smith y Smith', y si los otros valores fueran los que se muestran a continuación, invocar jq con la opción "-r" produciría un CSV válido:

$ 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"
pico
fuente
3
Pude 'jq somestuff | mapa (.) | @csv ', muy útil! Gracias
flickerfly
3
Su ejemplo colocará todos los nombres para mostrar en la primera línea y todos los valores en la segunda línea, en lugar de tener una línea por registro.
Brian Gordon
33

Prefiero hacer que cada registro sea una fila en mi CSV.

jq '.data | map([.displayName, .rank, .value] | join(", ")) | join("\n")'
Silas Paul
fuente
2
¿Qué pasa si .value es un número? Recibo el error "no se puede agregar la cadena y el número"
Cos
2
@Cos algo así en .value|tostringlugar del .valueejemplo anterior
matheeeny
44
@Cos, encontré que se requieren paréntesis. (.value|tostring)
ciscogambo
Además, use jq -rpara quitar las comillas
Clay
30

Dado solo este archivo, puede hacer algo como:

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

El .operador selecciona un campo de un objeto / hash. Por lo tanto, comenzamos con .data, que devuelve la matriz con los datos que contiene. Luego mapeamos dos veces la matriz, primero seleccionando el tee, luego seleccionando el valor, dándonos dos matrices con solo los valores de esas claves. Para cada matriz, unimos los elementos con "," formando dos líneas. El -rargumento le dice jqque no cite las cadenas resultantes.

Si su archivo real es más largo (es decir, tiene entradas para más de una persona), es probable que necesite algo un poco más complicado.

Steven D
fuente
No está funcionando para mí. En un tema relacionado, la respuesta stackoverflow.com/questions/32960857/… funciona y está muy bien explicada.
herve
10

Me resulta jqdifícil entenderlo. Aquí hay un poco de Ruby:

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

El analizador ruby ​​JSON vomitó sobre la coma final antes del corchete cerrado.

Glenn Jackman
fuente
2

Desde que etiquetó esto pythony suponiendo que el nombre del jsonarchivo esx.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
fuente
1

Aunque tuve que eliminar la última coma en su entrada de ejemplo para que funcione porque jqse quejaba de esperar otro elemento de matriz, esto:

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

...me consiguió...

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

Cómo funciona en pocas palabras:

  1. Atravesé el tercer nivel de objetos de datos utilizando el []formulario de índice vacío y la .dotnotación.
  2. Una vez lo suficientemente profundo, especifiqué los campos de datos que quería por nombre .[][].displayName.
  3. Me aseguré de que mis campos deseados estaban autoasociados devolviéndolos como objetos de matriz separados como [.[][].displayName], [.[][].value]
  4. Y luego canalizó esos objetos a la join(", ")función para unirlos como entidades separadas.

En verdad, hacer [.field]es simplemente otra forma de hacerlo, map(.field)pero esto es un poco más específico, ya que especifica el nivel de profundidad para recuperar los datos deseados.

mikeserv
fuente