Następujące tocsv
i fromcsv
Funkcje dostarczają rozwiązania podanego problemu, z wyjątkiem jednej komplikacji dotyczącej wymagania (6) dotyczącego nagłówków. Zasadniczo to wymaganie można spełnić za pomocą funkcji podanych tutaj, dodając krok transpozycji macierzy.
Niezależnie od tego, czy zostanie dodany krok transpozycji, zaletą przyjętego tutaj podejścia jest brak ograniczeń dotyczących kluczy lub wartości JSON. W szczególności mogą zawierać kropki (kropki), znaki nowej linii i/lub znaki NUL.
W tym przykładzie podana jest tablica obiektów, ale w rzeczywistości dowolny strumień poprawnych dokumentów JSON może być użyty jako dane wejściowe do tocsv
; dzięki magii jq oryginalny strumień zostanie odtworzony przez fromcsv
(w sensie równości jednostka po jednostce).
Oczywiście, ponieważ nie ma standardu CSV, plik CSV utworzony przez tocsv
funkcja może nie być zrozumiana przez wszystkie procesory CSV. W szczególności należy pamiętać, że tocsv
zdefiniowana tutaj funkcja mapuje osadzone znaki nowej linii w ciągach JSON lub nazwach kluczy na dwuznakowy ciąg „\n” (tj. dosłowny ukośnik odwrotny, po którym następuje litera „n”); operacja odwrotna wykonuje odwrotną translację w celu spełnienia „podróży w obie strony” wymagania.
(Użycie tail
jest tylko uproszczenie prezentacji; zmodyfikowanie rozwiązania tak, aby było to tylko jedno-jq.)
Plik CSV jest generowany przy założeniu, że dowolna wartość może być zawarta w polu, o ile (a) pole jest ujęte w cudzysłów i (b) cudzysłowy w polu są podwojone.
Każde ogólne rozwiązanie, które obsługuje „podróże w obie strony”, musi być nieco skomplikowane. Głównym powodem, dla którego przedstawione tutaj rozwiązanie jest bardziej złożone niż można by się spodziewać, jest to, że dodano trzecią kolumnę, częściowo po to, aby ułatwić rozróżnienie między liczbami całkowitymi a ciągami o wartościach całkowitych, ale głównie dlatego, że ułatwia rozróżnienie między rozmiarem 1 a rozmiarem -2 tablice utworzone przez jq's--stream
opcja. Nie trzeba dodawać, że istnieją inne sposoby rozwiązania tych problemów; można również zmniejszyć liczbę połączeń z jq.
Rozwiązanie jest prezentowane jako skrypt testowy, który sprawdza wymaganie w obie strony w wymownym przypadku testowym:
#!/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)
Oto plik CSV, który zostałby utworzony przez json | tocsv
, z wyjątkiem tego, że SO wydaje się nie zezwalać na dosłowne NUL, więc zastąpiłem to przez \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