如何使用 Codable 解析通用响应

How to parse generic response using Codable

每个 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 方法中进行适当的更改,该方法可以解析对象数组和单个对象。

我终于找到了一个 Workaround.i 创建了两个基础 class 一个用于对象数组,一个用于单个对象。

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 return 单个对象而不是使用 ResponseBaseObject。

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

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

您的响应对象应该只使用 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)
}