JSON-zu-XML-Konverter
Wandelt nach XML und zeigt, was warum umbenannt wurde.
Alles läuft in diesem Tab. Nichts, was Sie einfügen, wird hochgeladen, protokolliert oder irgendwohin gesendet. Öffnen Sie Ihr Netzwerkpanel und prüfen Sie es.
Fügen Sie oben JSON ein, und daneben erscheint wohlgeformtes XML, mit Deklaration, echter Einrückung und jedem Sonderzeichen maskiert. Lässt sich das JSON nicht parsen, bekommen Sie die Meldung des Parsers selbst statt eines leeren Bereichs, und musste ein Schlüssel umbenannt werden, um ein zulässiger XML-Elementname zu werden, sagt das Werkzeug, welcher und wozu.
Sie brauchen das, wenn etwas weiter unten nur XML spricht: ein SOAP-Endpunkt, der Import eines Alt-ERP, ein per XSD geprüftes Austauschformat, eine Fixture für einen Dienst, den Sie nachbilden. Es ist die unglamouröse Hälfte des Paares und die mit den schärferen Kanten, denn XML stellt Bedingungen, die JSON nicht kennt, und diese Bedingungen müssen irgendwo aufgelöst werden.
Alles läuft in diesem Tab. Nichts wird hochgeladen, und das ist erwähnenswert: Das JSON, das Leute in Konverter einfügen, ist meist eine mitgeschnittene API-Antwort, und mitgeschnittene API-Antworten enthalten Token, Kontonummern und Kundendatensätze.
XML braucht genau eine Wurzel. JSON nicht.
RFC 8259 erlaubt auf der obersten Ebene eines JSON-Dokuments jeden Wert: ein Objekt, ein Array, eine Zeichenkette, eine Zahl, true, false oder null. XML 1.0 verlangt genau ein Wurzelelement, das alles andere enthält – dieser Unterschied wird also bei jeder Umwandlung entschieden.
Ein Objekt mit genau einem Schlüssel hat bereits eine natürliche Wurzel, dieser Schlüssel wird also zum Wurzelelement, und nichts wird erfunden. {"order": {...}} ergibt <order>...</order> ohne Hülle, und das ist der Normalfall, weil genau diese Form beim Umwandeln von XML nach JSON herauskommt. Alles andere wird eingepackt, und das Notizfeld sagt es:
- Ein Objekt mit zwei oder mehr Schlüsseln auf oberster Ebene wird in ein einzelnes Element gepackt, standardmäßig root genannt und in der Bedienzeile änderbar.
- Ein Array auf oberster Ebene wird zweifach gepackt, denn ein Array hat keinen eigenen Elementnamen: Jedes Mitglied wird zu <item> innerhalb von <root>.
- Ein Skalar auf oberster Ebene wird zum Text des Wurzelelements, das JSON-Dokument 42 ergibt also <root>42</root>.
- Ein null auf oberster Ebene ergibt ein leeres Wurzelelement, <root/>.
Arrays wiederholen den Elementnamen. Sie bekommen keine Hülle.
Das ist die Entscheidung, die die meisten Konverter verkehrt herum treffen. {"line": ["a", "b"]} wird zu zwei benachbarten <line>-Elementen, nicht zu einem <line>-Element mit zwei <item>-Kindern. Wiederholung ist die Art, wie XML eine Liste ausdrückt; genau deshalb hat die Richtung XML nach JSON überhaupt das Singleton-Problem. Eine Hülle zu erfinden erzeugt XML, das kein bestehendes Schema akzeptieren würde, und es käme nicht zurück.
Zwei Folgen ergeben sich daraus. Ein leeres Array erzeugt gar nichts, der Schlüssel verschwindet also: null Wiederholungen eines Elements sind null Elemente. Und ein Array von Arrays wird flach, weil das innere keinen vom äußeren verschiedenen Namen hat: [[1,2],[3]] unter dem Schlüssel a ergibt drei <a>-Elemente. Wenn eines davon wichtig ist, bauen Sie das JSON vorher um.
{
"order": {
"@_id": "00042",
"line": [ "Widget", "Gasket" ],
"note": null,
"meta": {},
"tags": []
}
}
<?xml version="1.0" encoding="UTF-8"?>
<order id="00042">
<line>Widget</line>
<line>Gasket</line>
<note/>
<meta/>
</order>JSON-Schlüssel sind häufig keine zulässigen XML-Namen
XML 1.0 Abschnitt 2.3 definiert einen Name als NameStartChar gefolgt von NameChars. Ein NameStartChar ist ein Buchstabe, ein Unterstrich oder ein Doppelpunkt; es ist keine Ziffer, kein Leerzeichen, kein Und-Zeichen und kein Dollarzeichen. Ein JSON-Schlüssel kennt diese Einschränkung nicht, also sind "2024 total", "user@email" und "$ref" gewöhnliche Schlüssel, und keiner davon ist ein zulässiger Elementname.
Die .NET- und XSD-Welt maskiert und macht aus einem Leerzeichen _x0020_ – exakt und unlesbar. Dieses Werkzeug bereinigt stattdessen und meldet es: Unzulässige Zeichen werden eins zu eins ersetzt statt entfernt, und ein Name, der danach noch mit einer Ziffer beginnt, bekommt ein Präfix. Genau darum geht es, denn Entfernen lässt verschiedene Schlüssel auf denselben Namen zusammenfallen, Ersetzen nicht. Jede Umbenennung erscheint im Notizfeld.
- "2024 total" wird zu _2024_total: Das Leerzeichen wird ersetzt, dann erzwingt die führende Ziffer das Präfix.
- "2024-total" wird zu _2024-total: Der Bindestrich ist bereits zulässig, nur die führende Ziffer braucht das Präfix. Beide bleiben verschieden – genau das hätte Ihnen das Entfernen gekostet.
- "user@email" wird zu user_email, "$ref" zu _ref, und ein leerer Schlüssel zu einem bloßen Unterstrich.
- Ein bereits zulässiger Schlüssel geht unverändert durch, auch einer mit Doppelpunkt: "soap:Body" bleibt "soap:Body". Das ergibt ein präfigiertes Element ohne xmlns-Deklaration – wohlgeformt, aber nicht namensraum-wohlgeformt.
Attribute, Text, und was JSON zuerst verliert
Schlüssel, die mit @_ beginnen, werden zu Attributen des umschließenden Elements, und ein Schlüssel namens #text liefert den Textinhalt. Beides passt zur Richtung XML nach JSON, die Ausgabe jener Seite lässt sich also direkt zurückwandeln. Attributwerte werden härter maskiert als Text: neben &, < und " schreibt der Writer Tabulator, Zeilenvorschub und Wagenrücklauf als numerische Zeichenreferenzen, weil XML 1.0 Abschnitt 3.3.3 wörtlichen Leerraum in Attributwerten beim erneuten Parsen zu Leerzeichen normalisiert.
Zwei Verluste passieren innerhalb von JSON selbst, bevor dieses Werkzeug beteiligt ist, und sie sehen aus wie Umwandlungsfehler. JSON-Zahlen sind IEEE-754-Doubles, ein neunzehnstelliger Bezeichner als nackte Zahl hat seine hinteren Stellen also schon verloren, bevor der Text geparst ist. Und doppelte Schlüssel löst der Parser auf, der letzte gewinnt. Dazu kommt eine JavaScript-Eigenheit: Schlüssel, die wie Array-Indizes aussehen, werden zuerst und in aufsteigender numerischer Reihenfolge aufgezählt – ein Objekt, das "2", "10" und "name" mischt, gibt seine Elemente also nicht in der geschriebenen Reihenfolge aus.
null und das leere Objekt ergeben beide <x/>, sind also nicht unterscheidbar und kommen beide als leere Zeichenkette zurück. Brauchen Sie den Unterschied, ist xsi:nil="true" die einzige normkonforme Art, „vorhanden, aber null“ zu sagen, und sie verlangt den xsi-Namensraum auf einem Vorfahren.
Dasselbe in Code
Dieselbe Umwandlung in den vier Sprachen, die am meisten XML verarbeiten, dazu PHP und ein Shell-Einzeiler. Die Sicherheitsflags zählen auf dem Rückweg: Das Parsen von JSON ist nicht das Risiko, aber Code, der JSON nach XML wandelt, parst dieses XML fast immer irgendwo wieder – und die Standardeinstellungen in Java und .NET lösen ein DOCTYPE auf, falls eines auftaucht.
import { XMLBuilder } from 'fast-xml-parser';
const builder = new XMLBuilder({
ignoreAttributes: false, // default is true: @_ keys would be dropped
attributeNamePrefix: '@_',
textNodeName: '#text',
format: true,
indentBy: ' ',
suppressEmptyNode: true, // write <note/> rather than <note></note>
processEntities: true, // escape &, < and " in values
});
const xml = '<?xml version="1.0" encoding="UTF-8"?>\n' + builder.build(data);
// XMLBuilder does not sanitise keys. A key of "2024 total" is written
// verbatim and produces XML that will not parse, so check before building:
const illegal = Object.keys(flatten(data))
.filter((k) => !/^[A-Za-z_][\w.\-]*(:[A-Za-z_][\w.\-]*)?$/.test(k));
if (illegal.length) throw new Error('Illegal XML names: ' + illegal.join(', '));import json
import re
import xmltodict
def legal_name(key):
"""Replace illegal characters rather than stripping them, so that
distinct keys stay distinct. Prefix a leading digit."""
name = re.sub(r'[^\w.\-:]', '_', key, flags=re.UNICODE)
return name if re.match(r'^[A-Za-z_:]', name) else '_' + name
def sanitise(node):
if isinstance(node, dict):
return {legal_name(k): sanitise(v) for k, v in node.items()}
if isinstance(node, list):
return [sanitise(v) for v in node]
return node
data = json.loads(json_source)
if not isinstance(data, dict) or len(data) != 1:
data = {'root': data} # xmltodict.unparse requires a single root
print(xmltodict.unparse(
sanitise(data),
pretty=True, indent=' ',
attr_prefix='@_', cdata_key='#text',
full_document=True, # emit the <?xml ...?> declaration
))
# xmltodict raises ValueError("Document must have exactly one root.") rather
# than guessing, which is correct behaviour and the reason for the wrap.import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.SerializationFeature;
import com.fasterxml.jackson.dataformat.xml.XmlMapper;
JsonNode tree = new ObjectMapper().readTree(jsonSource);
XmlMapper xml = new XmlMapper();
xml.enable(SerializationFeature.INDENT_OUTPUT);
// JSON has no root name and Jackson will not invent one, so supply it.
String out = "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n"
+ xml.writer().withRootName("root").writeValueAsString(tree);
// Two things Jackson will not do for you:
// 1. It does not sanitise names. A key with a space throws
// IllegalArgumentException at write time, which is at least loud.
// 2. It writes every value as a child element. There is no attribute
// convention on a JsonNode, so @_ keys become elements unless you bind
// to a class annotated with @JacksonXmlProperty(isAttribute = true).using System.Xml;
using Newtonsoft.Json;
// The second argument is the root element name, used when the JSON does not
// already have exactly one top-level property. Without it, multi-key JSON
// throws JsonSerializationException rather than producing invalid XML.
XmlDocument? document = JsonConvert.DeserializeXmlNode(jsonSource, "root");
if (document is null) throw new InvalidOperationException("Empty JSON.");
var settings = new XmlWriterSettings { Indent = true, IndentChars = " " };
using var writer = XmlWriter.Create(Console.Out, settings);
document.Save(writer);
// Json.NET uses "@" for attributes and "#text" for text, so retarget the
// keys if your JSON came from a converter using "@_". It does not sanitise
// names either: a property called "2024 total" throws XmlException("The ''
// character, hexadecimal value 0x20, cannot be included in a name").<?php
$data = json_decode($source, true, 512, JSON_THROW_ON_ERROR);
function legal_name(string $key): string {
$name = preg_replace('/[^\w.\-:]/u', '_', $key);
return preg_match('/^[A-Za-z_:]/', $name) ? $name : '_' . $name;
}
function write_node(XMLWriter $w, string $name, mixed $value): void {
if (is_array($value) && array_is_list($value)) {
foreach ($value as $v) write_node($w, $name, $v); // repeat, no wrapper
return;
}
$w->startElement(legal_name($name));
if (is_array($value)) {
foreach ($value as $k => $v) {
if (str_starts_with((string) $k, '@_')) {
$w->writeAttribute(legal_name(substr((string) $k, 2)), (string) $v);
} elseif ($k === '#text') {
$w->text((string) $v);
} else {
write_node($w, (string) $k, $v);
}
}
} elseif ($value !== null) {
$w->text(is_bool($value) ? ($value ? 'true' : 'false') : (string) $value);
}
$w->endElement();
}
$single = count($data) === 1;
$w = new XMLWriter();
$w->openMemory();
$w->setIndent(true);
$w->setIndentString(' ');
$w->startDocument('1.0', 'UTF-8');
write_node($w, $single ? (string) array_key_first($data) : 'root',
$single ? reset($data) : $data);
$w->endDocument();
echo $w->outputMemory();# yq v4 (Mike Farah). JSON is a subset of YAML, so -p=json works directly.
yq -p=json -o=xml '.' payload.json
# Set the root and the key conventions to match this page:
yq -p=json -o=xml \
--xml-attribute-prefix='@_' \
--xml-content-name='#text' \
'{"root": .}' payload.json
# yq writes no XML declaration, so prepend one if a consumer expects it, and
# check the result: yq does not sanitise element names.
{ echo '<?xml version="1.0" encoding="UTF-8"?>'
yq -p=json -o=xml '{"root": .}' payload.json; } | xmllint --noout --nonet -Beachten Sie, was keine dieser Bibliotheken tut: einen Schlüssel zu einem zulässigen XML-Namen bereinigen. Jackson, Json.NET und XMLBuilder werfen entweder eine Ausnahme oder geben XML aus, das sich nicht parsen lässt, und yq gibt es stillschweigend aus. Stammen Ihre JSON-Schlüssel aus Benutzereingaben, einer Spaltenliste oder einer Tabellen-Kopfzeile, ist der Bereinigungsschritt Ihre Aufgabe – und Zeichen zu ersetzen statt zu löschen ist das, was zwei ähnliche Schlüssel davor bewahrt, ein Element zu werden.
Häufige Fragen
Verlässt mein JSON den Browser?
Nein. JSON-Parser, Namensbereiniger und XML-Writer sind allesamt JavaScript, das in diesem Tab läuft, und es gibt keine serverseitige Komponente, mit der sie sprechen könnten. Öffnen Sie das Netzwerkpanel und wandeln Sie etwas um; es wird nichts angefordert.
In dieser Richtung zählt es am meisten. In einen Konverter eingefügtes JSON ist meist eine beim Debuggen mitgeschnittene Antwort einer produktiven API, samt Access Token oder vollständigem Kundendatensatz. Mehrere Werkzeuge, die für diese Suche ranken, schicken diese Nutzlast an einen Server, und eines veröffentlicht gespeicherte Dokumente unter einer erratbaren URL.
Warum ist mein JSON in ein <root>-Element gepackt?
Weil XML genau ein Wurzelelement erlaubt und Ihr JSON mehr als einen Schlüssel auf oberster Ebene hatte, ein Array war oder ein nackter Skalar. Zwei benachbarte Wurzeln lassen sich in XML nicht schreiben.
Ein Objekt mit genau einem Schlüssel bleibt unangetastet: Dieser Schlüssel wird zur Wurzel, und es kommt keine Hülle dazu. {"order": {...}} ergibt also <order>, während ein zweiter Schlüssel auf oberster Ebene <root> ergibt. Der Name der Hülle lässt sich in der Bedienzeile ändern. Geht das XML irgendwohin, wo validiert wird, setzen Sie ihn auf das, was das Schema erwartet.
Wie werden JSON-Arrays umgewandelt?
Indem der Elementname je Mitglied einmal wiederholt wird, ohne Hülle. {"line": ["a", "b"]} erzeugt zwei <line>-Elemente nebeneinander. So stellt XML eine Liste dar, und deshalb lässt sich die Ausgabe zurückwandeln. Manche Konverter erzeugen stattdessen <line><item>a</item><item>b</item></line>, was sich mehr nach dem JSON liest und an jedem Schema scheitert, das für echtes XML geschrieben wurde.
Zwei Randfälle folgen daraus. Ein leeres Array gibt nichts aus, der Schlüssel verschwindet also, und ein direkt in einem anderen verschachteltes Array wird flach, weil das innere keinen eigenen Namen hat.
Was passiert mit Schlüsseln, die keine gültigen XML-Elementnamen sind?
Sie werden umbenannt, und jede Umbenennung steht im Notizfeld neben der Ausgabe. Unzulässige Zeichen werden eins zu eins durch einen Unterstrich ersetzt, und ein Name, der danach noch mit einer Ziffer beginnt, bekommt einen Unterstrich davor.
Ersetzen statt Entfernen ist Absicht: Entfernen würde "2024 total" und "2024total" zum selben Element machen und zwei verschiedene Felder verschmelzen. Eine perfekte Injektion ist es dennoch nicht: "first name" und "first_name" werden beide zu first_name, weil der Unterstrich im zweiten schon zulässig war.
Wie bekomme ich Attribute statt Kindelemente?
Stellen Sie dem Schlüssel @_ voran. {"user": {"@_id": "7", "name": "Alice"}} erzeugt <user id="7"><name>Alice</name></user>. Das Präfix lässt sich über dem Editor ändern; leeren Sie es, wird nie etwas als Attribut geschrieben.
Legen Sie dort nur Skalare ab. Der Wert wird in eine Zeichenkette umgewandelt, ein Objekt unter einem @_-Schlüssel wird also zum nutzlosen Text [object Object]. Eines tut dieser Writer, was die meisten nicht tun: Tabulatoren, Zeilenumbrüche und Wagenrückläufe in einem Attributwert werden als numerische Zeichenreferenzen geschrieben, ein mehrzeiliger Wert übersteht also ein erneutes Parsen, statt zu Leerzeichen eingeebnet zu werden.
Kann ich zurück nach JSON wandeln und bekomme, womit ich angefangen habe?
Für die meisten Dokumente ja, wenn Sie die Seite XML nach JSON mit denselben @_- und #text-Einstellungen benutzen. Wegen dieser Paarung wurden diese Standardwerte gewählt.
Vier Dinge überleben nicht. null und {} werden beide zu <x/> und kommen als leere Zeichenkette zurück. Ein leeres Array verschwindet ganz. Die Zahlentypen von JSON sind weg, weil XML keine Typen hat, 42 kommt also als "42" zurück, sofern Sie die Umwandlung nicht einschalten. Und die Schlüsselreihenfolge ist in keinem der beiden Formate bedeutungstragend. Ist exaktes Zurückwandeln Bedingung, behalten Sie das XML als Quelle der Wahrheit und lesen Sie mit XPath daraus.