O seguinte
tocsv
e fromcsv
As funções fornecem uma solução para o problema declarado, exceto por uma complicação em relação ao requisito (6) referente aos cabeçalhos. Essencialmente, esse requisito pode ser atendido usando as funções fornecidas aqui, adicionando uma etapa de transposição de matriz. Independentemente de uma etapa de transposição ser adicionada ou não, a vantagem da abordagem adotada aqui é que não há restrições nas chaves ou valores JSON. Em particular, podem conter pontos (pontos), novas linhas e/ou caracteres NUL.
No exemplo, uma matriz de objetos é fornecida, mas, na verdade, qualquer fluxo de documentos JSON válidos pode ser usado como entrada para
tocsv
; graças à magia do jq, o stream original será recriado por fromcsv
(no sentido de igualdade entidade por entidade). Claro, como não existe um padrão CSV, o CSV produzido pelo
tocsv
função pode não ser compreendida por todos os processadores CSV. Em particular, observe que o tocsv
função definida aqui mapeia novas linhas incorporadas em strings JSON ou nomes de chave para a string de dois caracteres "\n" (ou seja, uma barra invertida literal seguida pela letra "n"); a operação inversa executa a tradução inversa para atender a "ida e volta" requerimento. (O uso de
tail
é apenas simplificar a apresentação; seria trivial modificar a solução para torná-la apenas jq.) O CSV é gerado na suposição de que qualquer valor pode ser incluído em um campo desde que (a) o campo seja citado e (b) as aspas duplas dentro do campo sejam duplicadas.
Qualquer solução genérica que suporte "viagens de ida e volta" certamente será um pouco complicada. A principal razão pela qual a solução apresentada aqui é mais complexa do que se poderia esperar é porque uma terceira coluna é adicionada, em parte para facilitar a distinção entre inteiros e strings de valor inteiro, mas principalmente porque facilita a distinção entre o tamanho 1 e o tamanho -2 arrays produzidos por
--stream
do jq opção. Desnecessário dizer que existem outras maneiras de abordar essas questões; o número de chamadas para jq também pode ser reduzido. A solução é apresentada como um script de teste que verifica o requisito de ida e volta em um caso de teste revelador:
#!/bin/bash
function json {
cat<<EOF
[
{
"a": 1,
"b": [
1,
2,
"1"
],
"c": "d\",ef",
"embed\"ed": "quote",
"null": null,
"string": "null",
"control characters": "a\u0000c",
"newline": "a\nb"
},
{
"x": 1
}
]
EOF
}
function tocsv {
jq -ncr --stream '
(["path", "value", "stringp"],
(inputs | . + [.[1]|type=="string"]))
| map( tostring|gsub("\"";"\"\"") | gsub("\n"; "\\n"))
| "\"\(.[0])\",\"\(.[1])\",\(.[2])"
'
}
function fromcsv {
tail -n +2 | # first duplicate backslashes and deduplicate double-quotes
jq -rR '"[\(gsub("\\\\";"\\\\") | gsub("\"\"";"\\\"") ) ]"' |
jq -c '.[2] as $s
| .[0] |= fromjson
| .[1] |= if $s then . else fromjson end
| if $s == null then [.[0]] else .[:-1] end
# handle newlines
| map(if type == "string" then gsub("\\\\n";"\n") else . end)' |
jq -n 'fromstream(inputs)'
}
# Check the roundtrip:
json | tocsv | fromcsv | jq -s '.[0] == .[1]' - <(json)
Aqui está o CSV que seria produzido por
json | tocsv
, exceto que SO parece não permitir NULs literais, então eu substituí isso por \0
:"path","value",stringp
"[0,""a""]","1",false
"[0,""b"",0]","1",false
"[0,""b"",1]","2",false
"[0,""b"",2]","1",true
"[0,""b"",2]","false",null
"[0,""c""]","d"",ef",true
"[0,""embed\""ed""]","quote",true
"[0,""null""]","null",false
"[0,""string""]","null",true
"[0,""control characters""]","a\0c",true
"[0,""newline""]","a\nb",true
"[0,""newline""]","false",null
"[1,""x""]","1",false
"[1,""x""]","false",null
"[1]","false",null