Skip to content

Instantly share code, notes, and snippets.

@cheungbo-mong
Last active February 8, 2021 09:10
Show Gist options
  • Save cheungbo-mong/c75103d8f79d2a8fd1c2f178655f4803 to your computer and use it in GitHub Desktop.
Save cheungbo-mong/c75103d8f79d2a8fd1c2f178655f4803 to your computer and use it in GitHub Desktop.
Re-implement AnyCodable
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 }))
}
}
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)
}
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