Last active
July 17, 2018 08:06
-
-
Save aornano/23179ab94bcb033bde2427d1b3b74a0f to your computer and use it in GitHub Desktop.
Swift 4.x Codable and Decodable "Any" class + "NULL" class that can be used with Codable JSON network calls
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
class JSONAny: Codable { | |
public let value: Any | |
public init<T>(_ value: T?) { | |
self.value = value ?? () | |
} | |
static func decodingError(forCodingPath codingPath: [CodingKey]) -> DecodingError { | |
let context = DecodingError.Context(codingPath: codingPath, debugDescription: "Cannot decode JSONAny") | |
return DecodingError.typeMismatch(JSONAny.self, context) | |
} | |
static func encodingError(forValue value: Any, codingPath: [CodingKey]) -> EncodingError { | |
let context = EncodingError.Context(codingPath: codingPath, debugDescription: "Cannot encode JSONAny") | |
return EncodingError.invalidValue(value, context) | |
} | |
static func decode(from container: SingleValueDecodingContainer) throws -> Any { | |
if let value = try? container.decode(Bool.self) { | |
return value | |
} | |
if let value = try? container.decode(Int64.self) { | |
return value | |
} | |
if let value = try? container.decode(Double.self) { | |
return value | |
} | |
if let value = try? container.decode(String.self) { | |
return value | |
} | |
if container.decodeNil() { | |
return JSONNull() | |
} | |
throw decodingError(forCodingPath: container.codingPath) | |
} | |
static func decode(from container: inout UnkeyedDecodingContainer) throws -> Any { | |
if let value = try? container.decode(Bool.self) { | |
return value | |
} | |
if let value = try? container.decode(Int64.self) { | |
return value | |
} | |
if let value = try? container.decode(Double.self) { | |
return value | |
} | |
if let value = try? container.decode(String.self) { | |
return value | |
} | |
if let value = try? container.decodeNil() { | |
if value { | |
return JSONNull() | |
} | |
} | |
if var container = try? container.nestedUnkeyedContainer() { | |
return try decodeArray(from: &container) | |
} | |
if var container = try? container.nestedContainer(keyedBy: MyCodingKey.self) { | |
return try decodeDictionary(from: &container) | |
} | |
throw decodingError(forCodingPath: container.codingPath) | |
} | |
static func decode(from container: inout KeyedDecodingContainer<MyCodingKey>, forKey key: MyCodingKey) throws -> Any { | |
if let value = try? container.decode(Bool.self, forKey: key) { | |
return value | |
} | |
if let value = try? container.decode(Int64.self, forKey: key) { | |
return value | |
} | |
if let value = try? container.decode(Double.self, forKey: key) { | |
return value | |
} | |
if let value = try? container.decode(String.self, forKey: key) { | |
return value | |
} | |
if let value = try? container.decodeNil(forKey: key) { | |
if value { | |
return JSONNull() | |
} | |
} | |
if var container = try? container.nestedUnkeyedContainer(forKey: key) { | |
return try decodeArray(from: &container) | |
} | |
if var container = try? container.nestedContainer(keyedBy: MyCodingKey.self, forKey: key) { | |
return try decodeDictionary(from: &container) | |
} | |
throw decodingError(forCodingPath: container.codingPath) | |
} | |
static func decodeArray(from container: inout UnkeyedDecodingContainer) throws -> [Any] { | |
var arr: [Any] = [] | |
while !container.isAtEnd { | |
let value = try decode(from: &container) | |
arr.append(value) | |
} | |
return arr | |
} | |
static func decodeDictionary(from container: inout KeyedDecodingContainer<MyCodingKey>) throws -> [String: Any] { | |
var dict = [String: Any]() | |
for key in container.allKeys { | |
let value = try decode(from: &container, forKey: key) | |
dict[key.stringValue] = value | |
} | |
return dict | |
} | |
static func encode(to container: inout UnkeyedEncodingContainer, array: [Any]) throws { | |
for value in array { | |
if let value = value as? Bool { | |
try container.encode(value) | |
} else if let value = value as? Int64 { | |
try container.encode(value) | |
} else if let value = value as? Double { | |
try container.encode(value) | |
} else if let value = value as? String { | |
try container.encode(value) | |
} else if value is JSONNull { | |
try container.encodeNil() | |
} else if let value = value as? [Any] { | |
var container = container.nestedUnkeyedContainer() | |
try encode(to: &container, array: value) | |
} else if let value = value as? [String: Any] { | |
var container = container.nestedContainer(keyedBy: MyCodingKey.self) | |
try encode(to: &container, dictionary: value) | |
} else { | |
throw encodingError(forValue: value, codingPath: container.codingPath) | |
} | |
} | |
} | |
static func encode(to container: inout KeyedEncodingContainer<MyCodingKey>, dictionary: [String: Any]) throws { | |
for (key, value) in dictionary { | |
let key = MyCodingKey(stringValue: key)! | |
if let value = value as? Bool { | |
try container.encode(value, forKey: key) | |
} else if let value = value as? Int64 { | |
try container.encode(value, forKey: key) | |
} else if let value = value as? Double { | |
try container.encode(value, forKey: key) | |
} else if let value = value as? String { | |
try container.encode(value, forKey: key) | |
} else if value is JSONNull { | |
try container.encodeNil(forKey: key) | |
} else if let value = value as? [Any] { | |
var container = container.nestedUnkeyedContainer(forKey: key) | |
try encode(to: &container, array: value) | |
} else if let value = value as? [String: Any] { | |
var container = container.nestedContainer(keyedBy: MyCodingKey.self, forKey: key) | |
try encode(to: &container, dictionary: value) | |
} else { | |
throw encodingError(forValue: value, codingPath: container.codingPath) | |
} | |
} | |
} | |
static func encode(to container: inout SingleValueEncodingContainer, value: Any) throws { | |
if let value = value as? Bool { | |
try container.encode(value) | |
} else if let value = value as? Int64 { | |
try container.encode(value) | |
} else if let value = value as? Double { | |
try container.encode(value) | |
} else if let value = value as? String { | |
try container.encode(value) | |
} else if value is JSONNull { | |
try container.encodeNil() | |
} else { | |
throw encodingError(forValue: value, codingPath: container.codingPath) | |
} | |
} | |
public required init(from decoder: Decoder) throws { | |
if var arrayContainer = try? decoder.unkeyedContainer() { | |
self.value = try JSONAny.decodeArray(from: &arrayContainer) | |
} else if var container = try? decoder.container(keyedBy: MyCodingKey.self) { | |
self.value = try JSONAny.decodeDictionary(from: &container) | |
} else { | |
let container = try decoder.singleValueContainer() | |
self.value = try JSONAny.decode(from: container) | |
} | |
} | |
public func encode(to encoder: Encoder) throws { | |
if let arr = self.value as? [Any] { | |
var container = encoder.unkeyedContainer() | |
try JSONAny.encode(to: &container, array: arr) | |
} else if let dict = self.value as? [String: Any] { | |
var container = encoder.container(keyedBy: MyCodingKey.self) | |
try JSONAny.encode(to: &container, dictionary: dict) | |
} else { | |
var container = encoder.singleValueContainer() | |
try JSONAny.encode(to: &container, value: self.value) | |
} | |
} | |
} | |
class JSONNull: Codable { | |
public init() { | |
} | |
public required init(from decoder: Decoder) throws { | |
let container = try decoder.singleValueContainer() | |
if !container.decodeNil() { | |
throw DecodingError.typeMismatch(JSONNull.self, DecodingError.Context(codingPath: decoder.codingPath, debugDescription: "Wrong type for JSONNull")) | |
} | |
} | |
public func encode(to encoder: Encoder) throws { | |
var container = encoder.singleValueContainer() | |
try container.encodeNil() | |
} | |
} | |
class MyCodingKey : CodingKey { | |
let key: String | |
required init?(intValue: Int) { | |
return nil | |
} | |
required init?(stringValue: String) { | |
key = stringValue | |
} | |
var intValue: Int? { | |
return nil | |
} | |
var stringValue: String { | |
return key | |
} | |
} | |
// Here below some examples that can be used with these classes to replay the oldest NSCoding custom object (as example below "LOGINOBJ") with the new Codable/Decodable framework classes. | |
import Foundation | |
import UIKit | |
struct LoginObj: Codable { | |
var authentication : [String: JSONAny] | |
var info : [String: JSONAny] | |
var ok : String | |
var blacklist : String | |
var message : String | |
var userName: String | |
var passWord: String | |
var mustSaveToFile : Bool | |
enum ListKeys: String, CodingKey { | |
case authentication, info, ok, blacklist, message, userName, passWord, mustSaveToFile | |
} | |
init() { | |
authentication = [String: JSONAny]() | |
info = [String: JSONAny]() | |
ok = "" | |
blacklist = "" | |
message = "" | |
userName = "" | |
passWord = "" | |
mustSaveToFile = false | |
} | |
init (from decoder: Decoder) throws { | |
let container = try decoder.container (keyedBy: ListKeys.self) | |
authentication = try container.decodeIfPresent([String: JSONAny].self, forKey: .authentication) ?? [String: JSONAny]() | |
info = try container.decodeIfPresent([String: JSONAny].self, forKey: .info) ?? [String: JSONAny]() | |
ok = try container.decodeIfPresent (String.self, forKey: .ok) ?? "" | |
blacklist = try container.decodeIfPresent (String.self, forKey: .blacklist) ?? "" | |
message = try container.decodeIfPresent (String.self, forKey: .message) ?? "" | |
userName = try container.decodeIfPresent (String.self, forKey: .userName) ?? "" | |
passWord = try container.decodeIfPresent (String.self, forKey: .passWord) ?? "" | |
mustSaveToFile = try container.decodeIfPresent (Bool.self, forKey: .mustSaveToFile) ?? false | |
} | |
func encode (to encoder: Encoder) throws { | |
var container = encoder.container (keyedBy: ListKeys.self) | |
try container.encode (authentication, forKey: .authentication) | |
try container.encode (info, forKey: .info) | |
try container.encode (ok, forKey: .ok) | |
try container.encode (blacklist, forKey: .blacklist) | |
try container.encode (message, forKey: .message) | |
try container.encode (userName, forKey: .userName) | |
try container.encode (passWord, forKey: .passWord) | |
try container.encode (mustSaveToFile, forKey: .mustSaveToFile) | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment