XML ↔ JSON Converter — Java source
Convert XML to JSON and back, preserving attributes. Validates input and reports errors clearly, runs entirely in your browser, with a shareable link to your exact input.
This is the Java implementation — the same logic the interactive tool runs, in a shareable, citable form.
// xml-to-json — bidirectional XML <-> JSON converter. Java (17) port of src/lib/xml-to-json.ts — same logic as this dir's rust.rs (Go-twin contract); JSON -> XML and entity decoding omitted for the 80-line budget (see rust.rs / python.py).
import java.io.StringReader;
import java.util.ArrayList;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import javax.xml.parsers.DocumentBuilderFactory;
import org.w3c.dom.*;
import org.xml.sax.InputSource;
final class XmlJson {
// Element -> pretty 2-space JSON written straight to `out`: attributes
// become "@_<name>" keys, a text-only element becomes its bare string, an
// element with attributes/children becomes an object with direct text
// under "#text", and repeated child tags collapse into an array.
private static void writeValue(Element el, StringBuilder out, int depth) {
List<Element> kids = new ArrayList<>();
StringBuilder text = new StringBuilder();
for (Node n = el.getFirstChild(); n != null; n = n.getNextSibling()) {
if (n instanceof Element e) kids.add(e);
else if (n.getNodeType() == Node.TEXT_NODE || n.getNodeType() == Node.CDATA_SECTION_NODE)
text.append(n.getTextContent()); // direct text only (skip comments/PIs)
}
NamedNodeMap attrs = el.getAttributes();
String trimmed = text.toString().trim(), ind = " ".repeat(depth + 1);
if (kids.isEmpty() && attrs.getLength() == 0) {
out.append('"').append(esc(trimmed)).append('"'); // bare string
return;
}
out.append('{');
Map<String, List<Element>> groups = new LinkedHashMap<>(); // document order
for (Element k : kids)
groups.computeIfAbsent(k.getTagName(), x -> new ArrayList<>()).add(k);
boolean first = true;
for (int i = 0; i < attrs.getLength(); i++) {
Node a = attrs.item(i);
out.append(first ? "\n" : ",\n").append(ind).append("\"@_").append(a.getNodeName())
.append("\": \"").append(esc(a.getNodeValue())).append('"');
first = false;
}
for (Map.Entry<String, List<Element>> g : groups.entrySet()) {
out.append(first ? "\n" : ",\n").append(ind).append('"').append(esc(g.getKey())).append("\": ");
first = false;
List<Element> list = g.getValue();
if (list.size() == 1) writeValue(list.get(0), out, depth + 1);
else { // repeated tags -> array
out.append("[\n");
for (int j = 0; j < list.size(); j++) {
if (j > 0) out.append(",\n");
out.append(" ".repeat(depth + 2));
writeValue(list.get(j), out, depth + 2);
}
out.append('\n').append(ind).append(']');
}
}
if (!trimmed.isEmpty())
out.append(first ? "\n" : ",\n").append(ind).append("\"#text\": \"").append(esc(trimmed)).append('"');
out.append('\n').append(" ".repeat(depth)).append('}');
}
private static String esc(String s) {
return s.replace("\\", "\\\\").replace("\"", "\\\"").replace("\n", "\\n")
.replace("\r", "\\r").replace("\t", "\\t");
}
// Parse well-formed XML to pretty JSON, or null if the DOM parser rejects
// it (mismatched tags, no root, trailing junk all throw here).
static String xmlToJson(String xml) {
try {
Element root = DocumentBuilderFactory.newInstance().newDocumentBuilder()
.parse(new InputSource(new StringReader(xml))).getDocumentElement();
StringBuilder out = new StringBuilder("{\n \"").append(esc(root.getTagName())).append("\": ");
writeValue(root, out, 1);
return out.append("\n}").toString();
} catch (Exception e) {
return null;
}
}
public static void main(String[] args) {
System.out.println(xmlToJson("<user id=\"7\"><name>Alice</name><pet>cat</pet><pet>dog</pet></user>"));
System.out.println(xmlToJson("<a><b></a>")); // mismatched close -> null
}
}
Also available in 13 other languages
Every CosmoDev tool ships its pure logic in TypeScript (web) and Go (CLI), with authored implementations in a dozen-plus languages — the same contract, ported. Compare all languages side by side →