JSON.swift Quick Guide
JSON is a value-type enum representing any JSON value:
enum JSON { case objectCase, arrayCase, stringCase, intCase, numberCase, boolCase, nullCase, proxyCase }
Use ordinary Swift literals to construct values:
let person: JSON = [ "name": "Øystein", "age": 36, "admin": true, "tags": ["math", "programming"], "middleName": .null ]
Reading values
Typed accessors return optionals and fail with nil when the type is wrong:
value.string // String? value.int // Int? value.number // Double?; accepts integers and decimals value.bool // Bool? value.object // [String: JSON]? value.array // [JSON]? value.isNull // Bool
The .int accessor converts a decimal value only when it has no fractional part.
Object keys and array indexes return JSON?:
let name: String? = person["name"]?.string let age: Int? = person["age"]?.int let firstTag: String? = person["tags"]?[0]?.string
Mutating values
JSON has value semantics. Assigning it creates an independent value logically, while arrays and dictionaries benefit from copy-on-write storage.
Standalone assignments should be wrapped explicitly:
var first: JSON = ["name": "Abel", "age": 46] var second = first second["name"] = JSON("Zippel") second["age"] = JSON(37) second["active"] = JSON(true)
Assigning nil removes an object key or array element:
value["temporary"] = nil
Use .null to retain a key or element as JSON null:
value["middleName"] = .null
Array mutation uses zero-based indexes:
var numbers: JSON = [10, 20, 30] numbers[1] = JSON(25) numbers[0] = nil // removes the first element
Object members can also be removed explicitly:
value.removeValue(forKey: "name")
Invalid indexes, wrong receiver types, and out-of-range mutations are ignored.
A var JSON can change its entire type:
var value: JSON = "text" value = 42 value = [true, nil] value = ["ready": false]
Direct mutation is not supported.
var value: JSON = "text" value.string = "Sorry, you can't do this."
Paths
Paths provide one operation for walking through nested objects, arrays, and proxies. A path component is normally a String for an object key or an Int for an array index.
state["user"] state[0]
The same subscript syntax accepts mixed components:
let name = state["users", 1, "name"]?.string state["users", 1, "name"] = "Bruno" state["user"]?[1]?["name"] = "Bruno"
The explicit methods accept a path array of any supported length. Useful when the path is built dynamically.
let path: [Any] = ["users", 1, "name"] let name = state.getPath(path)?.string let changed = state.setPath(path, JSON("Ada"))
Serialization
Convert JSON to text:
let text = try JSON.stringify(value) let compact = try JSON.stringify(value, prettyPrinted: false)
Parse JSON text:
let value = try JSON.parse(text)
Both operations can throw. Serialization uses Foundation’s JSONSerialization and sorts object keys.
toString() is a friendly display operation, not strict JSON serialization. It produces readable JavaScript-like text, while JSON.stringify() produces valid JSON suitable for storage or transmission:
JSON("Hello World").toString() // Hello World try JSON.stringify(JSON("Hello World")) // "Hello World"
JSONProxy protocol
JSONProxy provides an in-memory value that resolves whenever it is read or serialized. It is represented in textual JSON exactly the same as its resolved JSON and is useful for live or externally owned values:
final class Counter: JSONProxy { var value = 0 func resolveJSONProxy() -> JSON { value += 1 return JSON(value) } } let counter = JSON(Counter()) counter.proxy is Counter // true counter.int // 1 counter.int // 2
An object-shaped proxy can also receive writes to selected keys. It is not required to make the proxy resolve to anything for this.
final class Settings: JSONProxy { var title = "Initial" var skip = 1 func resolveJSONProxy() -> JSON { ["title": title, "skip": skip] } func setJSONProxyValue(key: JSON, value: JSON?) { switch key.string { case "title": title = value?.string ?? title case "skip": skip = value?.int ?? skip default: break } } } var settings = JSON(Settings()) settings["title"] = "Updated" settings["skip"] = 10
Reads resolve proxies, but changing an ordinary value inside a detached resolved proxy does not write back unless the proxy provides the appropriate setter behavior.
Running the examples
The executable examples and assertions are in main.swift. With the Swift
compiler on the path:
swiftc JSON.swift main.swift -o jsonswift
./jsonswift
The examples use Foundation for parsing and serialization.
Licensing for use
© 2026 Hypervariety Custom Programming, LLC. All rights reserved. All commercial and non-commercial use is permitted by the author, as long as this copyright message accompanies the product and source.