是否有可能有一组符合相同协议的结构也支持Codable?

时间:2019-12-10 15:33:37

标签: ios swift struct codable swift-protocols

我已设置以下协议,并具有2个符合该协议的结构:

protocol ExampleProtocol: Decodable {
    var name: String { get set }
    var length: Int { get set }
}

struct ExampleModel1: ExampleProtocol {
    var name: String
    var length: Int
    var otherData: Array<String>
}

struct ExampleModel2: ExampleProtocol {
    var name: String
    var length: Int
    var dateString: String
}

我想反序列化从服务器收到的一些JSON数据,并且我知道它将在数组中返回ExampleModel1和ExampleModel2的混合:

struct ExampleNetworkResponse: Decodable {
    var someString: String
    var modelArray: Array<ExampleProtocol>
} 

总有没有使用Codable方法并轻松支持这两种模型?还是我需要为每个模型手动反序列化数据?

编辑1:

在结构上符合“可降解”的结果仍然相同:

struct ExampleModel1: ExampleProtocol, Decodable {

    enum CodingKeys: String, CodingKey {
        case name, length, otherData
    }

    init(from decoder: Decoder) throws {
        let container = try decoder.container(keyedBy: CodingKeys.self)
        self.name = try container.decode(String.self, forKey: .name)
        self.length = try container.decode(Int.self, forKey: .length)
        self.otherData = try container.decode(Array<String>.self, forKey: .otherData)
    }

    var name: String
    var length: Int
    var otherData: Array<String>
}

struct ExampleModel2: ExampleProtocol, Decodable {

    enum CodingKeys: String, CodingKey {
        case name, length, dateString
    }

    init(from decoder: Decoder) throws {
        let container = try decoder.container(keyedBy: CodingKeys.self)
        self.name = try container.decode(String.self, forKey: .name)
        self.length = try container.decode(Int.self, forKey: .length)
        self.dateString = try container.decode(String.self, forKey: .dateString)
    }

    var name: String
    var length: Int
    var dateString: String
}

struct ExampleNetworkResponse: Decodable {
    var someString: String
    var modelArray: Array<ExampleProtocol>
}

1 个答案:

答案 0 :(得分:2)

如果ExampleProtocol的数量有限,并且在同一数组中需要使用不同类型的ExampleProtocol,则可以为ExampleProtocol创建一个持有人并将其用于解码/编码

ExampleHolder可以将所有可能的Decodable ExampleProtocol类型保存在一个数组中。因此,解码器init不需要那么多的if-else范围,并且以后更容易添加更多。

建议将ExampleHolder保留为私有结构。因此,不可能在文件之外甚至在ExampleNetworkResponse之外也无法访问它。

enum ExampleNetworkResponseError: Error {
    case unsupportedExampleModelOnDecoding
}

private struct ExampleHolder: Decodable {
    let exampleModel: ExampleProtocol

    private let possibleModelTypes: [ExampleProtocol.Type] = [
        ExampleModel1.self,
        ExampleModel2.self
    ]

    init(from decoder: Decoder) throws {
        for type in possibleModelTypes {
            if let model = try? type.init(from: decoder) {
                exampleModel = model
                return
            }
        }

        throw ExampleNetworkResponseError.unsupportedExampleModelOnDecoding
    }
}

struct ExampleNetworkResponse: Decodable {
    var someString: String
    var modelArray: Array<ExampleProtocol>

    enum CodingKeys: String, CodingKey {
        case someString, modelArray
    }

    init(from decoder: Decoder) throws {
        let container = try decoder.container(keyedBy: CodingKeys.self)
        someString = try container.decode(String.self, forKey: .someString)
        let exampleHolderArray = try container.decode([ExampleHolder].self, forKey: .modelArray)
        modelArray = exampleHolderArray.map({ $0.exampleModel })
    }
}

–––––––––––––––––––––––––––––

如果在一个响应中数组中只能有一种ExampleProtocol,则:

struct ExampleNetworkResponse2<ModelArrayElement: ExampleProtocol>: Decodable {
    var someString: String
    var modelArray: Array<ModelArrayElement>
}

用法:

let decoder = JSONDecoder()
let response = try decoder.decode(
    ExampleNetworkResponse2<ExampleModel1>.self,
    from: dataToDecode
)