Skip to content

Instantly share code, notes, and snippets.

@maartene
Last active March 29, 2020 15:23
Show Gist options
  • Save maartene/94ccc4981ceae1daf51762f1e4d635b3 to your computer and use it in GitHub Desktop.
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.
// 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