JSON in Swift — the way it should be.
SwifterJSON is a lightweight, type-safe Swift enum for working with JSON data that doesn't map to a fixed Codable model. It leverages Swift's Codable infrastructure, so you can decode arbitrary JSON into a strongly-typed JSON value and traverse or mutate it with clean subscript syntax — no casting required.
Features
- Type-safe enum — every JSON value is represented as a
JSONcase (null,bool,int,double,string,array,object) - Codable — decode from
Data/Stringvia standardJSONDecoder; encode back viaJSONEncoder - Subscript access — chain
[key]and[index]subscripts to drill into nested structures - Mutable — modify deeply nested values in-place with subscript assignment
- Literal expressible — create
JSONvalues directly from Swift literals (strings, numbers, arrays, dictionaries,nil) - Codable interop — convert between
JSONand anyCodabletype without round-tripping throughData - Raw value bridging — convert to/from
Anyfor interop with APIs that use untyped dictionaries - Sendable & Hashable — safe for concurrent use and usable as dictionary keys
- Cross-platform — iOS, macOS, tvOS, watchOS, visionOS, Linux, Android, and WebAssembly
Requirements
| Dependency | Minimum |
|---|---|
| Swift | 6.0+ |
| iOS | 15.0+ |
| macOS | 11.0+ |
| tvOS | 15.0+ |
| watchOS | 8.0+ |
| visionOS | 1.0+ |
| Linux | Swift 6.0 toolchain |
| Android | Swift 6.0 toolchain |
| WebAssembly | SwiftWasm 6.1 SDK |
Installation
Swift Package Manager
Add SwifterJSON as a dependency in your Package.swift:
dependencies: [ .package(url: "https://github.com/guykogus/SwifterJSON.git", from: "4.0.0") ]
Then add it to your target:
.target( name: "YourTarget", dependencies: ["SwifterJSON"] )
Or in Xcode: File > Add Package Dependencies… and enter https://github.com/guykogus/SwifterJSON.git.
Usage
The problem
In the modern era of Codable it is rare that we need to handle JSON data manually. But sometimes the structure isn't known in advance — server-driven UI, feature flags, analytics payloads, etc.
Given this JSON:
{
"Apple": {
"address": {
"street": "1 Infinite Loop",
"city": "Cupertino",
"state": "CA",
"zip": "95014"
},
"employees": 132000
}
}With JSONSerialization you'd have to cast at every level:
guard let companies = try JSONSerialization.jsonObject(with: data) as? [String: Any] else { return } if let company = companies["Apple"] as? [String: Any], let address = company["address"] as? [String: Any], let city = address["city"] as? String { print("Apple is in \(city)") }
Mutations are even worse — you need mutable copies at each nesting level:
guard var companies = try JSONSerialization.jsonObject(with: data) as? [String: Any] else { return } if var apple = companies["Apple"] as? [String: Any], var address = apple["address"] as? [String: Any] { address["state"] = "California" apple["address"] = address companies["Apple"] = apple }
With SwifterJSON
Reading nested values is a single chained expression:
let companies = try JSONDecoder().decode(JSON.self, from: data) if let city = companies["Apple"]?["address"]?["city"]?.stringValue { print("Apple is in \(city)") }
Mutations work in-place — no intermediate copies:
var companies = try JSONDecoder().decode(JSON.self, from: data) companies["Apple"]?["address"]?["state"] = "California"
Constructing JSON from literals
JSON conforms to all the ExpressibleBy…Literal protocols, so you can write JSON values naturally:
let config: JSON = [ "feature_flags": [ "dark_mode": true, "max_retries": 3, "api_url": "https://api.example.com" ], "version": 2.1 ]
Converting between JSON and Codable types
Encode any Encodable value into JSON without going through Data:
struct User: Codable { let name: String let age: Int } let user = User(name: "Alice", age: 30) let json = try JSON(encodableValue: user) // json == ["name": "Alice", "age": 30]
Decode JSON back into a typed model:
let decoded: User = try json.decode()
Accessing values
Each JSON type has a corresponding accessor that returns an optional:
json.boolValue // Bool? json.intValue // Int? json.doubleValue // Double? json.stringValue // String? json.arrayValue // [JSON]? json.objectValue // [String: JSON]? json.isNull // Bool json.count // Int? (for arrays and objects)
Array subscripting
let fibonacci: JSON = [1, 1, 2, 3, 5, 8, 13] fibonacci[4]?.intValue // 5
Raw value interop
Bridge to/from Any for APIs that use untyped dictionaries:
let raw: Any? = json.rawValue let roundTripped = JSON(rawValue: raw)
License
SwifterJSON is available under the MIT license. See the LICENSE file for details.