Skip to content

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 →