【问题标题】:How to parse generic response using Codable如何使用 Codable 解析通用响应
【发布时间】:2019-04-06 10:58:57
【问题描述】:

每个 API 在响应中都有三个参数。

  1. Code :表示 API 是成功还是失败(1 或 0)
  2. Message:一个字符串
  3. Data:可以是对象或单个对象的Array

我已经创建了基础模型。

struct ResponseBase<T:Codable> : Codable {

    let code : String?
    let data : [T]
    let message : String?

    enum CodingKeys: String, CodingKey {
        case code = "Code"
        case data = "Data"
        case message = "Message"
    }

    init(from decoder: Decoder) throws {
        let values = try decoder.container(keyedBy: CodingKeys.self)
        code = try values.decodeIfPresent(String.self, forKey: .code)
        data = try values.decodeIfPresent([T].self, forKey: .data)
        message = try values.decodeIfPresent(String.self, forKey: .message)
    }
}

struct SocialWarmer : Codable {

    let createdDate : String?
    let lookUpId : String?
    let lookupKey : String?
    let lookupValue : String?
    let parentId : String?
    let statusFlag : String?
    let type : String?
    let updatedDate : String?

    enum CodingKeys: String, CodingKey {
        case createdDate = "CreatedDate"
        case lookUpId = "LookUpId"
        case lookupKey = "LookupKey"
        case lookupValue = "LookupValue"
        case parentId = "ParentId"
        case statusFlag = "StatusFlag"
        case type = "Type"
        case updatedDate = "UpdatedDate"
    }

    init(from decoder: Decoder) throws {
        let values = try decoder.container(keyedBy: CodingKeys.self)
        createdDate = try values.decodeIfPresent(String.self, forKey: .createdDate)
        lookUpId = try values.decodeIfPresent(String.self, forKey: .lookUpId)
        lookupKey = try values.decodeIfPresent(String.self, forKey: .lookupKey)
        lookupValue = try values.decodeIfPresent(String.self, forKey: .lookupValue)
        parentId = try values.decodeIfPresent(String.self, forKey: .parentId)
        statusFlag = try values.decodeIfPresent(String.self, forKey: .statusFlag)
        type = try values.decodeIfPresent(String.self, forKey: .type)
        updatedDate = try values.decodeIfPresent(String.self, forKey: .updatedDate)
    }

}

以下是 API 请求代码。

class BaseApiClient {

    static let `default`  = BaseApiClient()

    private init() {

    }

    func fetch<model:Codable>(request:APIRouter,decoder : JSONDecoder = JSONDecoder()  ,onSuccess: @escaping ([model]) -> Void) {

        if Connectivity.isReachable {
            (UIApplication.shared.delegate as! AppDelegate).addProgressView()
            Alamofire.request(request).responseJSON { (response) in
                switch response.result {
                case .success( let apiResponse) :
                    DispatchQueue.main.async {
                        (UIApplication.shared.delegate as! AppDelegate).hideProgrssVoew()
                    }
                    if let responseData = apiResponse as? [String:Any] , let status  = responseData["Code"] as? String , status == "SUCCESS" {
                        do {
                            let responseModel  = try decoder.decode(ResponseBase<model>.self, from: response.data!)
                            onSuccess(responseModel.data!)
                        }
                        catch let error as NSError {
                            print("failed reason : \(error.localizedDescription)")
                        }

                        print(model.Type.self)
                        print(model.self)




                    }
                    else {
                        UIApplication.shared.gettopMostViewController()?.presentAlerterror(title: "Erorr", message: "Service not Avilabel" ,okclick: nil)
                    }
                case .failure(let error) :
                    UIApplication.shared.gettopMostViewController()?.presentAlerterror(title: "Erorr", message: error.localizedDescription, okclick: nil)
                }
            }
        }
        else {
            (UIApplication.shared.delegate as! AppDelegate).hideProgrssVoew()
            UIApplication.shared.gettopMostViewController()?.presentAlerterror(title: "Error", message: "connnection not avilabel", okclick: nil)
        }
    }

}

以下是调用 API 的代码。

BaseApiClient.default.fetch(request: APIRouter.GetSocialWarmerType) { (response: [SocialWarmer]) in
    print(response)
}

但如果数据是单个对象,则此模型和 API 方法将不起作用。 我想要实现的是创建单个模型并在 API 方法中进行适当的更改,以解析对象数组和单个对象。

【问题讨论】:

  • 无关,那些init(from:) 方法在您的Codable 类型中是不必要的。
  • 我还建议只使用 Alamofire response 而不是 responseJSON。如果你要再次解析它,那么让 Alamofire 解析你的 JSON 是没有意义的。
  • 您说Code 将“指示API 是成功还是失败(1 或0)”。但是您随后将其定义为String?。似乎它是1 还是0,即应该是Int。我也不清楚ResponseBase 的所有三种类型都是可选的概念吗?你不能保证至少Code 总是在那里吗?
  • 最后,我建议泛型只使用Decodable(不是Codable)的约束。在实践中,这可能不是什么大问题,但ResponseBase 并不真正关心响应中的类型是否可以同时编码和解码。它只关心它是否可以被解码。

标签: generics codable


【解决方案1】:

最后我找到了一个解决方法。我创建了两个基类,一个用于对象数组,一个用于单个对象。

struct ResponseBaseArray<T:Codable> : Codable {

    let code : String?
    let data : [T]?
    let message : String?

    enum CodingKeys: String, CodingKey {
        case code = "Code"
        case data = "Data"
        case message = "Message"
    }

