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 →