Last active
March 29, 2020 15:23
-
-
Save maartene/94ccc4981ceae1daf51762f1e4d635b3 to your computer and use it in GitHub Desktop.
A simple wrapper so you get limited Codable comformance for Any values, including Array<Any> and Dictionary<String: Any> values.
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
// A simple way to get Codable comformance for [Any] and [String: Any] types, | |
// where Any can be any basic type as well as nested arrays and dictionaries. | |
// | |
// Use: | |
// let associativeStorage = [String: Any]() | |
// First wrap it | |
// let wrappedAssociativeStorage = try AnyWrapper.wrapperFor(associativeStorage) | |
// Then encode it using: | |
// let data = try encoder.encode(associativeStorage) | |
// | |
// Decode it using: | |
// let decodedWrappedAssociativeStorage = try decoder.decode(AnyWrapper.self, from: data) | |
// And get back your previous dictionary | |
// let decodedAssociativeStorage = decodedWrappedAssociativeStorage.value | |
// | |
// Supports: | |
// Basic types: | |
// - String | |
// - Int | |
// - Double | |
// - UUID | |
// | |
// Collections: | |
// - Array<Any> where Any is a AnyWrapper supported type | |
// - Dictionary<String: Any> where Any is a AnyWrapper supported type | |
// | |
// You can add custom types, but you will need to do so at a per type basis, just to be sure that you | |
// know the type when unwrapping. | |
enum AnyWrapper: Codable { | |
enum AnyWrapperErrors: Error { | |
case cannotConvertError | |
} | |
enum CodingKeys: CodingKey { | |
case type, value | |
} | |
case string(value: String) | |
case int(value: Int) | |
case double(value: Double) | |
case uuid(value: UUID) | |
case array(value: [AnyWrapper]) | |
case stringlyTypedDict(value: [String: AnyWrapper]) | |
static func wrapperFor(_ value: Any) throws -> AnyWrapper { | |
if let v = value as? String { | |
return .string(value: v) | |
} else if let v = value as? Int { | |
return .int(value: v) | |
} else if let v = value as? Double { | |
return .double(value: v) | |
} else if let v = value as? UUID { | |
return .uuid(value: v) | |
} else if let v = value as? [Any] { | |
let wrappedArray = v.compactMap { item in try? AnyWrapper.wrapperFor(item)} | |
return .array(value: wrappedArray) | |
} else if let v = value as? [String: Any] { | |
let wrappedDict = try v.mapValues { itemValue in try AnyWrapper.wrapperFor(itemValue) } | |
return .stringlyTypedDict(value: wrappedDict) | |
} else { | |
throw AnyWrapperErrors.cannotConvertError | |
} | |
} | |
var value: Any { | |
switch self { | |
case .string(let value): | |
return value | |
case .int(let value): | |
return value | |
case .double(let value): | |
return value | |
case .uuid(let value): | |
return value | |
case .array(let array): | |
return array.map {wrappedItem in wrappedItem.value} | |
case .stringlyTypedDict(let dict): | |
let unwrappedDict = dict.mapValues { itemValue in itemValue.value} | |
return unwrappedDict | |
} | |
} | |
func encode(to encoder: Encoder) throws { | |
var container = encoder.container(keyedBy: CodingKeys.self) | |
switch self { | |
case .string(let value): | |
try container.encode("string", forKey: .type) | |
try container.encode(value, forKey: .value) | |
case .int(let value): | |
try container.encode("int", forKey: .type) | |
try container.encode(value, forKey: .value) | |
case .double(let value): | |
try container.encode("double", forKey: .type) | |
try container.encode(value, forKey: .value) | |
case .uuid(let value): | |
try container.encode("uuid", forKey: .type) | |
try container.encode(value, forKey: .value) | |
case .array(let array): | |
try container.encode("array", forKey: .type) | |
try container.encode(array, forKey: .value) | |
case .stringlyTypedDict(let dict): | |
try container.encode("stringlyTypedDict", forKey: .type) | |
try container.encode(dict, forKey: .value) | |
} | |
} | |
init(from decoder: Decoder) throws { | |
let values = try decoder.container(keyedBy: CodingKeys.self) | |
let type = try values.decode(String.self, forKey: .type) | |
switch type { | |
case "string": | |
let v = try values.decode(String.self, forKey: .value) | |
self = .string(value: v) | |
case "int": | |
let v = try values.decode(Int.self, forKey: .value) | |
self = .int(value: v) | |
case "double": | |
let v = try values.decode(Double.self, forKey: .value) | |
self = .double(value: v) | |
case "uuid": | |
let v = try values.decode(UUID.self, forKey: .value) | |
self = .uuid(value: v) | |
case "array": | |
let v = try values.decode([AnyWrapper].self, forKey: .value) | |
self = .array(value: v) | |
case "stringlyTypedDict": | |
let v = try values.decode([String: AnyWrapper].self, forKey: .value) | |
self = .stringlyTypedDict(value: v) | |
default: | |
throw AnyWrapperErrors.cannotConvertError | |
} | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment