XML ↔ JSON Converter — Swift 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 Swift implementation — the same logic the interactive tool runs, in a shareable, citable form.
// xml-to-json — bidirectional XML <-> JSON converter. Swift (Foundation) port of src/lib/xml-to-json.ts — same logic as this dir's rust.rs (Go-twin contract); JSON -> XML and CDATA simplified for the 80-line budget, and JSONSerialization emits sorted keys like the Go twin (see rust.rs / python.py).
import Foundation
// XMLParser is event-driven: the delegate keeps a stack of open elements and
// composes each value as it closes (bare string when text-only).
final class TreeBuilder: NSObject, XMLParserDelegate {
var ok = true
private var stack = [NSMutableDictionary]()
private var text = [String]()
private(set) var rootName: String?
private(set) var root: Any?
func parser(_ p: XMLParser, didStartElement name: String, namespaceURI: String?,
qualifiedName qName: String?, attributes attrs: [String: String]) {
let el = NSMutableDictionary()
for (k, v) in attrs { el["@_\(k)"] = v } // attribute keys
if stack.isEmpty { rootName = name }
stack.append(el)
text.append("")
}
func parser(_ p: XMLParser, foundCharacters string: String) {
if !text.isEmpty { text[text.count - 1] += string }
}
func parser(_ p: XMLParser, didEndElement elementName: String, namespaceURI: String?,
qualifiedName qName: String?) {
let el = stack.removeLast()
let t = text.removeLast().trimmingCharacters(in: .whitespacesAndNewlines)
let value: Any
if el.count == 0 { value = t } // text-only -> bare string
else {
if !t.isEmpty { el["#text"] = t }
value = el
}
if let parent = stack.last { // repeated tags -> array
if let existing = parent[elementName] {
if let arr = existing as? NSMutableArray { arr.add(value) }
else { parent[elementName] = NSMutableArray(array: [existing, value]) }
} else { parent[elementName] = value }
} else { root = value }
}
func parser(_ p: XMLParser, parseErrorOccurred error: Error) { ok = false }
}
// Parse well-formed XML to pretty JSON, or nil if XMLParser reports an error
// (mismatched tags, no root, trailing junk all abort the parse).
func xmlToJson(_ xml: String) -> String? {
guard let data = xml.data(using: .utf8) else { return nil }
let builder = TreeBuilder()
let parser = XMLParser(data: data)
parser.delegate = builder
guard parser.parse(), builder.ok, let name = builder.rootName, let root = builder.root
else { return nil }
guard let out = try? JSONSerialization.data(withJSONObject: [name: root],
options: [.prettyPrinted, .sortedKeys])
else { return nil }
return String(data: out, encoding: .utf8)
}
// Demo: parse, attributes, repeated tags, malformed input.
let xml = "<user id=\"7\"><name>Alice</name><pet>cat</pet><pet>dog</pet></user>"
print(xmlToJson(xml) ?? "nil")
print(xmlToJson("<a><b></a>") ?? "nil") // mismatched close
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 →