hilbix / xml2json

Convert XML to JSON (uses python3)
0 stars 0 forks source link

Preservation of XML order #1

Open MilesTails01 opened 1 year ago

MilesTails01 commented 1 year ago

# <a><b>x</b><c>y</c><b>z</b></a> becomes {"a":{"b":["x","z"],"c":"y"}}

Ich glaube die Reihenfolge spielt bei xml eine Rolle. Bei dictonaries wird zwar mittlerweile die insert order gewährleistet, aber in dem fall werden bei b die Elemente kombiniert und verlieren so ihre ursprüngliche position.

wenn man also versucht JSON in xml zu konvertieren

{"a":{"b":["x","z"],"c":"y"}} ==> <a><b>x</b><b>z</b><c>y</c></a>

würde sich die reihenfolge mit dem orginal unterscheiden

<a><b>x</b><b>z</b><c>y</c></a>
<a><b>x</b><c>y</c><b>z</b></a>

man müsste die order wahrscheinlich mit ausgeben in die json, oder man wrappt die unterobjekte nochmal in einen array ein.

hilbix commented 1 year ago

For the benefit of the reader:

Tommy explains, that the output of xml2json does not preserve sub-element orders the way it parses XML.

This is intended, hence it is a "non preserving" parser. JSON object keys must be unique. To keep a high usability of the parser, this information gets lost, in favor of easy post-processing with jq.

For me a much bigger problem is, that it is nonincremental. I.E. some scenario of a SoC with 1 KiB RAM only, which has to extract a small value out of a 1 MiB XML stream. Also xml2json.py is not in C nor some other language which is easily embedable into such limited SoCs.

hilbix commented 1 year ago

Richtig erkannt und sollte vielleicht etwas klarer kommuniziert werden.

Ganz am Anfang vom README und am Ende vom Kommentar steht ein entsprechender Hinweis. Ich habe die entscheidenden Worte mal markiert:

Das bedeutet, aus dem Output vom xml2json kann man das XML nicht wieder korrekt rekonstruieren. Wenn man das will, ist dieser Parser einfach nicht dafür gedacht. Deswegen steht im Kommentar auch genau das zitierte Beispiel:

# <a><b>x</b><c>y</c><b>z</b></a> becomes {"a":{"b":["x","z"],"c":"y"}}

Es steht aber nicht da, um das "non-preserving" zu erklären, sondern dass multiple gleiche Unterentitäten in ein Array zusammengefasst werden (im Gegensatz dazu sind singuläre Unterentitäten kein Array!).

Da XML keine eigene Entsprechung eines Arrays hat (wird immer in eine Abfolge gleicher Unterentitäten abgebildet) ist das IMHO der beste Weg, um die Dinge hinterher auf einfache Weise mit jq wieder zu parsen.

Die Frage ist also hier weniger, dass das man hier keine bijektive Abbildung XML <-> JSON hat, sondern, dass man für a.b folgende 3 verschiedene Varianten hat statt 2:

XML jq -c .a.b von mir erwartet Alternative .a[0].b
<a><b>c</b><b>d</b></a> ["c","d"] ja
<a><b>c</b></a> "c" ja ["c"]
<a><b/></a> "" ja ja []
<a/> jq: error (at :1): Cannot index string with string "b" ja keine, aber siehe unten

Das sind 3 Varianten in 4 Darstellungen.

Das Problem bei letzterem ist, dass .a als String ausgegeben wird und nicht als Object, was ich aber recht gut finde, so dass das nachgelagerte Parsing auseinandersemmelt wenn da etwas im Input fehlt.

Ohne den Parser exorbitant zu verkomplizieren, kann man den Standardfall, für den ich den Parser gebaut habe ("ich möchte auf einfachste Weise einen Eintrag irgendwo tief aus einem XML extrahieren") nichtmehr abbilden.

Wären Entitäten z.B. immer ein Array (siehe "Alternative" oben), wird aus .a.b.c plötzlich .a[0].b[0].c[0]. Da brechen bei mir schon gedanklich alle Finger (und ich verwende nicht einmal die Deutsche Tastatur auf der [ nur mit Verrenkungen zu erreichen ist, sondern US-Layout, bei dem es bequem auf einer Taste liegt).

Aber die Alternative hätte den Vorteil, dass man in jq eine recht einfache Formel verweden könnte, um in letzterem Fall ein null (oder anderen Defaultwert) auszugeben:

.a[0] // {b:[null]} | .b (habe ich nicht ausprobiert, evtl. habe ich die Syntax verkackt)

ABER dann würden sich tyische Abfragen exorbitant verkomplizieren (.a[0].b[0].c[0]), nur zu dem Preis, dass ein evtl. niemals wirklich auftretender Fall leichter abgedeckt werden kann.

Ich halte es sogar für vorteilhaft, dass es hier 3 Fälle (Array, String, jq-Fehler) gibt statt nur 2 (Array, Fehler), denn ich verwende es meistens so:

$ ./xml2json.py <<<'<a><b>c</b></a>' | jq -r .a.b` | cat
c
$ ./xml2json.py <<<'<a><b>c</b><b>d</b></a>' | jq -r .a.b | cat
[
  "c",
  "d"
]

Das lässt sich gut bei der Weiterverarbeitung unterscheiden!

Useless use of cat? Was hat das | cat da zu suchen?

Einige Tools verändern ihren Output wenn sie auf ein tty ausgeben. jq gehört dazu!

ABER:

./xml2json.py <<<$'<a><b>[\n  "c",\n  "d"\n]</b></a>' | jq -r .a.b | cat
[
  "c",
  "d"
]

Das sehe ich trotzdem nicht als Problem an, denn:

$ ./xml2json.py <<<'<a><b>c</b><b>d</b></a>' | jq -r -c .a.b | cat
["c","d"]
$ ./xml2json.py <<<$'<a><b>[\n  "c",\n  "d"\n]</b></a>' | jq -r -c .a.b | cat
[
  "c",
  "d"
]

Sprich:

Noch etwas:

Der Shell-POSIX-Standard ist Scheiße:

$ x="$(echo a)"; y="$(echo $'a\n')"; test ".$a" = ".$b"; echo $?
0
a="$(echo a && echo x)"; b="$(echo $'a\n' && echo x)"; test ".$a" = ".$b"; echo $?
1

Das ist ein echtes Problem, weil:

echo Hello >-a
echo World >$'-a\n'
for a in -a*; do cat -- "`basename -- "$a"`"; done

gibt aus

Hello
Hello

Ich habe so gut wie kein Script bisher gefunden, das basename etc. verwendet und NICHT auf diesen Trick reinfällt! Dieses winzige Detail erzeugt auf der Welt vermutlich mehr Bugs als irgendein anderes jemals kreiertes Feature! Aber dieser Murx ist halt Standart.

Was passiert, wenn man das -- beim basename oder cat vergisst?

Dieser Bug ist auch in Myriarden von Scripts enthalten, aber nicht ganz so fatal wie das POSIX-"Feature"

hilbix commented 1 year ago

Anmerkung: Ich lasse das offen bis es besser dokumentiert ist oder ich eine bessere Alternative finde.