Skip to content

Instantly share code, notes, and snippets.

@hannesoid
Created June 26, 2017 13:47
Show Gist options
  • Save hannesoid/10a35895e4dc5d6f1bb6428f7d4d23a5 to your computer and use it in GitHub Desktop.
Save hannesoid/10a35895e4dc5d6f1bb6428f7d4d23a5 to your computer and use it in GitHub Desktop.
Decodes an unknown JSON structure using Swift 4 Decoding
import XCTest
// Requires a recent Swift 4 snapshot toolchain (Xcode 9 beta 2 not new enough)
public indirect enum JSONValue: Decodable {
case double(Double)
case string(String)
case bool(Bool)
case dictionary([String: JSONValue])
case array([JSONValue])
case `nil`
public init(from decoder: Decoder) throws {
let singleValueContainer = try decoder.singleValueContainer()
if let value = try? singleValueContainer.decode(Bool.self) {
self = .bool(value)
return
} else if let value = try? singleValueContainer.decode(String.self) {
self = .string(value)
return
} else if let value = try? singleValueContainer.decode(Double.self) {
self = .double(value)
return
} else if let value = try? singleValueContainer.decode([String: JSONValue].self) {
self = .dictionary(value)
return
} else if let value = try? singleValueContainer.decode([JSONValue].self) {
self = .array(value)
return
} else if singleValueContainer.decodeNil() {
self = .nil
return
}
throw DecodingError.dataCorrupted(DecodingError.Context(codingPath: decoder.codingPath, debugDescription: "Could not find reasonable type to map to JSONValue"))
}
}
// MARK: - Convenience
extension JSONValue {
public var string: String? {
switch self {
case .string(let value):
return value
default:
return nil
}
}
public var double: Double? {
switch self {
case .double(let value):
return value
default:
return nil
}
}
public var bool: Bool? {
switch self {
case .bool(let value):
return value
default:
return nil
}
}
public var dictionary: [String: JSONValue]? {
switch self {
case .dictionary(let value):
return value
default:
return nil
}
}
public var array: [JSONValue]? {
switch self {
case .array(let value):
return value
default:
return nil
}
}
public var isNil: Bool {
switch self {
case .nil:
return true
default:
return false
}
}
}
class JSONValueTests: XCTestCase {
func testDecoding() {
let decoder = JSONDecoder()
let a = try! decoder.decode(JSONValue.self, from: """
{
"storage": {
"subthing": "stringValue",
"a": 2,
"bool": true,
"nilValue": null,
},
"nilValue": null
}
""".data(using: .utf8)!)
XCTAssertEqual(a.dictionary?["storage"]?.dictionary?["subthing"]?.string, "stringValue")
XCTAssertEqual(a.dictionary?["storage"]?.dictionary?["a"]?.double, 2.0)
XCTAssertEqual(a.dictionary?["storage"]?.dictionary?["bool"]?.bool, true)
XCTAssertEqual(a.dictionary?["storage"]?.dictionary?["nilValue"]?.isNil, true)
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment