Swift 解码 Codable 可选异构集合

Swift Decoding a Codable optional heterogeneous collection

使用本教程 https://medium.com/@kewindannerfjordremeczki/swift-4-0-decodable-heterogeneous-collections-ecc0e6b468cf 我正在尝试解码异构集合。这适用于非可选集合,但我需要扩展此方法以处理可选集合。

我已经添加了我认为我需要能够执行此操作的方法,但是我在一行中遇到了我不知道如何修复的错误。

/// To support a new class family, create an enum that conforms to this protocol and contains the different types.
protocol ClassFamily: Decodable {
    /// The discriminator key.
    static var discriminator: Discriminator { get }

    /// Returns the class type of the object coresponding to the value.
    func getType() -> AnyObject.Type
}

/// Discriminator key enum used to retrieve discriminator fields in JSON payloads.
enum Discriminator: String, CodingKey {
    case type = "type"
}

extension JSONDecoder {
    /// Decode a heterogeneous list of objects.
    /// - Parameters:
    ///     - family: The ClassFamily enum type to decode with.
    ///     - data: The data to decode.
    /// - Returns: The list of decoded objects.
    func decode<T: ClassFamily, U: Decodable>(family: T.Type, from data: Data) throws -> [U] {
        return try self.decode([ClassWrapper<T, U>].self, from: data).compactMap { [=11=].object }
    }

    /// Decode a optional heterogeneous list of objects.
    /// - Parameters:
    ///     - family: The ClassFamily enum type to decode with.
    ///     - data: The data to decode.
    /// - Returns: The optional list of decoded objects.
    func decodeIfPresent<T: ClassFamily, U: Decodable>(family: T.Type, from data: Data) throws -> [U]? {
        return try self.decodeIfPresent(family: [ClassWrapper<T, U>].self, from: data)?.compactMap { [=11=].object }
    }

    private class ClassWrapper<T: ClassFamily, U: Decodable>: Decodable {
        /// The family enum containing the class information.
        let family: T
        /// The decoded object. Can be any subclass of U.
        let object: U?

        required init(from decoder: Decoder) throws {
            let container = try decoder.container(keyedBy: Discriminator.self)
            // Decode the family with the discriminator.
            family = try container.decode(T.self, forKey: T.discriminator)
            // Decode the object by initialising the corresponding type.
            if let type = family.getType() as? U.Type {
                object = try type.init(from: decoder)
            } else {
                object = nil
            }
        }
    }
}

extension KeyedDecodingContainer {

    /// Decode a heterogeneous list of objects for a given family.
    /// - Parameters:
    ///     - family: The ClassFamily enum for the type family.
    ///     - key: The CodingKey to look up the list in the current container.
    /// - Returns: The resulting list of heterogeneousType elements.
    func decode<T : Decodable, U : ClassFamily>(family: U.Type, forKey key: K) throws -> [T] {
        var container = try self.nestedUnkeyedContainer(forKey: key)
        var list = [T]()
        var tmpContainer = container
        while !container.isAtEnd {
            let typeContainer = try container.nestedContainer(keyedBy: Discriminator.self)
            let family: U = try typeContainer.decode(U.self, forKey: U.discriminator)
            if let type = family.getType() as? T.Type {
                list.append(try tmpContainer.decode(type))
            }
        }
        return list
    }


    /// Optionally decode a heterogeneous list of objects for a given family.
    /// - Parameters:
    ///     - family: The ClassFamily enum for the type family.
    ///     - key: The CodingKey to look up the list in the current container.
    /// - Returns: The resulting list of heterogeneousType elements.
    func decodeIfPresent<T : Decodable, U : ClassFamily>(family: U.Type, forKey key: K) throws -> [T]? {
        var container = try self.nestedUnkeyedContainer(forKey: key)
        var list = [T]()
        var tmpContainer = container
        while !container.isAtEnd {
            let typeContainer = try container.nestedContainer(keyedBy: Discriminator.self)
            let family: U? = try typeContainer.decodeIfPresent(U.self, forKey: U.discriminator)
            if let type = family?.getType() as? T.Type {
                list.append(try tmpContainer.decode(type))
            }
        }
        if list.isEmpty {
            return nil
        } else {
            return list
        }
    }
}

这一行在```JSONDecoder.decodeIfPresent()````

return try self.decodeIfPresent(family: [ClassWrapper<T, U>].self, from: data)?.compactMap { [=12=].object }

以下问题的错误

Generic parameter 'U' could not be inferred
Instance method 'decodeIfPresent(family:from:)' requires that '[JSONDecoder.ClassWrapper<T, U>]' conform to 'ClassFamily'

任何关于如何让这些方法与可选集合一起工作的指示将不胜感激

问题是您试图从 decodeIfPresent(family:from:) 自己的实现中递归调用而不提供基本情况。相反,您应该调用内置的 decodeIfPresent 方法,但遗憾的是 JSONDecoder 上不存在该方法,它仅存在于其容器中。

然而,您可以通过简单地捕获 DecodingError.keyNotFound.valueNotFound 并为它们返回 nil 来定义自己的 decodeIfPresent,否则让函数传播抛出的错误。

func decodeIfPresent<T: ClassFamily, U: Decodable>(family: T.Type, from data: Data) throws -> [U]? {
    do {
        return try self.decode(family: family, from: data)
    } catch DecodingError.keyNotFound {
        return nil
    } catch DecodingError.valueNotFound {
        return nil
    }
}