快速有条件的符合两种情况

时间:2018-07-20 19:24:52

标签: json swift generics protocols decodable

一个简短的问题是,当满足两个条件中的任何一个条件时,我该如何使该类型有条件地符合协议?

我有一个通用类型NetworkResponse<Data>。它代表服务器响应。定义方式如下:

enum NetworkResponse<Data> {
    case success(Data)
    case error(ServerError)
}

我想让NetworkResponse符合Decodable。这是我的服务器响应格式:

{
    "data": {
        "someKey": "someValue",
        "anotherKey": 15
    },
    "meta": {
        "returnCode": 0,
        "returnMessage": "operation is successful"
    }
}

data部分取决于发出的请求。 meta部分表示有关响应的一些元数据。就像是成功还是失败一样。

这就是我实现Decodable的方式:

extension NetworkResponse: Decodable where Data: Decodable {
    enum CodingKeys: CodingKey {
        case meta
        case data
    }

    init(from decoder: Decoder) throws {
        let container = try decoder.container(keyedBy: CodingKeys.self)
        let metaValue = try container.decode(ServerError.self, forKey: .meta)

        if metaValue.code != 0 {
            self = .error(metaValue)
        } else {
            self = .success(try container.decode(Data.self, forKey: .data))
        }
    }
}

到目前为止,一切都很好。但是,这是我的问题。对于某些不需要返回任何数据的api,省略了data部分作为响应。在这种情况下,我的回复将如下所示:

{
    "meta": {
        "returnCode": 0,
        "returnMessage": "operation is successful"
    }
}

在这种情况下,我想将响应json解码为NetworkResponse<Void>。但是由于Void不符合Decodable(因为它是非标称类型),所以编译器给出了错误。

为克服这个问题,我尝试创建Decodable的更专业的扩展,其中DataVoid,如下所示:

extension NetworkResponse: Decodable where Data == Void {
    init(from decoder: Decoder) throws {
        let container = try decoder.container(keyedBy: CodingKeys.self)
        let metaValue = try container.decode(AppErrors.Server.self, forKey: CodingKeys.meta)

        if metaValue.code != 0 {
            self = .error(metaValue)
        } else {
            self = .success(())
        }
    }
}

但是编译器仍然会像这样:Conflicting conformance of 'NetworkResponse<Data>' to protocol 'Decodable'; there cannot be more than one conformance, even with different conditional bounds

那么如何创建init(from:)Data时要使用的单独的Void函数?

1 个答案:

答案 0 :(得分:0)

我强烈建议您将通用参数的类型名称从Data更改为,因为它很容易与整个Swift广泛使用的Foundation.Data混淆。

关于问题本身,您可以制作一个空结构来表示“ void”,并在NetworkResponse中添加新的格:

struct EmptyData: Decodable {}

enum NetworkResponse<T> {
    case success(T)
    case successWithEmptyData
    case error(ServerError)
}

extension NetworkResponse: Decodable where T: Decodable {
    private enum CodingKeys: CodingKey {
        case meta
        case data
    }

    init(from decoder: Decoder) throws {
        let container = try decoder.container(keyedBy: CodingKeys.self)
        let metaValue = try container.decode(ServerError.self, forKey: .meta)

        if metaValue.code != 0 {
            self = .error(metaValue)
        } else if T.self == EmptyData.self {
            self = .successWithEmptyData
        } else {
            self = .success(try container.decode(T.self, forKey: .data))
        }
    }
}

let response = try! JSONDecoder().decode(NetworkResponse<EmptyData>.self, from: jsonData)

或者,当您期望没有数据返回并进行相应解码时,可以使.success案例中包含T?