Usando jq , ¿cómo se puede convertir a CSV la codificación JSON arbitraria de una matriz de objetos poco profundos?
Hay muchas preguntas y respuestas en este sitio que cubren modelos de datos específicos que codifican los campos, pero las respuestas a esta pregunta deberían funcionar dado cualquier JSON, con la única restricción de que es una matriz de objetos con propiedades escalares (no profundo / complejo / subobjetos, ya que aplanarlos es otra cuestión). El resultado debe contener una fila de encabezado con los nombres de los campos. Se dará preferencia a las respuestas que conserven el orden de campo del primer objeto, pero no es un requisito. Los resultados pueden incluir todas las celdas con comillas dobles, o solo incluir aquellas que requieran comillas (por ejemplo, 'a, b').
Ejemplos
Entrada:
[ {"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"} ]
Salida posible:
code,name,level,country NSW,New South Wales,state,AU AB,Alberta,province,CA ABD,Aberdeenshire,council area,GB AK,Alaska,state,US
Salida posible:
"code","name","level","country" "NSW","New South Wales","state","AU" "AB","Alberta","province","CA" "ABD","Aberdeenshire","council area","GB" "AK","Alaska","state","US"
Entrada:
[ {"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} ]
Salida posible:
name,value,level bang,!,0 letters,"a,b,c",0 letters,"x,y,z",1 bang,"""!""",0
Salida posible:
"name","value","level" "bang","!","0" "letters","a,b,c","0" "letters","x,y,z","1" "bang","""!""","1"
json2csv
está en stackoverflow.com/questions/57242240/…Respuestas:
Primero, obtenga una matriz que contenga todos los diferentes nombres de propiedad de objeto en su entrada de matriz de objeto. Esas serán las columnas de su CSV:
Luego, para cada objeto en la entrada de la matriz de objetos, asigne los nombres de columna que obtuvo a las propiedades correspondientes en el objeto. Esas serán las filas de su CSV.
Finalmente, coloque los nombres de las columnas antes de las filas, como un encabezado para el CSV, y pase el flujo de filas resultante al
@csv
filtro.Todos juntos ahora. Recuerde usar la
-r
bandera para obtener el resultado como una cadena sin formato:fuente
$rows
asignación de variable simplemente insertándola:(map(keys) | add | unique) as $cols | $cols, map(. as $row | $cols | map($row[.]))[] | @csv
$rows
no tiene que asignarse a una variable; Pensé que asignarlo a una variable hacía que la explicación fuera más agradable.El delgado
o:
Los detalles
Aparte
Describir los detalles es complicado porque jq está orientado a la transmisión, lo que significa que opera en una secuencia de datos JSON, en lugar de un valor único. El flujo JSON de entrada se convierte a algún tipo interno que se pasa a través de los filtros y luego se codifica en un flujo de salida al final del programa. El tipo interno no está modelado por JSON y no existe como un tipo con nombre. Se demuestra más fácilmente examinando la salida de un índice simple (
.[]
) o el operador de coma (examinarlo directamente podría hacerse con un depurador, pero eso sería en términos de los tipos de datos internos de jq, en lugar de los tipos de datos conceptuales detrás de JSON) .Tenga en cuenta que la salida no es una matriz (lo que sería
["a", "b"]
). La salida compacta (la-c
opción) muestra que cada elemento de la matriz (o argumento del,
filtro) se convierte en un objeto separado en la salida (cada uno está en una línea separada).Una secuencia es como un JSON-seq , pero utiliza nuevas líneas en lugar de RS como separador de salida cuando se codifica. En consecuencia, este tipo interno se conoce con el término genérico "secuencia" en esta respuesta, con "flujo" reservado para la entrada y salida codificadas.
Construyendo el filtro
Las claves del primer objeto se pueden extraer con:
Por lo general, las claves se mantendrán en su orden original, pero no se garantiza la conservación del orden exacto. En consecuencia, deberán usarse para indexar los objetos y obtener los valores en el mismo orden. Esto también evitará que los valores estén en las columnas incorrectas si algunos objetos tienen un orden de clave diferente.
Para generar las claves como la primera fila y hacerlas disponibles para indexación, se almacenan en una variable. La siguiente etapa de la canalización hace referencia a esta variable y usa el operador de coma para anteponer el encabezado al flujo de salida.
La expresión después de la coma es un poco complicada. El operador de índice de un objeto puede tomar una secuencia de cadenas (por ejemplo
"name", "value"
), devolviendo una secuencia de valores de propiedad para esas cadenas.$keys
es una matriz, no una secuencia, por lo que[]
se aplica para convertirla en una secuencia,que luego se puede pasar a
.[]
Esto también produce una secuencia, por lo que el constructor de la matriz se usa para convertirla en una matriz.
Esta expresión debe aplicarse a un solo objeto.
map()
se usa para aplicarlo a todos los objetos de la matriz externa:Por último, para esta etapa, esto se convierte en una secuencia para que cada elemento se convierta en una fila separada en la salida.
¿Por qué agrupar la secuencia en una matriz dentro de la
map
única para desagregarla fuera?map
produce una matriz;.[ $keys[] ]
produce una secuencia. Aplicarmap
a la secuencia de.[ $keys[] ]
produciría una matriz de secuencias de valores, pero dado que las secuencias no son de tipo JSON, en su lugar obtiene una matriz plana que contiene todos los valores.Los valores de cada objeto deben mantenerse separados, de modo que se conviertan en filas separadas en la salida final.
Finalmente, la secuencia se pasa a través del
@csv
formateador.Alterno
Los elementos se pueden separar más tarde que temprano. En lugar de usar el operador de coma para obtener una secuencia (pasando una secuencia como el operando derecho), la secuencia de encabezado (
$keys
) se puede envolver en una matriz y+
usarse para agregar la matriz de valores. Esto aún debe convertirse en una secuencia antes de pasarlo@csv
.fuente
keys_unsorted
lugar dekeys
para preservar el orden de las claves del primer objeto?[{"a":1,"b":2,"c":3}]
.Creé una función que genera una matriz de objetos o matrices en csv con encabezados. Las columnas estarían en el orden de los encabezados.
Entonces podrías usarlo así:
fuente
El siguiente filtro es ligeramente diferente porque asegurará que cada valor se convierta en una cadena. (Nota: use jq 1.5+)
Filtrar:
filter.jq
fuente
unique
se ordena de todos modos, por lo queunique|sort
se puede simplificar aunique
.-r
opción. De lo contrario, todas las citas se"
convierten en extra-escape, lo que no es un archivo CSV válido.Esta variante del programa de Santiago también es segura, pero asegura que los nombres de clave en el primer objeto se usen como encabezados de la primera columna, en el mismo orden en que aparecen en ese objeto:
fuente