Skip to content

JSON Formatter — Swift source

Beautify, minify and validate JSON instantly. Paste, format, copy - all in your browser. Share a link to your exact input.

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

// json-formatter — parse, pretty-print (2-space indent), and minify.
//
// Language: Swift (Swift 5.9, standard library only)
// Source:   CosmoDev polyglot showcase port of the `json-formatter` tool,
//           ported from src/tools/JsonFormatter.tsx (the canonical
//           TypeScript island).
// License:  display source — part of CosmoDev's polyglot tool pages.
//
// Foundation's JSONSerialization is deliberately NOT used: it does not
// guarantee insertion-order preservation for object keys. So — mirroring
// the Rust port — this file includes a small hand-written recursive-descent
// parser and serializer, pure Swift standard library. Object members are
// stored in an array of labeled tuples, which preserves source insertion
// order just like a JavaScript engine.
//
// Parity caveats: numbers are held as Double (matching JS Number), so
// integers beyond 2^53 lose precision, and Swift's Double description
// keeps a trailing ".0" / switches to exponential notation at different
// thresholds than ECMAScript's Number::toString (integral values below
// 1e15 are printed as plain integers to match the common cases).

/// A parsed JSON value. Object members live in an array to preserve order.
indirect enum Json: Equatable {
    case null
    case bool(Bool)
    case number(Double)
    case string(String)
    case array([Json])
    case object([Member])
}

/// One object member; a struct (not a tuple) so Json can synthesize Equatable.
struct Member: Equatable {
    let key: String
    let value: Json
}

/// Selects the output shape.
enum Mode {
    case beautify
    case minify
}

/// Mirrors the TypeScript { output, error } shape; error == nil means
/// success or empty input.
struct FormatResult: Equatable {
    var output: String
    var error: String?
}

/// A parse failure: scalar offset + message.
struct ParseError: Error, CustomStringConvertible {
    let offset: Int
    let message: String

    var description: String { "\(message) at scalar \(offset)" }
}

/// Parse and re-emit a JSON document the way the live island does.
/// `indent` is the number of spaces per nesting level for `.beautify`
/// (pass 2 to match the UI). Empty/whitespace-only input yields empty
/// output, no error.
func formatJson(_ input: String, mode: Mode = .beautify, indent: Int = 2) -> FormatResult {
    // Trim leading/trailing JSON whitespace (stdlib only — no Foundation).
    var raw = Substring(input)
    while let f = raw.first, f == " " || f == "\t" || f == "\n" || f == "\r" { raw = raw.dropFirst() }
    while let l = raw.last, l == " " || l == "\t" || l == "\n" || l == "\r" { raw = raw.dropLast() }
    if raw.isEmpty { return FormatResult(output: "", error: nil) }

    var parser = Parser(String(raw))
    do {
        let value = try parser.parseDocument()
        var out = ""
        let pad = String(repeating: " ", count: indent)
        switch mode {
        case .minify: value.writeCompact(to: &out)
        case .beautify: value.writePretty(to: &out, depth: 0, pad: pad)
        }
        return FormatResult(output: out, error: nil)
    } catch let e as ParseError {
        return FormatResult(output: "", error: e.description)
    } catch {
        return FormatResult(output: "", error: "unexpected parse failure")
    }
}

// ---------------------------------------------------------------------
// Parser — a minimal recursive-descent JSON parser.
// ---------------------------------------------------------------------

private struct Parser {
    let scalars: [Unicode.Scalar]
    var pos = 0

    init(_ text: String) { scalars = Array(text.unicodeScalars) }

    func fail(_ message: String) -> ParseError { ParseError(offset: pos, message: message) }

    /// nil means end of input — never a JSON token, so loops and switches
    /// that test peek() terminate.
    var peek: Unicode.Scalar? { pos < scalars.count ? scalars[pos] : nil }

    static func isDigit(_ c: Unicode.Scalar) -> Bool { c >= "0" && c <= "9" }

    mutating func skipWs() {
        while let c = peek, c == " " || c == "\t" || c == "\n" || c == "\r" { pos += 1 }
    }

    /// Match a literal keyword at the current position; on success advance.
    mutating func matchKw(_ kw: String) -> Bool {
        let wanted = Array(kw.unicodeScalars)
        guard pos + wanted.count <= scalars.count else { return false }
        for (i, w) in wanted.enumerated() where scalars[pos + i] != w { return false }
        pos += wanted.count
        return true
    }

    mutating func parseDocument() throws -> Json {
        skipWs()
        let value = try parseValue()
        skipWs()
        if peek != nil { throw fail("unexpected trailing characters") }
        return value
    }

