Быстрое декодирование кодируемой необязательной гетерогенной коллекции - PullRequest
0 голосов
/ 08 апреля 2020

Использование этого учебного пособия https://medium.com/@kewindannerfjordremeczki / swift-4-0-decodable-гетерогенных коллекций-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 { $0.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 { $0.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 { $0.object }

ошибки со следующими проблемами

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

Любые указатели как как заставить эти методы работать с необязательной коллекцией, будет высоко ценится

1 Ответ

1 голос
/ 08 апреля 2020

Проблема в том, что вы пытаетесь рекурсивно вызвать decodeIfPresent(family:from:) из собственной реализации, не предоставляя базовый вариант. Вместо этого вы должны вызывать встроенный метод decodeIfPresent, но, к сожалению, его нет в JSONDecoder, он существует только в его контейнерах.

Однако вы можете определить свой собственный decodeIfPresent, просто перехватив DecodingError.keyNotFound и .valueNotFound и вернув для них nil, в противном случае функция может распространить выданную ошибку.

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
    }
}
...