Как разобрать общий ответ с помощью Codable - PullRequest
0 голосов
/ 02 ноября 2018

Каждый 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, который может анализировать как массив объектов, так и один объект.

Ответы [ 2 ]

0 голосов
/ 24 июня 2019

Ваш объект ответа должен просто использовать 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)
}
0 голосов
/ 06 ноября 2018

Наконец, я нашел один Workaround.i создал два базовых класса, один для массива объекта и один для одного объекта.

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 вернет один объект или массив объектов.

...