    init(from decoder: Decoder) throws {
        let values = try decoder.container(keyedBy: CodingKeys.self)
        code = try values.decodeIfPresent(String.self, forKey: .code)
        data = try values.decodeIfPresent([T].self, forKey: .data)
        message = try values.decodeIfPresent(String.self, forKey: .message)
    }
}


struct ResponseBaseObject<T:Codable> : Codable {

    let code : String?
    let data : T?
    let message : String?

    enum CodingKeys: String, CodingKey {
        case code = "Code"
        case data = "Data"
        case message = "Message"
    }

    init(from decoder: Decoder) throws {
        let values = try decoder.container(keyedBy: CodingKeys.self)
        code = try values.decodeIfPresent(String.self, forKey: .code)
        data = try values.decodeIfPresent(T.self, forKey: .data)
        message = try values.decodeIfPresent(String.self, forKey: .message)
    }
}

以下是 Api 方法的代码。

class BaseApiClient {

    static let `default`  = BaseApiClient()

    private init() {

    }

    func fetch<model:Codable>(request:APIRouter , decoder: JSONDecoder = JSONDecoder() ,onSuccess: @escaping (model) -> Void) {

        if Connectivity.isReachable {
            (UIApplication.shared.delegate as! AppDelegate).addProgressView()
            Alamofire.request(request).responseJSON { (response) in
                switch response.result {
                case .success( let apiResponse) :

                    DispatchQueue.main.async {
                        (UIApplication.shared.delegate as! AppDelegate).hideProgrssVoew()
                    }
                    if let responseData = apiResponse as? [String:Any] , let status  = responseData["Code"] as? String , status == "SUCCESS" {
                        do {
                            let responseModel  = try decoder.decode(model.self, from: response.data!)
                            onSuccess(responseModel)
                        }
                        catch let error as NSError {
                            print("failed reason : \(error.localizedDescription)")
                        }
                    }
                    else {
                        UIApplication.shared.gettopMostViewController()?.presentAlerterror(title: "Erorr", message: "Service not Avilabel" ,okclick: nil)
                    }
                case .failure(let error) :
                    UIApplication.shared.gettopMostViewController()?.presentAlerterror(title: "Erorr", message: error.localizedDescription, okclick: nil)
                }
            }
        }
        else {
            (UIApplication.shared.delegate as! AppDelegate).hideProgrssVoew()
            UIApplication.shared.gettopMostViewController()?.presentAlerterror(title: "Error", message: "connnection not avilabel", okclick: nil)
        }
    }

}

以下是调用 API 的代码。

BaseApiClient.default.fetch(request: APIRouter.GetSocialWarmerType) { (rsult:ResponseBaseArray<[SocialWarmer]>) in
            print(rsult.data)
        }

如果您的 Api 返回单个对象而不是使用 ResponseBaseObject。

BaseApiClient.default.fetch(request: APIRouter.GetSocialWarmerType) { (rsult:ResponseBaseObject<SocialWarmer>) in
            print(rsult.data)
        }

但只有当您已经知道您的 Api 将返回单个对象或对象数组时,此解决方案仍然有效。

【讨论】:

  • "但是这个解决方案只有在你已经知道你的 Api 将返回单个对象或对象数组时才有效”......任何给定的端点应该始终是一个数组或一个实例。如果你有内部 API 会根据是否找到一个结果或多个结果来更改响应的结构,那么您应该修复它而不是围绕糟糕的 Web 服务设计编写客户端。注意,通用 ResponseBase 仍然应该虽然能够处理这两种类型,但在调用点,您应该知道响应的精确结构。
【解决方案2】:

您的响应对象应该只使用T,而不是[T]

struct ResponseObject<T: Decodable>: Decodable {
    let code: String
    let data: T?
    let message: String?

    enum CodingKeys: String, CodingKey {
        case code = "Code"
        case data = "Data"
        case message = "Message"
    }
}

然后,当您指定泛型的类型时,您可以指定它是单个实例还是数组。

例如,当你解码单个实例时,它是:

let responseObject = try JSONDecoder().decode(ResponseObject<Foo>.self, from: data)
let foo = responseObject.data

但是对于一个数组,它是:

let responseObject = try JSONDecoder().decode(ResponseObject<[Foo]>.self, from: data)
let array = responseObject.data

这是一个示例游乐场:

struct ResponseObject<T: Decodable>: Decodable {
    let code: String?
    let data: T
    let message: String?

    enum CodingKeys: String, CodingKey {
        case code = "Code"
        case data = "Data"
        case message = "Message"
    }
}

struct Foo: Codable {
    let value: Int
}

do {
    let data = """
        {
            "Code": "some code",
            "Message": "some message",
            "Data": {
                "value": 42
            }
        }
        """.data(using: .utf8)!

    let responseObject = try JSONDecoder().decode(ResponseObject<Foo>.self, from: data)
    let foo = responseObject.data
    print(foo)
} catch {
    print(error)
}

do {
    let data = """
        {
            "Code": "some code",
            "Message": "some message",
            "Data": [
                {
                    "value": 1
                }, {
                    "value": 2
                }, {
                    "value": 3
                }
            ]
        }
        """.data(using: .utf8)!

    let responseObject = try JSONDecoder().decode(ResponseObject<[Foo]>.self, from: data)
    let array = responseObject.data
    print(array)
} catch {
    print(error)
}

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2023-04-07
    • 2020-01-14
    • 2021-09-05
    • 2021-03-19
    • 1970-01-01
    • 2020-05-30
    • 2018-12-29
    • 2019-07-16
    相关资源
    最近更新 更多