Skip to content

JSON Repair — Swift source

Fix broken JSON - trailing commas, single quotes, unquoted keys, comments, Python constants, BOM and truncated documents - and get clean pretty-printed JSON plus a list of every repair applied. Runs entirely in your browser.

This is the Swift implementation — the same logic the interactive tool runs, in a shareable, citable form.

// JSON repair pipeline — Swift port of the json-repair tool (excerpt of the
// 7-pass pipeline in typescript.ts / go.go: quote, Python-constant, trailing
// comma, truncation passes); unterminated '...' is left for the truncation scan.
import Foundation
private func replaceMatches(_ text: String, pattern: String,
                            xform: (NSTextCheckingResult, NSString) -> String) -> String {
    let re = try! NSRegularExpression(pattern: pattern)
    let ns = text as NSString
    var out = ""
    var last = 0
    for m in re.matches(in: text, range: NSRange(location: 0, length: ns.length)) {
        out += ns.substring(with: NSRange(location: last, length: m.range.location - last))
        out += xform(m, ns)
        last = m.range.location + m.range.length
    }
    return out + ns.substring(with: NSRange(location: last, length: ns.length - last))
}
// Transform only the segments outside double-quoted strings.
private func outsideStrings(_ text: String, _ map: (String) -> String) -> String {
    let re = try! NSRegularExpression(pattern: "\"(?:[^\"\\\\]|\\\\.)*\"")
    let ns = text as NSString
    var out = ""
    var last = 0
    re.enumerateMatches(in: text, range: NSRange(location: 0, length: ns.length)) { m, _, _ in
        let r = m!.range
        out += map(ns.substring(with: NSRange(location: last, length: r.location - last)))
        out += ns.substring(with: r)
        last = r.location + r.length
    }
    return out + map(ns.substring(with: NSRange(location: last, length: ns.length - last)))
}
// Single-quoted strings -> double-quoted: escape inner double quotes,
// collapse \' to ', keep every other escape; double-quoted copy verbatim.
func singleToDoubleQuotes(_ text: String) -> String {
    replaceMatches(text, pattern: "'(?:[^'\\\\]|\\\\.)*'") { m, ns in
        let body = ns.substring(with: NSRange(location: m.range.location + 1, length: m.range.length - 2))
        return "\"" + replaceMatches(replaceMatches(body, pattern: "\\\\'") { _, _ in "'" },
                                     pattern: "\"") { _, _ in "\\\"" } + "\""
    }
}
func fixPythonConstants(_ text: String) -> String {
    outsideStrings(text) { s in
        replaceMatches(s, pattern: "\\b(True|False|None)\\b") { m, ns in ["True": "true", "False": "false"][ns.substring(with: m.range)] ?? "null" }
    }
}
// Commas followed only by whitespace and a closing } or ] — dropped.
func stripTrailingCommas(_ text: String) -> String {
    outsideStrings(text) { s in
        replaceMatches(s, pattern: ",(\\s*[}\\]])") { m, ns in ns.substring(with: m.range(at: 1)) }
    }
}
// Recover truncated JSON: close an unterminated string, drop a dangling
// comma, give a dangling colon a null value, then close open brackets.
func closeTruncated(_ text: String) -> String {
    let a = Array(text)
    var stack: [Character] = []
    var inString = false
    var i = 0
    while i < a.count {
        let c = a[i]
        if inString {
            if c == "\\" { i += 1 } // skip the escaped character
            else if c == "\"" { inString = false }
        } else if c == "\"" { inString = true }
        else if c == "{" || c == "[" { stack.append(c) }
        else if c == "}" || c == "]" { if !stack.isEmpty { stack.removeLast() } }
        i += 1
    }
    var out = inString ? text + "\"" : text
    while let last = out.last, last == "," || last.isWhitespace { out.removeLast() }
    if out.hasSuffix(":") { out += " null" }
    return out + stack.reversed().map { $0 == "{" ? "}" : "]" }
}
func repair(_ text: String) -> String {
    [singleToDoubleQuotes, fixPythonConstants, stripTrailingCommas, closeTruncated]
        .reduce(text) { work, pass in pass(work) }
}
// {"name": "Ada", admin: true, tags: ["x", "y"]}
print(repair("{'name': 'Ada', admin: True, tags: ['x', 'y',],"))

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 →