GitHub

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.

Read the original on github.com ↗