Generic JSON makes it easy to deal with freeform JSON strings without creating a separate, well-typed structure.
Swift 4 introduced a new JSON encoding and decoding machinery represented by the Codable protocol. The feature is very nice and very type-safe, meaning it’s no longer possible to just willy-nilly decode a JSON string pulling random untyped data from it. Which is goodTM most of the time – but what should you do when you do want to just willy-nilly encode or decode a JSON string without introducing a separate, well-typed structure for it? For example:
// error: heterogeneous collection literal could only be inferred to '[String : Any]'; // add explicit type annotation if this is intentional let json = [ "foo": "foo", "bar": 1, ] // Okay then: let json: [String:Any] = [ "foo": "foo", "bar": 1, ] // But: fatal error: Dictionary<String, Any> does not conform to Encodable because Any does not conform to Encodable. let encoded = try JSONEncoder().encode(json)
So this doesn’t work very well. Also, the json value can’t be checked for equality with another, although arbitrary JSON values should support equality. Enter JSON.
let json: JSON = [ "foo": "foo", "bar": 1, ] // "{"bar":1,"foo":"foo"}" let str = try String(data: try JSONEncoder().encode(json), encoding: .utf8)! let hopefullyTrue = (json == json) // true!
struct Player: Codable { let name: String let swings: Bool } let val = try JSON(encodable: Player(name: "Miles", swings: true)) val == [ "name": "Miles", "swings": true, ] // true
Consider the following JSON structure:
let json: JSON = [ "num": 1, "str": "baz", "bool": true, "obj": [ "foo": "jar", "bar": 1, ] ]
Querying values can be done using optional property accessors, subscripting or dynamic member subscripting:
// Property accessors if let str = json.objectValue?["str"]?.stringValue { ... } if let foo = json.objectValue?["obj"]?.objectValue?["foo"]?.stringValue { ... } // Subscripting if let str = json["str"]?.stringValue { ... } if let foo = json["obj"]?["foo"]?.stringValue { ... } // Dynamic member subscripting if let str = json.str?.stringValue { ... } if let foo = json.obj?.foo?.stringValue { ... }
You may even drill through nested structures using a dot-separated key path:
let val = json[keyPath: "obj.foo"] // "jar"