Last active
February 8, 2021 09:10
-
-
Save mengoreo/c75103d8f79d2a8fd1c2f178655f4803 to your computer and use it in GitHub Desktop.
Re-implement AnyCodable
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
import Foundation | |
struct AnyCodable: Codable { | |
let value: Any | |
init<T>(_ value: T?) { | |
self.value = value ?? () | |
} | |
} | |
extension AnyCodable { | |
// MARK: - DECODE | |
init(from decoder: Decoder) throws { | |
let container = try decoder.singleValueContainer() | |
// MARK: Swift.Misc | |
if container.decodeNil() { | |
self.init(Self?.none) | |
} else if | |
let decoded = try? container.decode(CodableBox<Bool>.self), | |
let opened = decoded.opened | |
{ | |
self.init(opened) | |
} | |
// MARK: Swift.Math | |
else if | |
let decoded = try? container.decode(CodableBox<Int>.self), | |
let opened = decoded.opened | |
{ | |
self.init(opened) | |
} else if | |
let decoded = try? container.decode(CodableBox<Int8>.self), | |
let opened = decoded.opened | |
{ | |
self.init(opened) | |
} else if | |
let decoded = try? container.decode(CodableBox<Int16>.self), | |
let opened = decoded.opened | |
{ | |
self.init(opened) | |
} else if | |
let decoded = try? container.decode(CodableBox<Int32>.self), | |
let opened = decoded.opened | |
{ | |
self.init(opened) | |
} else if | |
let decoded = try? container.decode(CodableBox<Int64>.self), | |
let opened = decoded.opened | |
{ | |
self.init(opened) | |
} else if | |
let decoded = try? container.decode(CodableBox<UInt>.self), | |
let opened = decoded.opened | |
{ | |
self.init(opened) | |
} else if | |
let decoded = try? container.decode(CodableBox<UInt8>.self), | |
let opened = decoded.opened | |
{ | |
self.init(opened) | |
} else if | |
let decoded = try? container.decode(CodableBox<UInt16>.self), | |
let opened = decoded.opened | |
{ | |
self.init(opened) | |
} else if | |
let decoded = try? container.decode(CodableBox<UInt32>.self), | |
let opened = decoded.opened | |
{ | |
self.init(opened) | |
} else if | |
let decoded = try? container.decode(CodableBox<UInt64>.self), | |
let opened = decoded.opened | |
{ | |
self.init(opened) | |
} else if | |
let decoded = try? container.decode(CodableBox<Float>.self), | |
let opened = decoded.opened | |
{ | |
self.init(opened) | |
} else if | |
let decoded = try? container.decode(CodableBox<Double>.self), | |
let opened = decoded.opened | |
{ | |
self.init(opened) | |
} | |
// MARK: Swift.String | |
else if | |
let decoded = try? container.decode(CodableBox<String>.self), | |
let opened = decoded.opened | |
{ | |
self.init(opened) | |
} | |
// MARK: Swift.Collection | |
else if | |
let decoded = try? container.decode(CodableBox<[AnyCodable]>.self), | |
let opened = decoded.opened | |
{ | |
self.init(opened.map { $0.value }) | |
} else if | |
let decoded = try? container.decode(CodableBox<[String: AnyCodable]>.self), | |
let opened = decoded.opened | |
{ | |
self.init(opened.mapValues { $0.value }) | |
} | |
// MARK: Foundation | |
else if | |
let decoded = try? container.decode(CodableBox<Date>.self), | |
let opened = decoded.opened | |
{ | |
self.init(opened) | |
} else if | |
let decoded = try? container.decode(CodableBox<DateInterval>.self), | |
let opened = decoded.opened | |
{ | |
self.init(opened) | |
} else if | |
let decoded = try? container.decode(CodableBox<URL>.self), | |
let opened = decoded.opened | |
{ | |
self.init(opened) | |
} else if | |
let decoded = try? container.decode(CodableBox<Decimal>.self), | |
let opened = decoded.opened | |
{ | |
self.init(opened) | |
} else if | |
let decoded = try? container.decode(CodableBox<CharacterSet>.self), | |
let opened = decoded.opened | |
{ | |
self.init(opened) | |
} else if | |
let decoded = try? container.decode(CodableBox<Data>.self), | |
let opened = decoded.opened | |
{ | |
self.init(opened) | |
} else if | |
let decoded = try? container.decode(CodableBox<IndexPath>.self), | |
let opened = decoded.opened | |
{ | |
self.init(opened) | |
} else if | |
let decoded = try? container.decode(CodableBox<IndexSet>.self), | |
let opened = decoded.opened | |
{ | |
self.init(opened) | |
} else if | |
let decoded = try? container.decode(CodableBox<TimeZone>.self), | |
let opened = decoded.opened | |
{ | |
self.init(opened) | |
} else if | |
let decoded = try? container.decode(CodableBox<UUID>.self), | |
let opened = decoded.opened | |
{ | |
self.init(opened) | |
} | |
// MARK: Unsupported | |
else { | |
throw DecodingError.dataCorruptedError(in: container, debugDescription: "AnyCodable value cannot be decoded") | |
} | |
} | |
// MARK: - ENCODE | |
func encode(to encoder: Encoder) throws { | |
var container = encoder.singleValueContainer() | |
switch value { | |
// MARK: Swift.Misc | |
case is Void: | |
try container.encodeNil() | |
case let converted as Bool: | |
try container.encode(CodableBox(converted)) | |
// MARK: Swift.Math | |
case let converted as Int: | |
try container.encode(CodableBox(converted)) | |
case let converted as Int8: | |
try container.encode(CodableBox(converted)) | |
case let converted as Int16: | |
try container.encode(CodableBox(converted)) | |
case let converted as Int32: | |
try container.encode(CodableBox(converted)) | |
case let converted as Int64: | |
try container.encode(CodableBox(converted)) | |
case let converted as UInt: | |
try container.encode(CodableBox(converted)) | |
case let converted as UInt8: | |
try container.encode(CodableBox(converted)) | |
case let converted as UInt16: | |
try container.encode(CodableBox(converted)) | |
case let converted as UInt32: | |
try container.encode(CodableBox(converted)) | |
case let converted as UInt64: | |
try container.encode(CodableBox(converted)) | |
case let converted as Float: | |
try container.encode(CodableBox(converted)) | |
case let converted as Double: | |
try container.encode(CodableBox(converted)) | |
// MARK: Swift.String | |
case let converted as String: | |
try container.encode(CodableBox(converted)) | |
// MARK: Swift.Collections | |
case let converted as [Any?]: | |
let codables = converted.map { AnyCodable($0) } | |
try container.encode(CodableBox(codables)) | |
case let converted as Set<AnyHashable>: | |
// set will converted to array since no hashable conformance on anycodable | |
let codables = converted.map { AnyCodable($0.base) } | |
try container.encode(CodableBox(codables)) | |
case let converted as [String: Any?]: | |
let codables = converted.mapValues { AnyCodable($0) } | |
try container.encode(CodableBox(codables)) | |
// MARK: Foundation | |
case let converted as Date: | |
try container.encode(CodableBox(converted)) | |
case let converted as DateInterval: | |
try container.encode(CodableBox(converted)) | |
case let converted as URL: | |
try container.encode(CodableBox(converted)) | |
case let converted as Decimal: | |
try container.encode(CodableBox(converted)) | |
case let converted as CharacterSet: | |
try container.encode(CodableBox(converted)) | |
case let converted as Data: | |
try container.encode(CodableBox(converted)) | |
case let converted as IndexPath: | |
try container.encode(CodableBox(converted)) | |
case let converted as IndexSet: | |
try container.encode(CodableBox(converted)) | |
case let converted as TimeZone: | |
try container.encode(CodableBox(converted)) | |
case let converted as UUID: | |
try container.encode(CodableBox(converted)) | |
// MARK: Unsupported | |
default: | |
let context = EncodingError.Context(codingPath: container.codingPath, debugDescription: "AnyCodable value cannot be encoded") | |
throw EncodingError.invalidValue(value, context) | |
} | |
} | |
} | |
// MARK: - EQUATABLE | |
extension AnyCodable: Equatable { | |
static func == (lhs: AnyCodable, rhs: AnyCodable) -> Bool { | |
switch (lhs.value, rhs.value) { | |
// MARK: - Swift.Misc | |
case is (Void, Void): | |
return true | |
case let (lhs as Bool, rhs as Bool): | |
return lhs == rhs | |
// MARK: Swift.Math | |
case let (lhs as Int, rhs as Int): | |
return lhs == rhs | |
case let (lhs as Int8, rhs as Int8): | |
return lhs == rhs | |
case let (lhs as Int16, rhs as Int16): | |
return lhs == rhs | |
case let (lhs as Int32, rhs as Int32): | |
return lhs == rhs | |
case let (lhs as Int64, rhs as Int64): | |
return lhs == rhs | |
case let (lhs as UInt, rhs as UInt): | |
return lhs == rhs | |
case let (lhs as UInt8, rhs as UInt8): | |
return lhs == rhs | |
case let (lhs as UInt16, rhs as UInt16): | |
return lhs == rhs | |
case let (lhs as UInt32, rhs as UInt32): | |
return lhs == rhs | |
case let (lhs as UInt64, rhs as UInt64): | |
return lhs == rhs | |
case let (lhs as Float, rhs as Float): | |
return lhs == rhs | |
case let (lhs as Double, rhs as Double): | |
return lhs == rhs | |
// MARK: Swift.String | |
case let (lhs as String, rhs as String): | |
return lhs == rhs | |
// MARK: Swift.Collections | |
case let (lhs as [String: AnyCodable], rhs as [String: AnyCodable]): | |
return lhs == rhs | |
case let (lhs as [String: Any], rhs as [String: Any]): | |
return lhs.mapValues { AnyCodable($0) } == rhs.mapValues { AnyCodable($0) } | |
case let (lhs as [AnyCodable], rhs as [AnyCodable]): | |
return lhs == rhs | |
case let (lhs as [Any], rhs as [Any]): | |
return lhs.map { AnyCodable($0) } == rhs.map { AnyCodable($0) } | |
// MARK: Foundation | |
case let (lhs as Date, rhs as Date): | |
return lhs == rhs | |
case let (lhs as DateInterval, rhs as DateInterval): | |
return lhs == rhs | |
case let (lhs as URL, rhs as URL): | |
return lhs == rhs | |
case let (lhs as Decimal, rhs as Decimal): | |
return lhs == rhs | |
case let (lhs as CharacterSet, rhs as CharacterSet): | |
return lhs == rhs | |
case let (lhs as Data, rhs as Data): | |
return lhs == rhs | |
case let (lhs as IndexPath, rhs as IndexPath): | |
return lhs == rhs | |
case let (lhs as IndexSet, rhs as IndexSet): | |
return lhs == rhs | |
case let (lhs as TimeZone, rhs as TimeZone): | |
return lhs == rhs | |
case let (lhs as UUID, rhs as UUID): | |
return lhs == rhs | |
// MARK: Unsupported | |
default: | |
return false | |
} | |
} | |
} | |
extension AnyCodable: CustomStringConvertible { | |
var description: String { | |
switch value { | |
case is Void: | |
return String(describing: nil as Any?) | |
case let value as CustomStringConvertible: | |
return value.description | |
default: | |
return String(describing: value) | |
} | |
} | |
} | |
extension AnyCodable: CustomDebugStringConvertible { | |
var debugDescription: String { | |
switch value { | |
case let value as CustomDebugStringConvertible: | |
return "AnyCodable(\(value.debugDescription))" | |
default: | |
return "AnyCodable(\(description))" | |
} | |
} | |
} | |
extension AnyCodable: ExpressibleByNilLiteral {} | |
extension AnyCodable: ExpressibleByBooleanLiteral {} | |
extension AnyCodable: ExpressibleByIntegerLiteral {} | |
extension AnyCodable: ExpressibleByFloatLiteral {} | |
extension AnyCodable: ExpressibleByStringLiteral {} | |
extension AnyCodable: ExpressibleByArrayLiteral {} | |
extension AnyCodable: ExpressibleByDictionaryLiteral {} | |
extension AnyCodable { | |
init(nilLiteral _: ()) { | |
self.init(nil as Any?) | |
} | |
init(booleanLiteral value: Bool) { | |
self.init(value) | |
} | |
init(integerLiteral value: Int) { | |
self.init(value) | |
} | |
init(floatLiteral value: Double) { | |
self.init(value) | |
} | |
init(extendedGraphemeClusterLiteral value: String) { | |
self.init(value) | |
} | |
init(stringLiteral value: String) { | |
self.init(value) | |
} | |
init(arrayLiteral elements: Any...) { | |
self.init(elements) | |
} | |
init(dictionaryLiteral elements: (AnyHashable, Any)...) { | |
self.init([AnyHashable: Any](elements, uniquingKeysWith: { first, _ in first })) | |
} | |
} |
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
func testAllSupportedTypes() throws { | |
let dictionary: AnyCodable = [ | |
"Void": (), | |
"Bool": true, | |
"Int": Int(1), | |
"Int8": Int8(2), | |
"Int16": Int16(3), | |
"Int32": Int32(4), | |
"Int64": Int64(5), | |
"UInt": UInt(6), | |
"UInt8": UInt8(7), | |
"UInt16": UInt16(8), | |
"UInt32": UInt32(9), | |
"UInt64": UInt64(10), | |
"Float": Float(11.0), | |
"Double": 12.22, | |
"String": "13", | |
"[String: Any]": [ | |
"a": "alpha", | |
"b": "bravo", | |
"c": "charlie", | |
], | |
"[Any]": [1, 2, 3], | |
"Date": Date(), | |
"DateInterval": DateInterval(start: .init(timeIntervalSince1970: 0), end: .init()), | |
"URL": URL(string: "https://example.com")!, | |
"Decimal": Decimal(integerLiteral: 42), | |
"CharacterSet": CharacterSet.urlQueryAllowed, | |
"Data": "Hello 반가워요 初めまして ביי 👩👩👧".data(using: .utf8)!, | |
"IndexPath": IndexPath(indexes: [8, 42]), | |
"IndexSet": IndexSet([8, 42, 7, 3, 256]), | |
"TimeZone": TimeZone.current, | |
"UUID": UUID(), | |
] | |
let encoded = try JSONEncoder().encode(dictionary) | |
let decoded = try JSONDecoder().decode(AnyCodable.self, from: encoded) | |
XCTAssertEqual(decoded, dictionary) | |
} |
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
import Foundation | |
struct CodableBox<T: Codable>: Codable { | |
private let content: [String: T] | |
init(_ value: T) { | |
content = [ | |
"\(T.self).codableBox.X6TFZYQwqbXVYeRmWr5N": value | |
] | |
} | |
var opened: T? { | |
content["\(T.self).codableBox.X6TFZYQwqbXVYeRmWr5N"] | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment