Last active
November 23, 2019 00:10
-
-
Save asaday/5938fda14de94777e9c2fb629ea8abb9 to your computer and use it in GitHub Desktop.
JSON/Object Decoder
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
typealias ObjDecoderConverter = ((_ path: [CodingKey], _ container: [String: Any]) -> Any?) | |
class ObjDecoder: Decoder { | |
var converter: ObjDecoderConverter? | |
var useThrow = false | |
var codingPath: [CodingKey] = [] | |
var userInfo: [CodingUserInfoKey: Any] = [:] | |
var container: Any = NSNull() | |
fileprivate var _iso8601Formatter: ISO8601DateFormatter = { | |
let formatter = ISO8601DateFormatter() | |
formatter.formatOptions = .withInternetDateTime | |
return formatter | |
}() | |
init(converter: ObjDecoderConverter? = nil) { | |
self.converter = converter | |
} | |
func throwDecodingError(_ e: DecodingError) throws { | |
if useThrow { throw e } | |
} | |
public func decode<T: Decodable>(_ type: T.Type, from value: Any) throws -> T { | |
var src = value | |
if let s = src as? String { src = s.data(using: .utf8) ?? Data() } | |
if let d = src as? Data { src = try JSONSerialization.jsonObject(with: d, options: []) } | |
if let a = src as? AnyCodable { src = a.value } | |
container = src | |
return try decodeValue(container, type: type) | |
} | |
public func optDecode<T: Decodable>(_ type: T.Type, from value: Any) -> T? { | |
do { return try decode(type, from: value) } | |
catch { return nil } | |
} | |
func singleValueContainer() throws -> SingleValueDecodingContainer { | |
return self | |
} | |
func unkeyedContainer() throws -> UnkeyedDecodingContainer { | |
guard let v = container as? [Any] else { | |
try throwDecodingError(DecodingError.valueNotFound([Any].self, DecodingError.Context(codingPath: codingPath, debugDescription: "not array"))) | |
return UnkeyDC(decoder: self, container: []) | |
} | |
return UnkeyDC(decoder: self, container: v) | |
} | |
func container<Key>(keyedBy _: Key.Type) throws -> KeyedDecodingContainer<Key> { | |
guard let v = container as? [String: Any] else { | |
try throwDecodingError(DecodingError.typeMismatch([String: Any].self, DecodingError.Context(codingPath: codingPath, debugDescription: "no key"))) | |
return KeyedDecodingContainer(KeyDCP<Key>(decoder: self, container: [:])) | |
} | |
return KeyedDecodingContainer(KeyDCP<Key>(decoder: self, container: v)) | |
} | |
func copy(with value: Any) -> ObjDecoder { | |
let d = ObjDecoder() | |
d.container = value | |
d.codingPath = codingPath | |
d.userInfo = userInfo | |
d.useThrow = useThrow | |
d.converter = converter | |
return d | |
} | |
func decodeValue<T>(_ value: Any, type: T.Type) throws -> T where T: Decodable { | |
return try (unbox(value: value, type: type) as? T) ?? type.init(from: copy(with: value)) | |
} | |
// for array | |
struct UnkeyDC: UnkeyedDecodingContainer { | |
let decoder: ObjDecoder | |
var codingPath: [CodingKey] { return decoder.codingPath } | |
var count: Int? { return container.count } | |
var isAtEnd: Bool { return currentIndex >= container.count } | |
var currentIndex: Int = 0 | |
let container: [Any] | |
init(decoder: ObjDecoder, container: [Any]) { | |
self.decoder = decoder | |
self.container = container | |
} | |
mutating func popValue() throws -> Any { | |
if isAtEnd { | |
try decoder.throwDecodingError(DecodingError.valueNotFound(Any.self, DecodingError.Context(codingPath: decoder.codingPath, debugDescription: "Unkeyed at end"))) | |
} | |
let value = container[currentIndex] | |
currentIndex += 1 | |
return value | |
} | |
mutating func decodeNil() throws -> Bool { | |
decoder.codingPath.append(CodingKeys(intValue: currentIndex)!) | |
defer { decoder.codingPath.removeLast() } | |
if try popValue() is NSNull { return true } | |
currentIndex -= 1 | |
return false | |
} | |
mutating func decode<T>(_ type: T.Type) throws -> T where T: Decodable { | |
decoder.codingPath.append(CodingKeys(intValue: currentIndex)!) | |
defer { decoder.codingPath.removeLast() } | |
return try decoder.decodeValue(popValue(), type: type) | |
} | |
mutating func nestedContainer<NestedKey>(keyedBy type: NestedKey.Type) throws -> KeyedDecodingContainer<NestedKey> where NestedKey: CodingKey { | |
return try superDecoder().container(keyedBy: type) | |
} | |
mutating func nestedUnkeyedContainer() throws -> UnkeyedDecodingContainer { | |
return try superDecoder().unkeyedContainer() | |
} | |
mutating func superDecoder() throws -> Decoder { | |
return try decoder.copy(with: popValue()) | |
} | |
} | |
// for dictionary | |
struct KeyDCP<Key: CodingKey>: KeyedDecodingContainerProtocol { | |
let decoder: ObjDecoder | |
var codingPath: [CodingKey] { return decoder.codingPath } | |
var allKeys: [Key] { return container.keys.compactMap { Key(stringValue: $0) } } | |
let container: [String: Any] | |
init(decoder: ObjDecoder, container: [String: Any]) { | |
self.decoder = decoder | |
self.container = container | |
} | |
func getValue(key: Key) throws -> Any { | |
var ov: Any? | |
if let c = decoder.converter?(codingPath, container) { ov = c } | |
else { ov = container[key.stringValue] } | |
guard let v = ov else { | |
try decoder.throwDecodingError(DecodingError.keyNotFound(key, DecodingError.Context(codingPath: decoder.codingPath, debugDescription: "No value associated with key"))) | |
return NSNull() // dont throw no key | |
} | |
return v | |
} | |
func contains(_ key: Key) -> Bool { | |
return container.keys.contains(key.stringValue) | |
} | |
func decodeNil(forKey key: Key) throws -> Bool { | |
decoder.codingPath.append(key) | |
defer { decoder.codingPath.removeLast() } | |
return try getValue(key: key) is NSNull | |
} | |
func decode<T>(_ type: T.Type, forKey key: Key) throws -> T where T: Decodable { | |
decoder.codingPath.append(key) | |
defer { decoder.codingPath.removeLast() } | |
return try decoder.decodeValue(getValue(key: key), type: type) | |
} | |
func nestedContainer<NestedKey>(keyedBy type: NestedKey.Type, forKey key: Key) throws -> KeyedDecodingContainer<NestedKey> where NestedKey: CodingKey { | |
return try superDecoder(forKey: key).container(keyedBy: type) | |
} | |
func nestedUnkeyedContainer(forKey key: Key) throws -> UnkeyedDecodingContainer { | |
return try superDecoder(forKey: key).unkeyedContainer() | |
} | |
func superDecoder() throws -> Decoder { | |
return try superDecoder(forKey: Key(stringValue: "super")!) | |
} | |
func superDecoder(forKey key: Key) throws -> Decoder { | |
return try decoder.copy(with: getValue(key: key)) | |
} | |
} | |
} | |
// for single value | |
extension ObjDecoder: SingleValueDecodingContainer { | |
func decodeNil() -> Bool { return container is NSNull } | |
func decode<T>(_ type: T.Type) throws -> T where T: Decodable { | |
return try decodeValue(container, type: type) | |
} | |
} | |
extension ObjDecoder { | |
func decodeNumber(_ value: Any) throws -> NSNumber { | |
if let v = value as? NSNumber { return v } | |
if let v = value as? Int { return NSNumber(value: v) } | |
if let s = value as? String { | |
if s.lowercased() == "true" { return NSNumber(value: true) } | |
if s.lowercased() == "false" { return NSNumber(value: false) } | |
if let v = Int(s) { return NSNumber(value: v) } | |
} | |
try throwDecodingError(DecodingError.typeMismatch(Int.self, DecodingError.Context(codingPath: codingPath, debugDescription: "decode number"))) | |
return 0 // default | |
} | |
func decodeString(_ value: Any) throws -> String { | |
if let v = value as? String { return v } | |
if let v = value as? NSNumber { return v.stringValue } | |
try throwDecodingError(DecodingError.typeMismatch(String.self, DecodingError.Context(codingPath: codingPath, debugDescription: "decode number"))) | |
return "" // default | |
} | |
func unbox<T>(value: Any, type: T.Type) throws -> Any? { | |
switch type { | |
case is Int.Type: return try decodeNumber(value).intValue | |
case is Float.Type: return try decodeNumber(value).floatValue | |
case is Double.Type: return try decodeNumber(value).doubleValue | |
case is Bool.Type: return try decodeNumber(value).boolValue | |
case is Int8.Type: return try decodeNumber(value).int8Value | |
case is Int16.Type: return try decodeNumber(value).int16Value | |
case is Int32.Type: return try decodeNumber(value).int32Value | |
case is Int64.Type: return try decodeNumber(value).int64Value | |
case is UInt.Type: return try decodeNumber(value).uintValue | |
case is UInt8.Type: return try decodeNumber(value).uint8Value | |
case is UInt16.Type: return try decodeNumber(value).uint16Value | |
case is UInt32.Type: return try decodeNumber(value).uint32Value | |
case is UInt64.Type: return try decodeNumber(value).uint64Value | |
case is String.Type: return try decodeString(value) | |
case is URL.Type: | |
return try (URL(string: decodeString(value)) ?? URL(fileURLWithPath: "none")) | |
case is Data.Type: | |
if let d = value as? Data { return d } | |
if let s = value as? String { return Data(base64Encoded: s) } | |
case is Date.Type: | |
if let d = value as? Date { return d } | |
if let s = value as? String, let d = _iso8601Formatter.date(from: s) { return d } | |
if let n = value as? Int { return Date(timeIntervalSince1970: TimeInterval(n)) } | |
default: break | |
} | |
if !useThrow { | |
if type is AnyCodable.Type { return AnyCodable(value) } | |
} | |
if let v = value as? T { return v } | |
return nil | |
} | |
} | |
extension Decoder { | |
func tes<T:RawRepresentable>(_ def:T) throws -> T where T.RawValue == String { | |
return try T.init(rawValue: singleValueContainer().decode(String.self)) ?? def | |
} | |
func es<T:RawRepresentable>(_ def:T) -> T where T.RawValue == String { | |
return (try? tes(def)) ?? def | |
} | |
} | |
struct CodingKeys: CodingKey { | |
var stringValue: String | |
var intValue: Int? | |
init?(intValue: Int) { stringValue = "\(intValue)"; self.intValue = intValue } | |
init?(stringValue: String) { self.stringValue = stringValue } | |
} | |
struct AnyCodable: Codable { | |
var value: Any | |
init(_ value: Any) { self.value = value } | |
init(from decoder: Decoder) throws { | |
if let container = try? decoder.container(keyedBy: CodingKeys.self) { | |
var result: [String: Any] = [:] | |
try container.allKeys.forEach { (key) throws in | |
result[key.stringValue] = try container.decode(AnyCodable.self, forKey: key).value | |
} | |
value = result | |
return | |
} | |
if var container = try? decoder.unkeyedContainer() { | |
var result: [Any] = [] | |
while !container.isAtEnd { result.append(try container.decode(AnyCodable.self).value) } | |
value = result | |
return | |
} | |
if let container = try? decoder.singleValueContainer() { | |
if let v = try? container.decode(Int.self) { value = v; return } | |
if let v = try? container.decode(Double.self) { value = v; return } | |
if let v = try? container.decode(Bool.self) { value = v; return } | |
if let v = try? container.decode(String.self) { value = v; return } | |
if let v = try? container.decode([AnyCodable].self) { value = v; return } | |
if let v = try? container.decode([String: AnyCodable].self) { value = v; return } | |
} | |
throw DecodingError.dataCorrupted(DecodingError.Context(codingPath: decoder.codingPath, debugDescription: "can not decode")) | |
} | |
func encode(to encoder: Encoder) throws { | |
if let array = value as? [Any] { | |
var container = encoder.unkeyedContainer() | |
for value in array { | |
let decodable = AnyCodable(value) | |
try container.encode(decodable) | |
} | |
return | |
} | |
if let dictionary = value as? [String: Any] { | |
var container = encoder.container(keyedBy: CodingKeys.self) | |
for (key, value) in dictionary { | |
let codingKey = CodingKeys(stringValue: key)! | |
let decodable = AnyCodable(value) | |
try container.encode(decodable, forKey: codingKey) | |
} | |
return | |
} | |
var container = encoder.singleValueContainer() | |
if let intVal = value as? Int { try container.encode(intVal); return } | |
if let doubleVal = value as? Double { try container.encode(doubleVal); return } | |
if let boolVal = value as? Bool { try container.encode(boolVal); return } | |
if let stringVal = value as? String { try container.encode(stringVal); return } | |
throw EncodingError.invalidValue(value, EncodingError.Context(codingPath: encoder.codingPath, debugDescription: "can not encode")) | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment