Last active
March 28, 2022 14:39
-
-
Save P0ed/31f402b578770f737a6cf2e835e19d63 to your computer and use it in GitHub Desktop.
This file contains hidden or 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 protocol JSONDecodable { | |
init(json: JSON) throws | |
} | |
public protocol JSONContainer { | |
func get() throws -> JSON | |
} | |
public struct AnyJSONContainer { | |
public var json: () throws -> JSON | |
} | |
extension AnyJSONContainer: JSONContainer { | |
public func get() throws -> JSON { try json() } | |
} | |
public struct JSON { | |
public var object: Any | |
public var codingPath: [CodingKey] | |
} | |
public extension JSON { | |
enum Key { | |
case field(String) | |
case index(Int) | |
} | |
} | |
public extension JSON { | |
init(_ object: Any) { self = JSON(object: object, codingPath: []) } | |
func nestedContainer(forKey key: String) throws -> JSON { | |
if let object = (try convert() as NSDictionary)[key] { | |
return JSON(object: object, codingPath: codingPath + [Key.field(key)]) | |
} else { | |
throw DecodingError.keyNotFound(Key.field(key), .path(codingPath)) | |
} | |
} | |
func array() throws -> [JSON] { | |
try convert(NSArray.self).enumerated().map { JSON(object: $1, codingPath: codingPath + [Key.index($0)]) } | |
} | |
func decode<A>(_ transform: (JSON) throws -> A) throws -> A { | |
try decodeOptional(transform) | |
?? { throw DecodingError.valueNotFound(A.self, .path(codingPath)) }() | |
} | |
func decodeOptional<A>(_ transform: (JSON) throws -> A) throws -> A? { | |
object is NSNull ? nil : try transform(self) | |
} | |
func convert<A>(_ type: A.Type = A.self) throws -> A { | |
try decode { json in try (json.object as? A) | |
?? { throw DecodingError.typeMismatch(A.self, .path(json.codingPath)) }() | |
} | |
} | |
} | |
extension JSON.Key: CodingKey { | |
public var stringValue: String { | |
switch self { | |
case .field(let value): return value | |
case .index(let value): return "\(value)" | |
} | |
} | |
public var intValue: Int? { | |
switch self { | |
case .field: return nil | |
case .index(let value): return value | |
} | |
} | |
public init?(stringValue: String) { self = .field(stringValue) } | |
public init?(intValue: Int) { self = .index(intValue) } | |
} | |
extension JSON: JSONContainer { | |
public func get() throws -> JSON { self } | |
} | |
public extension JSONContainer { | |
subscript(_ field: String) -> JSONContainer { AnyJSONContainer { try self.get().nestedContainer(forKey: field) } } | |
var ifPresent: JSON? { try? get() } | |
func array() throws -> [JSON] { try get().array() } | |
func convert<A>(_ type: A.Type = A.self) throws -> A { try get().convert() } | |
func decode<A>(_ transform: (JSON) throws -> A) throws -> A { try get().decode(transform) } | |
func decode<A: JSONDecodable>(_ type: A.Type = A.self) throws -> A { try decode(A.init(json:)) } | |
func decodeOptional<A>(_ transform: (JSON) throws -> A) throws -> A? { try get().decodeOptional(transform) } | |
func decodeOptional<A: JSONDecodable>(_ type: A.Type = A.self) throws -> A? { try decodeOptional(A.init(json:)) } | |
} | |
extension Array: JSONDecodable where Element: JSONDecodable { | |
public init(json: JSON) throws { self = try json.array().map { try $0.decode() } } | |
} | |
// MARK: Types convertible from `Any` with `as?` operator | |
public protocol JSONConvertible: JSONDecodable {} | |
public extension JSONConvertible { | |
init(json: JSON) throws { self = try json.convert() } | |
} | |
extension NSDictionary: JSONConvertible {} | |
extension NSArray: JSONConvertible {} | |
extension NSNumber: JSONConvertible {} | |
extension String: JSONConvertible {} | |
extension Int: JSONConvertible {} | |
extension Int8: JSONConvertible {} | |
extension Int16: JSONConvertible {} | |
extension Int32: JSONConvertible {} | |
extension Int64: JSONConvertible {} | |
extension UInt: JSONConvertible {} | |
extension UInt8: JSONConvertible {} | |
extension UInt16: JSONConvertible {} | |
extension UInt32: JSONConvertible {} | |
extension UInt64: JSONConvertible {} | |
extension Float: JSONConvertible {} | |
extension Double: JSONConvertible {} | |
extension Bool: JSONConvertible {} | |
private extension DecodingError.Context { | |
static func path(_ codingPath: [CodingKey]) -> DecodingError.Context { | |
DecodingError.Context( | |
codingPath: codingPath, | |
debugDescription: codingPath.map { $0.stringValue }.debugDescription | |
) | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment