/** * 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(_ 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 })) } }