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.


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


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"

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")'

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.


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.


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

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.

Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.