Следният tocsv
и fromcsv
функции предоставят решение на посочения проблем с изключение на едно усложнение по отношение на изискване (6) относно заглавките. По същество това изискване може да бъде изпълнено с помощта на дадените тук функции чрез добавяне на стъпка за транспониране на матрица.
Независимо дали е добавена или не стъпка на транспониране, предимството на възприетия тук подход е, че няма ограничения за JSON ключовете или стойностите. По-специално, те могат да съдържат точки (точки), нови редове и/или NUL знаци.
В примера е даден масив от обекти, но всъщност всеки поток от валидни JSON документи може да се използва като вход към tocsv
; благодарение на магията на jq, оригиналният поток ще бъде пресъздаден от fromcsv
(в смисъл на равенство на обект по обект).
Разбира се, тъй като няма CSV стандарт, CSV, произведен от tocsv
функцията може да не се разбира от всички CSV процесори. По-специално, имайте предвид, че tocsv
функцията, дефинирана тук, картографира вградени нови редове в JSON низове или имена на ключове към низа от два знака "\n" (т.е. буквална обратна наклонена черта, последвана от буквата "n"); обратната операция извършва обратния превод, за да отговори на "обръщането" изискване.
(Използването на tail
е само за опростяване на представянето; би било нетривиално да модифицирате решението, за да го направите само-jq.)
CSV се генерира при допускането, че всяка стойност може да бъде включена в поле, стига (a) полето да е в кавички и (b) двойните кавички в полето да са удвоени.
Всяко генерично решение, което поддържа "обиколни пътувания", е обвързано да бъде донякъде сложно. Основната причина, поради която представеното тук решение е по-сложно, отколкото може да се очаква, е, че е добавена трета колона, отчасти за да улесни разграничаването между цели числа и низове с цели числа, но главно защото улеснява разграничаването между size-1 и size -2 масива, произведени от jq's--stream
опция. Излишно е да казвам, че има други начини, по които тези проблеми могат да бъдат адресирани; броят на обажданията до jq също може да бъде намален.
Решението е представено като тестов скрипт, който проверява двупосочното изискване на показателен тестов случай:
#!/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)
Ето CSV, който ще бъде произведен от json | tocsv
, с изключение на това, че SO изглежда забранява буквалните NUL, така че го замених с \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