Swift可编码的泛型,保存响应中的通用数据

问题描述 投票:0回答:1

[我将尽最大可能解释我想做的事情,就像我在过去几天搜索Google时一样。

我的应用程序正在与几种不同的API通信,但是让我们首先考虑来自一个API的响应。每个端点的响应都包含一些“通用参数”,例如状态或错误消息,以及我们最感兴趣的单个对象或对象数组,它带来了重要的数据,我们可能想要对其进行编码,存储,放置到Realm,CoreData等。

例如,对单个对象的响应:

{
    "status": "success",
    "response_code": 200,
    "messages": [
        "message1",
        "message2"
    ]
    "data": {
        OBJECT we're interested in.
    }
}

或者,使用对象数组进行响应:

{
    "status": "success",
    "response_code": 200,
    "messages": [
        "message1",
        "message2"
    ]
    "data": [
        {
            OBJECT we're interested in.
        },
        {
            OBJECT we're interested in.
        }
    ]
}

好。这足够简单,易于理解。

现在,我要编写一个包含“通用参数”或“ statusresponse_codemessages的“根”对象,并具有用于特定对象(或对象数组)的另一个属性我们感兴趣。


继承第一种方法是创建一个根对象,如下所示:

class Root: Codable {
    let status: String
    let response_code: Int
    let messages: [String]?

    private enum CodingKeys: String, CodingKey {
        case status, response_code, messages
    }

    required public init(from decoder: Decoder) throws {
        let container = try? decoder.container(keyedBy: CodingKeys.self)
        status = try container?.decodeIfPresent(String.self, forKey: .code) ?? ""
        response_code = try container?.decodeIfPresent(Int.self, forKey: .error) ?? 0
        messages = try container?.decodeIfPresent([String].self, forKey: .key)
    }

    public func encode(to encoder: Encoder) throws {}
}

一旦有了这个Root对象,我就可以创建从该Root对象继承的特定对象,并在JSONDecoder中传递我的特定对象,在那里,我有一个不错的解决方案。但是,此解决方案无法使用数组。也许对于一个没有的人,但是我不能承受太多的压力,我不想制造额外的'复数'对象,该对象只是为了容纳对象数组而存在,例如:

class Objects: Root {
    let objects: [Object]
    // Code that decodes array of "Object" from "data" key
}

struct Object: Codable {
    let property1
    let property2
    let property3
    // Code that decodes all properties of Object
}

它看起来不干净,它需要一个单独的对象,该对象仅保存一个数组,在某些情况下,由于继承,它在存储到Realm时会产生问题,它[[最重要的是产生的可读性较低。


Generics

我的第二个想法是尝试使用泛型,所以我做了a little something like thisstruct Root<T: Codable>: Codable { let status: String let response_code: Int let messages: [String]? let data: T? private enum CodingKeys: String, CodingKey { case status, response_code, messages, data } required public init(from decoder: Decoder) throws { let container = try? decoder.container(keyedBy: CodingKeys.self) status = try container?.decodeIfPresent(String.self, forKey: .code) ?? "" response_code = try container?.decodeIfPresent(Int.self, forKey: .error) ?? 0 messages = try container?.decodeIfPresent([String].self, forKey: .key) data = try container.decodeIfPresent(T.self, forKey: .data) } public func encode(to encoder: Encoder) throws {} }
有了这个,我能够像这样将单个对象和对象数组传递给JSONDecoder:

let decodedValue = try JSONDecoder().decode(Root<Object>.self, from: data) // or let decodedValue = try JSONDecoder().decode(Root<[Object]>.self, from: data)

这非常好。我可以在Root结构的.data属性中获取所需的结构,并根据需要将其用作单个对象或对象数组。我可以轻松地存储它,但是我想不受限制地继承继承来带来更高的示例。在我的情况下,这种想法失败的地方是当我想在不确定T设置的地方访问“公共属性”时。这是对我的应用程序中实际发生情况的简化说明,我将对其进行扩展以说明该通用解决方案不适用于我的地方,并最终提出我的问题。 


问题和问题

如顶部所述,应用程序使用3个API,并且所有3个API都有不同的Root结构,当然还有很多不同的“子结构”-为其命名。我在应用程序中只有一个地方,只有一个APIResponse对象,可以回到应用程序的UI部分,在其中从decoded value中提取1个可读错误,decoded value是该“子结构”,是我的任何“特定对象”,CarDogHousePhone。使用Inheritance解决方案,我可以执行以下操作:struct APIResponse <T> { var value: T? { didSet { extractErrorDescription() } } var errorDescription: String? = "Oops." func extractErrorDescription() { if let responseValue = value as? Root1, let error = responseValue.errors.first { self.errorDescription = error } else if let responseValue = value as? Root2 { self.errorDescription = responseValue.error } else if let responseValue = value as? Root3 { self.errorDescription = responseValue.message } } }
但是使用

Generics

解决方案,我无法做到这一点。如果我尝试用Root1Root2Root3编写相同的代码,如Generics示例中所示,如下所示:func extractErrorDescription() { if let responseValue = value as? Root1, let error = responseValue.errors.first { self.errorDescription = error } }
我会说Generic parameter 'T' could not be inferred in cast to 'Root1'时出错,在这里,我试图提取错误,但我不知道哪个子结构传递给了Root1。是Root1<Dog>还是Root1<Phone>Root1<Car>-我不知道如何计算,而且我显然需要知道以找出值是Root1还是Root2Root3

我正在寻找的解决方案是可以让我通过上面显示的泛型解决方案区分Root对象,或者可以让我以完全不同的方式进行体系结构解码的解决方案,同时牢记我编写的所有内容,尤其是避免“多个”对象的能力

*如果JSON不通过JSON验证程序,请忽略,它是出于这个问题而手写的]**如果编写的代码未运行,请忽略,这更多的是架构问题,而不是如何编译某些代码。

ios swift generics codable jsondecoder
1个回答
0
投票
您在这里寻找的是协议。

protocol ErrorProviding { var error: String? { get } }

我故意将errorDescription更改为error,因为这似乎是您的根类型所具有的(但是您可以在此处绝对重命名)。

然后APIResponse要求:

struct APIResponse<T: ErrorProviding> { var value: T? var error: String? { value?.error } }

然后每个经过特殊处理的根类型都实现了该协议:

extension Root1: ErrorProviding { var error: String? { errors.first } }

但是已经具有正确形状的简单根类型可以声明一致性,而无需额外的实现。

extension Root2: ErrorProviding {}

假设您不仅需要error,还可以将其设置为APIPayload,而不是ErrorProviding,并添加任何其他公共要求。
© www.soinside.com 2019 - 2024. All rights reserved.