// From https://github.com/Flight-School/AnyCodable/blob/master/Sources/AnyCodable/AnyDecodable.swift // swiftlint:disable cyclomatic_complexity // swiftlint:disable line_length // swiftlint:disable type_name #if canImport(FoundationEssentials) import FoundationEssentials #else import Foundation #endif /** A type-erased `Decodable` value. The `AnyDecodable` type forwards decoding responsibilities to an underlying value, hiding its specific underlying type. You can decode mixed-type values in dictionaries and other collections that require `Decodable` conformance by declaring their contained type to be `AnyDecodable`: let json = """ { "boolean": true, "integer": 42, "double": 3.141592653589793, "string": "string", "array": [1, 2, 3], "nested": { "a": "alpha", "b": "bravo", "c": "charlie" }, "null": null } """.data(using: .utf8)! let decoder = JSONDecoder() let dictionary = try! decoder.decode([String: AnyDecodable].self, from: json) */ struct AnyDecodable: Decodable { let value: Any init(_ value: T?) { self.value = value ?? () } } @usableFromInline protocol _AnyDecodable { var value: Any { get } init(_ value: T?) } extension AnyDecodable: _AnyDecodable {} extension _AnyDecodable { init(from decoder: Decoder) throws { let container = try decoder.singleValueContainer() if container.decodeNil() { self.init(Optional.none) } 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 uint = try? container.decode(UInt.self) { self.init(uint) } 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([AnyDecodable].self) { self.init(array.map { $0.value }) } else if let dictionary = try? container.decode([String: AnyDecodable].self) { self.init(dictionary.mapValues { $0.value }) } else { throw DecodingError.dataCorruptedError(in: container, debugDescription: "AnyDecodable value cannot be decoded") } } } extension AnyDecodable: Equatable { static func == (lhs: AnyDecodable, rhs: AnyDecodable) -> Bool { switch (lhs.value, rhs.value) { case (Optional.none, Optional.none), 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 [String: AnyDecodable], rhs as [String: AnyDecodable]): return lhs == rhs case let (lhs as [AnyDecodable], rhs as [AnyDecodable]): return lhs == rhs default: return false } } } extension AnyDecodable: 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 AnyDecodable: CustomDebugStringConvertible { var debugDescription: String { switch value { case let value as CustomDebugStringConvertible: return "AnyDecodable(\(value.debugDescription))" default: return "AnyDecodable(\(description))" } } } extension AnyDecodable: Hashable { func hash(into hasher: inout Hasher) { switch value { case let value as Bool: hasher.combine(value) case let value as Int: hasher.combine(value) case let value as Int8: hasher.combine(value) case let value as Int16: hasher.combine(value) case let value as Int32: hasher.combine(value) case let value as Int64: hasher.combine(value) case let value as UInt: hasher.combine(value) case let value as UInt8: hasher.combine(value) case let value as UInt16: hasher.combine(value) case let value as UInt32: hasher.combine(value) case let value as UInt64: hasher.combine(value) case let value as Float: hasher.combine(value) case let value as Double: hasher.combine(value) case let value as String: hasher.combine(value) case let value as [String: AnyDecodable]: hasher.combine(value) case let value as [AnyDecodable]: hasher.combine(value) default: break } } } // swiftlint:enable cyclomatic_complexity // swiftlint:enable line_length // swiftlint:enable type_name