    mutating func parseValue() throws -> Json {
        guard let c = peek else { throw fail("unexpected end of input") }
        if c == "-" || Self.isDigit(c) { return try parseNumber() }
        switch c {
        case "{": return try parseObject()
        case "[": return try parseArray()
        case "\"": return .string(try parseString())
        case "t", "f": return try parseBool()
        case "n": return try parseNull()
        default: throw fail("unexpected token")
        }
    }

    mutating func parseObject() throws -> Json {
        pos += 1 // consume '{'
        skipWs()
        var members: [Member] = []
        if peek == "}" { pos += 1; return .object(members) }
        while true {
            skipWs()
            guard peek == "\"" else { throw fail("expected string key") }
            let key = try parseString()
            skipWs()
            guard peek == ":" else { throw fail("expected ':' after key") }
            pos += 1
            skipWs()
            members.append(Member(key: key, value: try parseValue()))
            skipWs()
            if peek == "," { pos += 1; continue }
            if peek == "}" { pos += 1; return .object(members) }
            throw fail("expected ',' or '}'")
        }
    }

    mutating func parseArray() throws -> Json {
        pos += 1 // consume '['
        skipWs()
        var items: [Json] = []
        if peek == "]" { pos += 1; return .array(items) }
        while true {
            skipWs()
            items.append(try parseValue())
            skipWs()
            if peek == "," { pos += 1; continue }
            if peek == "]" { pos += 1; return .array(items) }
            throw fail("expected ',' or ']'")
        }
    }

    mutating func parseString() throws -> String {
        pos += 1 // opening '"'
        var s = ""
        while true {
            guard let c = peek else { throw fail("unterminated string") }
            if c == "\"" { pos += 1; return s }
            if c == "\\" {
                pos += 1
                try parseEscape(&s)
            } else if c.value < 0x20 {
                throw fail("unescaped control character in string")
            } else {
                s.unicodeScalars.append(c)
                pos += 1
            }
        }
    }

    private mutating func parseEscape(_ s: inout String) throws {
        guard let c = peek else { throw fail("unterminated escape") }
        switch c {
        case "\"": pos += 1; s.append("\"")
        case "\\": pos += 1; s.append("\\")
        case "/": pos += 1; s.append("/")
        case "b": pos += 1; s.unicodeScalars.append(Unicode.Scalar(8))
        case "f": pos += 1; s.unicodeScalars.append(Unicode.Scalar(12))
        case "n": pos += 1; s.append("\n")
        case "r": pos += 1; s.append("\r")
        case "t": pos += 1; s.append("\t")
        case "u": pos += 1; try parseUnicodeEscape(&s)
        default: throw fail("invalid escape sequence")
        }
    }

    /// Enforce correct UTF-16 surrogate pairing for '\u' escapes.
    private mutating func parseUnicodeEscape(_ s: inout String) throws {
        let cp = try parseHex4()
        if (0xD800...0xDBFF).contains(cp) {
            guard peek == "\\" else { throw fail("dangling high surrogate") }
            pos += 1
            guard peek == "u" else { throw fail("expected '\\u' for surrogate pair") }
            pos += 1
            let lo = try parseHex4()
            guard (0xDC00...0xDFFF).contains(lo) else {
                throw fail("invalid low surrogate after high surrogate")
            }
            let scalar = 0x10000 + ((cp - 0xD800) << 10) + (lo - 0xDC00)
            if let ch = Unicode.Scalar(scalar) { s.unicodeScalars.append(ch) }
        } else if (0xDC00...0xDFFF).contains(cp) {
            throw fail("unexpected low surrogate")
        } else if let ch = Unicode.Scalar(cp) {
            s.unicodeScalars.append(ch)
        }
    }

    /// Read exactly four hexadecimal digits following a '\u'.
    private mutating func parseHex4() throws -> UInt32 {
        var value: UInt32 = 0
        for _ in 0..<4 {
            guard let c = peek else { throw fail("incomplete '\\u' escape") }
            let d: UInt32
            switch c {
            case "0"..."9": d = c.value - Unicode.Scalar("0").value
            case "a"..."f": d = c.value - Unicode.Scalar("a").value + 10
            case "A"..."F": d = c.value - Unicode.Scalar("A").value + 10
            default: throw fail("invalid hex digit in '\\u' escape")
            }
            value = value * 16 + d
            pos += 1
        }
        return value
    }

