214 lines
7.1 KiB
Swift
214 lines
7.1 KiB
Swift
|
|
/**
|
||
|
|
* AnyCodable
|
||
|
|
* Copyright (c) Morten Bjerg Gregersen 2020
|
||
|
|
* MIT license, see LICENSE file for details
|
||
|
|
*/
|
||
|
|
|
||
|
|
import Foundation
|
||
|
|
|
||
|
|
/**
|
||
|
|
A type-erased `Encodable` value.
|
||
|
|
|
||
|
|
Heavily inspired by: https://github.com/Flight-School/AnyCodable
|
||
|
|
|
||
|
|
The above can't be used as a dependency, as it triggers linker errors
|
||
|
|
when Fluxor and FluxorTestSupport is used in a test target.
|
||
|
|
*/
|
||
|
|
public struct AnyCodable {
|
||
|
|
public let value: Any
|
||
|
|
|
||
|
|
public init<T>(_ value: T?) {
|
||
|
|
if let dictionary = value as? [String: AnyCodable] {
|
||
|
|
self.value = dictionary as [AnyHashable: AnyCodable]
|
||
|
|
} else if let dictionary = value as? [String: Any] {
|
||
|
|
self.value = dictionary.mapValues(AnyCodable.init) as [AnyHashable: AnyCodable]
|
||
|
|
} else if let array = value as? [Any], !(array is [AnyCodable]) {
|
||
|
|
self.value = array.map(AnyCodable.init)
|
||
|
|
} else {
|
||
|
|
self.value = value ?? ()
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
extension AnyCodable: Encodable {
|
||
|
|
// swiftlint:disable:next cyclomatic_complexity function_body_length
|
||
|
|
public func encode(to encoder: Encoder) throws {
|
||
|
|
var container = encoder.singleValueContainer()
|
||
|
|
switch value {
|
||
|
|
case is Void:
|
||
|
|
try container.encodeNil()
|
||
|
|
case let bool as Bool:
|
||
|
|
try container.encode(bool)
|
||
|
|
case let int as Int:
|
||
|
|
try container.encode(int)
|
||
|
|
case let int8 as Int8:
|
||
|
|
try container.encode(int8)
|
||
|
|
case let int16 as Int16:
|
||
|
|
try container.encode(int16)
|
||
|
|
case let int32 as Int32:
|
||
|
|
try container.encode(int32)
|
||
|
|
case let int64 as Int64:
|
||
|
|
try container.encode(int64)
|
||
|
|
case let uint as UInt:
|
||
|
|
try container.encode(uint)
|
||
|
|
case let uint8 as UInt8:
|
||
|
|
try container.encode(uint8)
|
||
|
|
case let uint16 as UInt16:
|
||
|
|
try container.encode(uint16)
|
||
|
|
case let uint32 as UInt32:
|
||
|
|
try container.encode(uint32)
|
||
|
|
case let uint64 as UInt64:
|
||
|
|
try container.encode(uint64)
|
||
|
|
case let float as Float:
|
||
|
|
try container.encode(float)
|
||
|
|
case let double as Double:
|
||
|
|
try container.encode(double)
|
||
|
|
case let string as String:
|
||
|
|
try container.encode(string)
|
||
|
|
case let date as Date:
|
||
|
|
try container.encode(date)
|
||
|
|
case let url as URL:
|
||
|
|
try container.encode(url)
|
||
|
|
case let array as [Any?]:
|
||
|
|
try container.encode(array.map { AnyCodable($0) })
|
||
|
|
case let dictionary as [String: Any?]:
|
||
|
|
try container.encode(dictionary.mapValues { AnyCodable($0) })
|
||
|
|
case let encodable as Encodable:
|
||
|
|
try encodable.encode(to: encoder)
|
||
|
|
default:
|
||
|
|
let debugDescription = "Value cannot be encoded"
|
||
|
|
let context = EncodingError.Context(codingPath: container.codingPath, debugDescription: debugDescription)
|
||
|
|
throw EncodingError.invalidValue(value, context)
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
extension AnyCodable: Decodable {
|
||
|
|
public init(from decoder: Decoder) throws {
|
||
|
|
let container = try decoder.singleValueContainer()
|
||
|
|
if container.decodeNil() {
|
||
|
|
self.init(())
|
||
|
|
} else if let bool = try? container.decode(Bool.self) {
|
||
|
|
self.init(bool)
|
||
|
|
} else if let int = try? container.decode(Int.self) {
|
||
|
|
self.init(int)
|
||
|
|
} else if let double = try? container.decode(Double.self) {
|
||
|
|
self.init(double)
|
||
|
|
} else if let string = try? container.decode(String.self) {
|
||
|
|
self.init(string)
|
||
|
|
} else if let array = try? container.decode([AnyCodable].self) {
|
||
|
|
self.init(array)
|
||
|
|
} else if let dictionary = try? container.decode([String: AnyCodable].self) {
|
||
|
|
self.init(dictionary)
|
||
|
|
} else {
|
||
|
|
throw DecodingError.dataCorruptedError(in: container, debugDescription: "Value cannot be decoded")
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
extension AnyCodable: Equatable {
|
||
|
|
// swiftlint:disable:next cyclomatic_complexity
|
||
|
|
public static func == (lhs: AnyCodable, rhs: AnyCodable) -> Bool {
|
||
|
|
switch (lhs.value, rhs.value) {
|
||
|
|
case is (Void, Void):
|
||
|
|
return true
|
||
|
|
case let (lhs as Bool, rhs as Bool):
|
||
|
|
return lhs == rhs
|
||
|
|
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
|
||
|
|
case let (lhs as String, rhs as String):
|
||
|
|
return lhs == rhs
|
||
|
|
case let (lhs as [AnyHashable: AnyCodable], rhs as [AnyHashable: AnyCodable]):
|
||
|
|
return lhs == rhs
|
||
|
|
case let (lhs as [AnyCodable], rhs as [AnyCodable]):
|
||
|
|
return lhs == rhs
|
||
|
|
default:
|
||
|
|
return false
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
extension AnyCodable: CustomStringConvertible {
|
||
|
|
public 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 {
|
||
|
|
public 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: ExpressibleByExtendedGraphemeClusterLiteral {}
|
||
|
|
extension AnyCodable: ExpressibleByStringLiteral {}
|
||
|
|
extension AnyCodable: ExpressibleByArrayLiteral {}
|
||
|
|
extension AnyCodable: ExpressibleByDictionaryLiteral {}
|
||
|
|
public 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(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 }))
|
||
|
|
}
|
||
|
|
}
|