XML ↔ JSON Converter — C++ 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 C++ implementation — the same logic the interactive tool runs, in a shareable, citable form.
// xml-to-json — bidirectional XML <-> JSON converter. C++ (17) port of src/lib/xml-to-json.ts — same logic as this dir's rust.rs (Go-twin contract); JSON -> XML, prolog, and entity decoding omitted for the 80-line budget (see rust.rs / python.py).
#include <cctype>
#include <cstdio>
#include <memory>
#include <optional>
#include <string>
#include <vector>
struct El { // attributes + children in document order, direct text
std::string tag;
std::vector<std::pair<std::string, std::string>> attrs;
std::vector<std::shared_ptr<El>> kids;
std::string text;
};
struct P { const char* p; };
static void skip_ws(P& p) { while (*p.p && std::isspace((unsigned char)*p.p)) p.p++; }
static std::string parse_name(P& p) { // [A-Za-z_:][A-Za-z0-9_.:-]*
const char* s = p.p;
if (!(std::isalpha((unsigned char)*s) || *s == '_' || *s == ':')) return "";
for (s++; std::isalnum((unsigned char)*s) || *s == '_' || *s == ':' || *s == '.' || *s == '-'; s++) {}
std::string out(p.p, s); p.p = s; return out;
}
static std::string q(const std::string& s) { // JSON string literal, minimal escaping
std::string o = "\"";
for (char c : s) {
if (c == '"' || c == '\\') o += '\\';
else if (c == '\n') { o += "\\n"; continue; }
o += (static_cast<unsigned char>(c) < 0x20) ? ' ' : c;
}
return o + '"';
}
// Parse one element (cursor on '<'); nullptr = malformed.
static std::shared_ptr<El> parse_element(P& p) {
auto e = std::make_shared<El>();
p.p++;
e->tag = parse_name(p);
if (e->tag.empty()) return nullptr;
for (;;) { // attributes until '>' or '/>'
skip_ws(p);
if (*p.p == '/') { if (p.p[1] != '>') return nullptr; p.p += 2; return e; }
if (*p.p == '>') { p.p++; break; }
std::string nm = parse_name(p); skip_ws(p);
if (nm.empty() || *p.p != '=') return nullptr;
p.p++; skip_ws(p);
if (*p.p != '"' && *p.p != '\'') return nullptr;
char quote = *p.p++; const char* v = p.p;
while (*p.p && *p.p != quote) p.p++;
if (!*p.p) return nullptr;
e->attrs.push_back({nm, std::string(v, p.p)});
p.p++;
}
for (;;) { // content until the matching close tag
if (!*p.p) return nullptr; // unterminated
if (p.p[0] == '<' && p.p[1] == '/') {
p.p += 2; std::string cn = parse_name(p); skip_ws(p);
if (cn.empty() || *p.p != '>' || cn != e->tag) return nullptr; // mismatched
p.p++; break;
}
if (p.p[0] == '<') {
auto kid = parse_element(p);
if (!kid) return nullptr;
e->kids.push_back(std::move(kid));
} else e->text += *p.p++;
}
return e;
}
// JSON value for an element: "@_<name>" attribute keys, bare string when
// text-only, "#text" for mixed direct text, repeated tags -> an array.
static std::string value(const El& e) {
std::string t = e.text;
t.erase(0, t.find_first_not_of(" \t\r\n"));
t.erase(t.find_last_not_of(" \t\r\n") + 1);
if (e.kids.empty() && e.attrs.empty()) return q(t); // text-only -> bare string
std::string o = "{";
bool first = true;
auto kv = [&](const std::string& k, const std::string& v) {
o += (first ? "" : ",") + q(k) + ":" + v; first = false;
};
for (auto& a : e.attrs) kv("@_" + a.first, q(a.second));
std::vector<bool> done(e.kids.size(), false);
for (size_t i = 0; i < e.kids.size(); i++) { // repeated tags -> array
if (done[i]) continue;
int cnt = 0;
for (auto& k : e.kids) if (k->tag == e.kids[i]->tag) cnt++;
if (cnt == 1) { kv(e.kids[i]->tag, value(*e.kids[i])); continue; }
std::string a = "[";
bool f2 = true;
for (size_t j = 0; j < e.kids.size(); j++) if (e.kids[j]->tag == e.kids[i]->tag) {
a += (f2 ? "" : ",") + value(*e.kids[j]);
f2 = false; done[j] = true;
}
kv(e.kids[i]->tag, a + "]");
}
if (!t.empty()) kv("#text", q(t));
return o + "}";
}
// Parse well-formed XML to compact JSON, or nullopt on malformation (no root,
// mismatched tags, trailing junk). Pretty-printing lives in the TS/Go twins.
static std::optional<std::string> xml_to_json(const char* xml) {
P p{xml}; skip_ws(p);
if (*p.p != '<') return std::nullopt;
auto root = parse_element(p);
if (!root) return std::nullopt;
skip_ws(p);
if (*p.p) return std::nullopt;
return "{" + q(root->tag) + ":" + value(*root) + "}";
}
int main() {
auto ok = xml_to_json("<user id=\"7\"><name>Alice</name><pet>cat</pet><pet>dog</pet></user>");
auto bad = xml_to_json("<a><b></a>"); // mismatched close
std::puts(ok ? ok->c_str() : "(null)");
std::puts(bad ? bad->c_str() : "(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 →