Následující tocsv
a fromcsv
funkce poskytují řešení uvedeného problému s výjimkou jedné komplikace týkající se požadavku (6) týkajícího se záhlaví. Tento požadavek lze v podstatě splnit pomocí zde uvedených funkcí přidáním kroku transpozice matice.
Bez ohledu na to, zda je přidán krok transpozice, výhodou tohoto přístupu je, že neexistují žádná omezení pro klíče nebo hodnoty JSON. Zejména mohou obsahovat tečky (tečky), nové řádky a/nebo znaky NUL.
V příkladu je uvedeno pole objektů, ale ve skutečnosti lze jako vstup do tocsv
použít jakýkoli proud platných dokumentů JSON; díky kouzlu jq bude původní stream znovu vytvořen pomocí fromcsv
(ve smyslu rovnosti entit po entitě).
Vzhledem k tomu, že neexistuje žádný standard CSV, je samozřejmě CSV vytvářený souborem tocsv
funkce nemusí být srozumitelná všem procesorům CSV. Zejména mějte na paměti, že tocsv
zde definovaná funkce mapuje vložené nové řádky v řetězcích JSON nebo názvech klíčů na dvouznakový řetězec „\n“ (tj. doslovné zpětné lomítko následované písmenem „n“); inverzní operace provede inverzní překlad, aby splnila „zpáteční cestu“ požadavek.
(Použití tail
slouží pouze ke zjednodušení prezentace; bylo by triviální upravit řešení tak, aby bylo pouze jq.)
CSV je generován za předpokladu, že do pole může být zahrnuta jakákoli hodnota, pokud (a) pole je uvozováno a (b) dvojité uvozovky v poli jsou zdvojené.
Jakékoli generické řešení, které podporuje „okružní cesty“, musí být poněkud komplikované. Hlavním důvodem, proč je zde prezentované řešení složitější, než by se dalo očekávat, je ten, že byl přidán třetí sloupec, částečně proto, aby bylo snadné rozlišovat mezi celými čísly a řetězci s celočíselnou hodnotou, ale hlavně proto, že usnadňuje rozlišení mezi velikostí-1 a velikostí. -2 pole vytvořená jq--stream
volba. Netřeba dodávat, že existují i jiné způsoby, jak tyto problémy řešit; počet volání jq by se také mohl snížit.
Řešení je prezentováno jako testovací skript, který kontroluje požadavek na zpáteční cestu na výmluvném testovacím případu:
#!/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)
Zde je soubor CSV, který by vytvořil json | tocsv
, kromě toho, že SO zřejmě nepovoluje doslovné NUL, takže jsem to nahradil \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