De volgende tocsv
en fromcsv
functies bieden een oplossing voor het genoemde probleem met uitzondering van een complicatie met betrekking tot eis (6) met betrekking tot de headers. In wezen kan aan deze eis worden voldaan met behulp van de hier gegeven functies door een matrixtransponeringsstap toe te voegen.
Of er nu wel of niet een transpositiestap wordt toegevoegd, het voordeel van de hier gevolgde aanpak is dat er geen beperkingen zijn op de JSON-sleutels of -waarden. Ze kunnen met name punten (punten), nieuwe regels en/of NUL-tekens bevatten.
In het voorbeeld wordt een array van objecten gegeven, maar in feite kan elke stroom van geldige JSON-documenten worden gebruikt als invoer voor tocsv
; dankzij de magie van jq wordt de originele stream opnieuw gemaakt door fromcsv
(in de zin van gelijkheid per entiteit).
Omdat er geen CSV-standaard is, wordt natuurlijk de CSV geproduceerd door detocsv
functie wordt mogelijk niet door alle CSV-processors begrepen. Houd er in het bijzonder rekening mee dat de tocsv
functie die hier is gedefinieerd, wijst ingesloten nieuwe regels in JSON-tekenreeksen of sleutelnamen toe aan de tekenreeks "\n" van twee tekens (d.w.z. een letterlijke backslash gevolgd door de letter "n"); de inverse bewerking voert de inverse vertaling uit om te voldoen aan de "round-trip" vereiste.
(Het gebruik van tail
is alleen om de presentatie te vereenvoudigen; het zou beter zijn om de oplossing aan te passen om er een alleen-jq-oplossing van te maken.)
De CSV wordt gegenereerd in de veronderstelling dat elke waarde in een veld kan worden opgenomen zolang (a) het veld tussen aanhalingstekens staat en (b) dubbele aanhalingstekens in het veld worden verdubbeld.
Elke generieke oplossing die "round-trips" ondersteunt, zal ongetwijfeld wat gecompliceerd zijn. De belangrijkste reden waarom de hier gepresenteerde oplossing complexer is dan men zou verwachten, is omdat er een derde kolom is toegevoegd, deels om het onderscheid te maken tussen gehele getallen en strings met een geheel getal, maar vooral omdat het gemakkelijk is om onderscheid te maken tussen de grootte-1 en de grootte. -2 arrays geproduceerd door jq's--stream
keuze. Onnodig te zeggen dat er andere manieren zijn waarop deze problemen kunnen worden aangepakt; het aantal oproepen naar jq kan ook worden verminderd.
De oplossing wordt gepresenteerd als een testscript dat de retourvereiste controleert op een veelzeggende testcase:
#!/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)
Hier is de CSV die zou worden geproduceerd door json | tocsv
, behalve dat SO letterlijke NUL's lijkt te verbieden, dus ik heb dat vervangen door \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