    private mutating func parseNumber() throws -> Json {
        let start = pos
        if peek == "-" { pos += 1 }
        if peek == "0" {
            pos += 1
        } else if let c = peek, c >= "1", c <= "9" {
            while let d = peek, Self.isDigit(d) { pos += 1 }
        } else {
            throw ParseError(offset: start, message: "invalid number")
        }
        if peek == "." {
            pos += 1
            guard let d = peek, Self.isDigit(d) else { throw fail("expected digit after decimal point") }
            while let d = peek, Self.isDigit(d) { pos += 1 }
        }
        if peek == "e" || peek == "E" {
            pos += 1
            if peek == "+" || peek == "-" { pos += 1 }
            guard let d = peek, Self.isDigit(d) else { throw fail("expected digit in exponent") }
            while let d = peek, Self.isDigit(d) { pos += 1 }
        }
        let text = String(String.UnicodeScalarView(scalars[start..<pos]))
        guard let value = Double(text) else { throw fail("invalid number") }
        return .number(value)
    }

    private mutating func parseBool() throws -> Json {
        if matchKw("true") { return .bool(true) }
        if matchKw("false") { return .bool(false) }
        throw fail("invalid literal")
    }

    private mutating func parseNull() throws -> Json {
        if matchKw("null") { return .null }
        throw fail("invalid literal")
    }
}

// ---------------------------------------------------------------------
// Serializer.
// ---------------------------------------------------------------------

private func pushIndent(_ out: inout String, depth: Int, pad: String) {
    for _ in 0..<depth { out += pad }
}

/// Lowercase hex without Foundation's String(format:).
private func hex4(_ value: UInt32) -> String {
    let digits = Array("0123456789abcdef".unicodeScalars)
    var s = ""
    for shift in [12, 8, 4, 0] {
        s.unicodeScalars.append(digits[Int((value >> UInt32(shift)) & 0xF)])
    }
    return String(s)
}

/// Emit a JSON string literal, escaping per RFC 8259. Like JSON.stringify it
/// leaves '/' unescaped and emits control characters below U+0020 as \uXXXX.
private func writeJsonString(_ value: String, to out: inout String) {
    out += "\""
    for c in value.unicodeScalars {
        switch c {
        case "\"": out += "\\\""
        case "\\": out += "\\\\"
        case "\n": out += "\\n"
        case "\r": out += "\\r"
        case "\t": out += "\\t"
        case Unicode.Scalar(8): out += "\\b"
        case Unicode.Scalar(12): out += "\\f"
        default:
            if c.value < 0x20 {
                out += "\\u" + hex4(c.value)
            } else {
                out.unicodeScalars.append(c)
            }
        }
    }
    out += "\""
}

/// Print a double the way JavaScript most often renders it: integral values
/// below 1e15 without a trailing ".0", everything else via Swift's
/// shortest-round-trip description.
private func writeNumber(_ v: Double, to out: inout String) {
    if v.rounded() == v && v.magnitude < 1e15 {
        out += String(Int(v))
    } else {
        out += "\(v)"
    }
}

extension Json {
    func writeCompact(to out: inout String) {
        switch self {
        case .null: out += "null"
        case .bool(let b): out += b ? "true" : "false"
        case .number(let n): writeNumber(n, to: &out)
        case .string(let s): writeJsonString(s, to: &out)
        case .array(let items):
            out += "["
            for (i, v) in items.enumerated() {
                if i > 0 { out += "," }
                v.writeCompact(to: &out)
            }
            out += "]"
        case .object(let members):
            out += "{"
            for (i, m) in members.enumerated() {
                if i > 0 { out += "," }
                writeJsonString(m.key, to: &out)
                out += ":"
                m.value.writeCompact(to: &out)
            }
            out += "}"
        }
    }

    func writePretty(to out: inout String, depth: Int, pad: String) {
        switch self {
        case .array(let items) where !items.isEmpty:
            out += "["
            for (i, v) in items.enumerated() {
                if i > 0 { out += "," }
                out += "\n"
                pushIndent(&out, depth: depth + 1, pad: pad)
                v.writePretty(to: &out, depth: depth + 1, pad: pad)
            }
            out += "\n"
            pushIndent(&out, depth: depth, pad: pad)
            out += "]"
        case .object(let members) where !members.isEmpty:
            out += "{"
            for (i, m) in members.enumerated() {
                if i > 0 { out += "," }
                out += "\n"
                pushIndent(&out, depth: depth + 1, pad: pad)
                writeJsonString(m.key, to: &out)
                out += ": "
                m.value.writePretty(to: &out, depth: depth + 1, pad: pad)
            }
            out += "\n"
            pushIndent(&out, depth: depth, pad: pad)
            out += "}"
        default:
            // Scalars and empty containers render identically in both modes.
            writeCompact(to: &out)
        }
    }
}

// Top-level demo (this file compiles standalone with `swiftc swift.swift`,
// where the @main attribute is not available — script mode has no @main).
let sample = #"{"name":"CosmoDev","tools":["json","base64"],"free":true,"nested":{"ok":1}}"#
let result = formatJson(sample, mode: .beautify, indent: 2)
if let error = result.error {
    print("error: \(error)")
} else {
    print(result.output)
}

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 →