Skip to content

JSON Minify — Swift source

Remove all whitespace from JSON for the most compact serialization. Validates as it minifies, 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.

// json-minify — compact JSON to its smallest valid form. Swift port
// (canonical TS: src/lib/json-minify.ts; Go twin: cli/json-minify).
// Foundation's JSONSerialization re-escapes and loses key order, so — like
// the Rust port — this is a hand-rolled RFC 8259 scanner over UTF-8 bytes:
// validate, copy tokens verbatim, drop only whitespace. Invalid JSON -> nil.
struct JsonMinify {
    private let src: [UInt8]
    private var out: [UInt8] = []
    private var i = 0
    private func peek() -> UInt8 { i < src.count ? src[i] : 0 }
    private mutating func take() -> UInt8 { let c = peek(); i += 1; return c } // 0 at end-of-input
    private mutating func skipWs() { while peek() == 32 || peek() == 9 || peek() == 10 || peek() == 13 { i += 1 } }
    private mutating func digits() -> Bool { var any = false; while peek() >= 0x30 && peek() <= 0x39 { i += 1; any = true }; return any }
    private mutating func string() -> Bool {
        out.append(src[i]); i += 1 // opening quote (0x22)
        while true {
            let c = take()
            if c == 0x22 { out.append(c); return true } // closing quote
            if c == 0x5C { // backslash
                let e = take()
                out.append(e)
                if e == 0x75 { // 'u' — 4 hex digits
                    for _ in 0..<4 { let h = take(); if !"0123456789abcdefABCDEF".utf8.contains(h) { return false }; out.append(h) }
                } else if !"\"\\/bfnrt".utf8.contains(e) { return false } // 0 is not in the set
            } else if c < 0x20 { return false } // control byte / EOF
            else { out.append(c) }
        }
    }
    // Numbers are grammar-checked and copied verbatim — no float round-trip.
    private mutating func number() -> Bool {
        let start = i
        if peek() == 0x2D { i += 1 } // '-'
        if peek() == 0x30 { i += 1 } // '0' — "01" is rejected by the caller
        else if peek() >= 0x31 && peek() <= 0x39 { i += 1; while peek() >= 0x30 && peek() <= 0x39 { i += 1 } }
        else { return false } // bare '-'
        if peek() == 0x2E { i += 1; if !digits() { return false } } // '.'
        if peek() == 0x65 || peek() == 0x45 { i += 1; if peek() == 0x2B || peek() == 0x2D { i += 1 }; if !digits() { return false } } // e/E
        out.append(contentsOf: src[start..<i])
        return true
    }
    private mutating func literal(_ word: String) -> Bool {
        let w = Array(word.utf8)
        guard i <= src.count, src.count - i >= w.count, Array(src[i..<i + w.count]) == w else { return false }
        out.append(contentsOf: w); i += w.count
        return true
    }
    // Objects and arrays share one loop: ',' continues, the matching bracket
    // closes; object elements are "key": value pairs.
    private mutating func value() -> Bool {
        skipWs()
        let c = peek()
        if c == 0x7B || c == 0x5B { // '{' or '['
            let close: UInt8 = c == 0x7B ? 0x7D : 0x5D
            out.append(c); i += 1; skipWs()
            if peek() == close { out.append(close); i += 1; return true } // {} / []
            while true {
                if close == 0x7D { skipWs(); if peek() != 0x22 || !string() { return false }; skipWs(); if i >= src.count || src[i] != 0x3A { return false }; i += 1; out.append(0x3A) } // "key":
                if !value() { return false }
                skipWs()
                let d = take()
                if d == 0x2C { out.append(d); continue } // ','
                if d == close { out.append(close); return true }
                return false
            }
        }
        if c == 0x22 { return string() }
        if c == 0x74 || c == 0x66 || c == 0x6E { return literal(c == 0x74 ? "true" : c == 0x66 ? "false" : "null") }
        return (c == 0x2D || (c >= 0x30 && c <= 0x39)) && number()
    }
    // Compact JSON serialization of input, or nil for invalid JSON.
    static func minify(_ input: String) -> String? {
        var p = JsonMinify(src: Array(input.utf8))
        guard p.value() else { return nil }
        p.skipWs()
        guard p.i == p.src.count else { return nil } // trailing garbage fails
        return String(decoding: p.out, as: UTF8.self)
    }
}
print(JsonMinify.minify("{\n  \"a\": 1,\n  \"b\": [\n    1,\n    2\n  ]\n}") ?? "(invalid)") // {"a":1,"b":[1,2]}
print(JsonMinify.minify("{invalid}") ?? "(nil — invalid JSON)") // invalid JSON -> nil

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 →