Swift 4 Codable Array's
Asked Answered
Z

5

18

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.

Zoophobia answered 29/8, 2017 at 0:7 Comment(3)
Is your question how to transform the JSON you provided into an Array of Person (example entity) ? Or an Array of heterogeneous objects ?Call
Well I know if I have {"firstname": "Tom", "lastname": "Smith", "age": 31} and a person class I could convert the JSON into a person object in Swift using the codable. But I'm not sure how I can do it if I have that array of JSON that I get from Alamofire.Zoophobia
I'm not really familiar with Alamofire (or with this library), but there's github.com/Otbivnoe/CodableAlamofireFosdick
C
19

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)

Call answered 29/8, 2017 at 0:25 Comment(2)
Very detailed answer. Thank you very much. Don't quite have time to look through it all now but will get to it at some point. Thanks so much!!!Zoophobia
Answer updated with Alamofire 5 code (no need for extensions)Call
V
1

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] = []
}
Verbalize answered 19/9, 2023 at 8:18 Comment(0)
A
0

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)
        }
    }
Arsenical answered 18/11, 2017 at 6:14 Comment(3)
What is unique about your answer compared to the answer that is accepted?Zoophobia
you don't need extension or anything. I mean it is very easy just to use the data response, from existing function. Simple alternative for people who already using Alamofire for long. Right?Arsenical
Yeah. Well after doing more research there are better ways to handle this. Adding responseCodable I feel like just adds complexity. I would also argue that my main question was about Swift 4 Codable Arrays. Not Alamofire. Although I mentioned Alamofire that was not my main question.Zoophobia
M
0

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.

Margaretamargarete answered 16/10, 2019 at 14:10 Comment(0)
A
0

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)
}
Abuttals answered 31/1, 2020 at 9:56 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.