Skip to content

Instantly share code, notes, and snippets.

@loromits
Last active November 5, 2019 12:56
Show Gist options
  • Save loromits/89e9aa76f820295bea1e18649e645ef6 to your computer and use it in GitHub Desktop.
Save loromits/89e9aa76f820295bea1e18649e645ef6 to your computer and use it in GitHub Desktop.
Modification of https://gist.github.com/itaiferber/d26cf93355f3415f4f684510f21ca202 to be able to explore contents of JSON.
import Foundation
public enum JSON : Codable {
case null
case boolean(Bool)
case number(Double)
case string(String)
case array([JSON])
case dictionary([String : JSON])
public init(from decoder: Decoder) throws {
let container = try decoder.singleValueContainer()
if container.decodeNil() {
self = .null
} else if let bool = try container.decodeIfMatched(Bool.self) {
self = .boolean(bool)
} else if let double = try container.decodeIfMatched(Double.self) {
self = .number(double)
} else if let string = try container.decodeIfMatched(String.self) {
self = .string(string)
} else if let array = try container.decodeIfMatched([JSON].self) {
self = .array(array)
} else if let dictionary = try container.decodeIfMatched([String : JSON].self) {
self = .dictionary(dictionary)
} else {
let context = DecodingError.Context(codingPath: decoder.codingPath,
debugDescription: "The type is incompatible with JSON.")
throw DecodingError.typeMismatch(JSON.self, context)
}
}
public func encode(to encoder: Encoder) throws {
var container = encoder.singleValueContainer()
switch self {
case .null: try container.encodeNil()
case .boolean(let boolean): try container.encode(boolean)
case .number(let number): try container.encode(number)
case .string(let string): try container.encode(string)
case .array(let array): try container.encode(array)
case .dictionary(let dictionary): try container.encode(dictionary)
}
}
var isNull: Bool {
if case .null = self {
return true
}
return false
}
var number: Double? {
if case .number(let value) = self {
return value
}
return nil
}
var boolean: Bool? {
if case .boolean(let value) = self {
return value
}
return nil
}
var string: String? {
if case .string(let value) = self {
return value
}
return nil
}
var array: [JSON]? {
if case .array(let value) = self {
return value
}
return nil
}
var dictionary: [String : JSON]? {
if case .dictionary(let value) = self {
return value
}
return nil
}
subscript(key: String) -> JSON? {
return dictionary?[key]
}
subscript(index: Int) -> JSON? {
return array.flatMap { index < $0.endIndex ? $0[index] : nil }
}
var value: Any? {
switch self {
case .null: return nil
case .boolean(let boolean): return boolean
case .number(let number): return number
case .string(let string): return string
case .array(let array): return array.compactMap { $0.value }
case .dictionary(let dictionary): return dictionary.compactMapValues { $0.value }
}
}
}
private extension SingleValueDecodingContainer {
func decodeIfMatched<T : Decodable>(_ type: T.Type) throws -> T? {
do {
return try self.decode(T.self)
} catch DecodingError.typeMismatch {
return nil
}
}
}
extension JSON {
var playgroundDescription: String {
return String(describing: self)
.replacingOccurrences(of: "__lldb_expr_\\d+\\.", with: "", options: .regularExpression, range: nil)
.replacingOccurrences(of: "\"", with: "")
}
}
let json = """
{
"firstName": "John",
"lastName": "Smith",
"isAlive": true,
"age": 27,
"address": {
"streetAddress": "21 2nd Street",
"city": "New York",
"state": "NY",
"postalCode": "10021-3100"
},
"phoneNumbers": [
{
"type": "home",
"number": "212 555-1234"
},
{
"type": "office",
"number": "646 555-4567"
},
{
"type": "mobile",
"number": "123 456-7890"
}
],
"children": [],
"spouse": null
}
""".data(using: .utf8)!
let decoded = try JSONDecoder().decode(JSON.self, from: json)
print(decoded["phoneNumbers"]?[1]?["number"]?.string) // Optional("646 555-4567")
print(decoded["phoneNumbers"]?.playgroundDescription) // Optional("array([JSON.dictionary([type: JSON.string(home), number: JSON.string(212 555-1234)]), JSON.dictionary([type: JSON.string(office), number: JSON.string(646 555-4567)]), JSON.dictionary([type: JSON.string(mobile), number: JSON.string(123 456-7890)])])")
print(decoded["isAlive"]?.boolean) // Optional(true)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment