-
-
Save itaiferber/d26cf93355f3415f4f684510f21ca202 to your computer and use it in GitHub Desktop.
An oversimplified enum which can decode arbitrary JSON payload contents.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import Foundation | |
public enum JSON : Codable { | |
case null | |
case number(NSNumber) | |
case string(String) | |
case array([JSON]) | |
case dictionary([String : JSON]) | |
public var value: Any? { | |
switch self { | |
case .null: return nil | |
case .number(let number): return number | |
case .string(let string): return string | |
case .array(let array): return array.map { $0.value } | |
case .dictionary(let dictionary): return dictionary.mapValues { $0.value } | |
} | |
} | |
public init?(_ value: Any?) { | |
guard let value = value else { | |
self = .null | |
return | |
} | |
if let int = value as? Int { | |
self = .number(NSNumber(value: int)) | |
} else if let double = value as? Double { | |
self = .number(NSNumber(value: double)) | |
} else if let string = value as? String { | |
self = .string(string) | |
} else if let array = value as? [Any] { | |
var mapped = [JSON]() | |
for inner in array { | |
guard let inner = JSON(inner) else { | |
return nil | |
} | |
mapped.append(inner) | |
} | |
self = .array(mapped) | |
} else if let dictionary = value as? [String : Any] { | |
var mapped = [String : JSON]() | |
for (key, inner) in dictionary { | |
guard let inner = JSON(inner) else { | |
return nil | |
} | |
mapped[key] = inner | |
} | |
self = .dictionary(mapped) | |
} else { | |
return nil | |
} | |
} | |
public init(from decoder: Decoder) throws { | |
let container = try decoder.singleValueContainer() | |
guard !container.decodeNil() else { | |
self = .null | |
return | |
} | |
if let int = try container.decodeIfMatched(Int.self) { | |
self = .number(NSNumber(value: int)) | |
} else if let double = try container.decodeIfMatched(Double.self) { | |
self = .number(NSNumber(value: 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 { | |
throw DecodingError.typeMismatch(JSON.self, DecodingError.Context(codingPath: decoder.codingPath, debugDescription: "Unable to decode JSON as any of the possible types.")) | |
} | |
} | |
public func encode(to encoder: Encoder) throws { | |
var container = encoder.singleValueContainer() | |
switch self { | |
case .null: try container.encodeNil() | |
case .number(let number): | |
if number.objCType.pointee == 0x64 /* 'd' */ { | |
try container.encode(number.doubleValue) | |
} else { | |
try container.encode(number.intValue) | |
} | |
case .string(let string): try container.encode(string) | |
case .array(let array): try container.encode(array) | |
case .dictionary(let dictionary): try container.encode(dictionary) | |
} | |
} | |
} | |
fileprivate extension SingleValueDecodingContainer { | |
func decodeIfMatched<T : Decodable>(_ type: T.Type) throws -> T? { | |
do { | |
return try self.decode(T.self) | |
} catch DecodingError.typeMismatch { | |
return nil | |
} | |
} | |
} | |
let json = """ | |
{ | |
"values": [ | |
[1, null, "hi"], | |
{"hello": "world"} | |
] | |
} | |
""".data(using: .utf8)! | |
let decoder = JSONDecoder() | |
let decoded = try decoder.decode(JSON.self, from: json) | |
print(decoded.value) |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment