Swift 4 Codable Array's

22.8k views Asked by At

So I have an API route that returns a JSON array of objects. For example:

[
    {"firstname": "Tom", "lastname": "Smith", "age": 31},
    {"firstname": "Bob", "lastname": "Smith", "age": 28}
]

I'm trying to envision how to use the new codable feature in Swift for to convert those into two objects in a class. So if I have a person class that is codable I would want to take that response and have it give me two person objects.

I'm also using Alamofire to handle the requests.

How can I do this? So far everything I've seen related to the codable stuff only allows 1 object. And I haven't seen any integration with Alamofire or a web framework.

5

There are 5 answers

2
nathan On BEST ANSWER

Update regarding Alamofire 5: responseJSONDecodable.

struct Person: Codable {
    let firstName, lastName: String
    let age: Int

    enum CodingKeys : String, CodingKey {
        case firstName = "firstname"
        case lastName = "lastname"
        case age
    }
}

Alamofire.request(request).responseJSONDecodable { (response: DataResponse<Person>) in
    print(response)
}

Alamofire 4 won't add Codable support for now (see #2177), you can use this extension instead: https://github.com/Otbivnoe/CodableAlamofire.

let jsonData = """
[
    {"firstname": "Tom", "lastname": "Smith", "age": 31},
    {"firstname": "Bob", "lastname": "Smith", "age": 28}
]
""".data(using: .utf8)!

struct Person: Codable {
    let firstName, lastName: String
    let age: Int

    enum CodingKeys : String, CodingKey {
        case firstName = "firstname"
        case lastName = "lastname"
        case age
    }
}

let decoded = try! JSONDecoder().decode([Person].self, from: jsonData)

Sample: http://swift.sandbox.bluemix.net/#/repl/59a4b4fad129044611590820

Using CodableAlamofire:

let decoder = JSONDecoder()
Alamofire.request(url).responseDecodableObject(keyPath: nil, decoder: decoder) { (response: DataResponse<[Person]>) in
    let persons = response.result.value
    print(persons)
}

keypath corresponds to the path where the results are contained in the JSON structure. E.g:

{
    "result": {
        "persons": [
            {"firstname": "Tom", "lastname": "Smith", "age": 31},
            {"firstname": "Bob", "lastname": "Smith", "age": 28}
        ]
    }
}

keypath => results.persons

[
    {"firstname": "Tom", "lastname": "Smith", "age": 31},
    {"firstname": "Bob", "lastname": "Smith", "age": 28}
]

keypath => nil (empty keypath throws an exception)

0
Abdoelrhman On

to decode to array, you've your response in a type alias for clarity:

typealias ServiceResponseObject = [ResponseObject]

but then you'll have to confirm Array to codable:

extension Array: Decodable where Element: Decodable {}

that should make it all work.

0
Shourob Datta On

Swift 5 Using Codable

Alamofire Generic Response

PersonModel.swift (create with SwiftyJsonAccelerator)

import Foundation

class PersonModel: Codable {

  enum CodingKeys: String, CodingKey {
    case age
    case firstname
    case lastname   }

  var age: Int?   var firstname: String?   var lastname: String?

  init (age: Int?, firstname: String?, lastname: String?) {
    self.age = age
    self.firstname = firstname
    self.lastname = lastname   }

  required init(from decoder: Decoder) throws {
    let container = try decoder.container(keyedBy: CodingKeys.self)
    age = try container.decodeIfPresent(Int.self, forKey: .age)
    firstname = try container.decodeIfPresent(String.self, forKey: .firstname)
    lastname = try container.decodeIfPresent(String.self, forKey: .lastname)   }

}

Generic Get Response

func genericGET<T: Decodable>(urlString: String, completion: @escaping (T?) -> ()) {

Alamofire.request(urlString)
    .responseJSON { response in
        // check for errors
        switch response.result {
        case .success(_):

            do {
                let obj = try JSONDecoder().decode(T.self, from: response.data!)
                completion(obj)
            } catch let jsonErr {
                print("Failed to decode json:", jsonErr)
            }

            break
        case .failure(_):


            completion(nil)

            break
        }
}

}

Call this method

genericGET(urlString: "YOUR_URL") { (persons: [PersonModel]?) in
    print(persons?[0].firstname)
}
0
Andrew_STOP_RU_WAR_IN_UA On

Inherit object in array from "Сodable"

struct CustomObject: Codable {
    let var1: String
    let var2: Int
}

As result:

now this array will be Codable:

var objects: [CustomObject] = []

the same AnotherCustomObject will be codable:

struct AnotherCustomObject: Codable {
    var var1: String
    var var2: String
    var objects: [CustomObject] = []
}
3
abbawssdsad On

I managed to serialize data response to codable objects.

As all you may have been familiar with converting json object [String: String] for example. That json object need to be converted to Data by using json.data(using: .utf8)!.

With Alamofire, it is easy to get that data (or at least this kind of data worked for me, already compatible with .utf8 thing), I can just use this already available function

func responseData(queue: DispatchQueue?, completionHandler: @escaping (DataResponse<Data>) -> Void) -> Self

Then just use that data as input for the Decoder in the completionHandler

let objek = try JSONDecoder().decode(T.self, from: data)

You can also make this to some generic serialization function, with a little tweak, from the documentation

Generic Response Object Serialization

to this modification

func responseCodable<T: Codable>(
    queue: DispatchQueue? = nil,
    completionHandler: @escaping (DataResponse<T>) -> Void)
    -> Self
{
    let responseSerializer = DataResponseSerializer<T> { request, response, data, error in
        guard error == nil else { return .failure(BackendError.network(error: error!)) }

        guard let data = data else {
            return .failure(BackendError.objectSerialization(reason: "data is not valid"))
        }


        do{
            let objek = try JSONDecoder().decode(T.self, from: data!)
            return .success(objek)
        } catch let e {
            return .failure(BackendError.codableSerialization(error: e))
        }

    }

    return response(queue: queue, responseSerializer: responseSerializer, completionHandler: completionHandler)
}

Sample struct

struct Fids: Codable {

   var Status: Status?
   var Airport: Airport?
   var Record: [FidsRecord]
}

Use the function this way

    Alamofire.request("http://whatever.com/zzz").responseCodable { (response: DataResponse<Fids>) in
        switch response.result{
        case .success(let value):
            print(value.Airport)
        // MARK: do whatever you want
        case .failure(let error):
            print(error)
            self.showToast(message: error.localizedDescription)
        }
    }