-
-
Save sukov/d3834c0e7b72e4f7575f753b352f6ddd to your computer and use it in GitHub Desktop.
Codable (Encode & Decode) [Any] and [String: Any] Swift 5
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
// Original: https://gist.github.com/loudmouth/332e8d89d8de2c1eaf81875cfcd22e24 | |
import Foundation | |
private struct JSONCodingKeys: CodingKey { | |
var stringValue: String | |
init(stringValue: String) { | |
self.stringValue = stringValue | |
} | |
var intValue: Int? | |
init?(intValue: Int) { | |
self.init(stringValue: "\(intValue)") | |
self.intValue = intValue | |
} | |
} | |
extension KeyedDecodingContainer { | |
func decode(_ type: [String: Any].Type, forKey key: K) throws -> [String: Any] { | |
let container = try nestedContainer(keyedBy: JSONCodingKeys.self, forKey: key) | |
return try container.decode(type) | |
} | |
func decode(_ type: [Any].Type, forKey key: K) throws -> [Any] { | |
var container = try nestedUnkeyedContainer(forKey: key) | |
return try container.decode(type) | |
} | |
func decode(_ type: [String: Any].Type) throws -> [String: Any] { | |
var dictionary = [String: Any]() | |
for key in allKeys { | |
if let boolValue = try? decode(Bool.self, forKey: key) { | |
dictionary[key.stringValue] = boolValue | |
} else if let intValue = try? decode(Int.self, forKey: key) { | |
dictionary[key.stringValue] = intValue | |
} else if let stringValue = try? decode(String.self, forKey: key) { | |
dictionary[key.stringValue] = stringValue | |
} else if let doubleValue = try? decode(Double.self, forKey: key) { | |
dictionary[key.stringValue] = doubleValue | |
} else if let nestedDictionary = try? decode([String: Any].self, forKey: key) { | |
dictionary[key.stringValue] = nestedDictionary | |
} else if let nestedArray = try? decode([Any].self, forKey: key) { | |
dictionary[key.stringValue] = nestedArray | |
} else if let isValueNil = try? decodeNil(forKey: key), isValueNil == true { | |
dictionary[key.stringValue] = nil | |
} else { | |
throw DecodingError.dataCorrupted(DecodingError.Context(codingPath: codingPath + [key], debugDescription: "Unable to decode value")) | |
} | |
} | |
return dictionary | |
} | |
} | |
extension UnkeyedDecodingContainer { | |
mutating func decode(_ type: [Any].Type) throws -> [Any] { | |
var array: [Any] = [] | |
while isAtEnd == false { | |
if let value = try? decode(Bool.self) { | |
array.append(value) | |
} else if let value = try? decode(Int.self) { | |
array.append(value) | |
} else if let value = try? decode(String.self) { | |
array.append(value) | |
} else if let value = try? decode(Double.self) { | |
array.append(value) | |
} else if let nestedDictionary = try? decode([String: Any].self) { | |
array.append(nestedDictionary) | |
} else if let nestedArray = try? decodeNestedArray([Any].self) { | |
array.append(nestedArray) | |
} else if let isValueNil = try? decodeNil(), isValueNil == true { | |
array.append(Optional<Any>.none as Any) | |
} else { | |
throw DecodingError.dataCorrupted(DecodingError.Context(codingPath: codingPath, debugDescription: "Unable to decode value")) | |
} | |
} | |
return array | |
} | |
mutating func decode(_ type: [String: Any].Type) throws -> [String: Any] { | |
let container = try nestedContainer(keyedBy: JSONCodingKeys.self) | |
return try container.decode(type) | |
} | |
mutating func decodeNestedArray(_ type: [Any].Type) throws -> [Any] { | |
var container = try nestedUnkeyedContainer() | |
return try container.decode(type) | |
} | |
} | |
extension KeyedEncodingContainerProtocol where Key == JSONCodingKeys { | |
mutating func encode(_ value: [String: Any]) throws { | |
for (key, value) in value { | |
let key = JSONCodingKeys(stringValue: key) | |
switch value { | |
case let value as Bool: | |
try encode(value, forKey: key) | |
case let value as Int: | |
try encode(value, forKey: key) | |
case let value as String: | |
try encode(value, forKey: key) | |
case let value as Double: | |
try encode(value, forKey: key) | |
case let value as [String: Any]: | |
try encode(value, forKey: key) | |
case let value as [Any]: | |
try encode(value, forKey: key) | |
case Optional<Any>.none: | |
try encodeNil(forKey: key) | |
default: | |
throw EncodingError.invalidValue(value, EncodingError.Context(codingPath: codingPath + [key], debugDescription: "Invalid JSON value")) | |
} | |
} | |
} | |
} | |
extension KeyedEncodingContainerProtocol { | |
mutating func encode(_ value: [String: Any]?, forKey key: Key) throws { | |
guard let value = value else { return } | |
var container = nestedContainer(keyedBy: JSONCodingKeys.self, forKey: key) | |
try container.encode(value) | |
} | |
mutating func encode(_ value: [Any]?, forKey key: Key) throws { | |
guard let value = value else { return } | |
var container = nestedUnkeyedContainer(forKey: key) | |
try container.encode(value) | |
} | |
} | |
extension UnkeyedEncodingContainer { | |
mutating func encode(_ value: [Any]) throws { | |
for (index, value) in value.enumerated() { | |
switch value { | |
case let value as Bool: | |
try encode(value) | |
case let value as Int: | |
try encode(value) | |
case let value as String: | |
try encode(value) | |
case let value as Double: | |
try encode(value) | |
case let value as [String: Any]: | |
try encode(value) | |
case let value as [Any]: | |
try encodeNestedArray(value) | |
case Optional<Any>.none: | |
try encodeNil() | |
default: | |
let keys = JSONCodingKeys(intValue: index).map({ [ $0 ] }) ?? [] | |
throw EncodingError.invalidValue(value, EncodingError.Context(codingPath: codingPath + keys, debugDescription: "Invalid JSON value")) | |
} | |
} | |
} | |
mutating func encode(_ value: [String: Any]) throws { | |
var container = nestedContainer(keyedBy: JSONCodingKeys.self) | |
try container.encode(value) | |
} | |
mutating func encodeNestedArray(_ value: [Any]) throws { | |
var container = nestedUnkeyedContainer() | |
try container.encode(value) | |
} | |
} |
Just leaving a comment in case it helps anyone else.
I had a JSON that this was decoding, with double values with no decimal value (e.g. 25.0) - The ordering of the casting in this gist meant that it was cast as an Int successfully - and when accessing this value it was returning '25'
However, I was expecting doubles and in Swift, casting an Int (25) to a double, returns nil... the fix was to swap the ordering so that if it can be a double, it will be otherwise it will be an Int.
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Thanks for this 👍🏻