Using Swift 4's Decodable protocol for nested JSON server response

Viewed 922

I am trying to implement Swift 4's new Decodable protocol and having some hard time approaching it.

This is my JSON server response:

{
  "success": true,
  "errorCode": 0,
  "message": "Succcess",
  "data": {
    "name": "Logan Howlett",
    "nickname": "The Wolverine",
    "image": "http://heroapps.co.il/employee-tests/ios/logan.jpg",
    "dateOfBirth": 1880,
    "powers": [
      "Adamantium Bones",
      "Self-Healing",
      "Adamantium Claws"
    ],
    "actorName": "Hugh Jackman",
    "movies": [
      {
        "name": "X-Men Origins: Wolverine",
        "year": 2009
      },
      {
        "name": "The Wolverine",
        "year": 2013
      },
      {
        "name": "X-Men: Days of Future Past",
        "year": 2014
      },
      {
        "name": "Logan",
        "year": 2017
      },
    ]
  }
}

What would be the best approach to Decode the data part of the response? Also, what happens if the data is suddenly an array instead of an object, how can I support both data types?

Thanks a lot :)

1 Answers

First you can create an extension as a helper:

extension Data {
    func decode <Generic: Codable> () -> Generic? {
        let decoder = JSONDecoder()
        let object = try? decoder.decode(Generic.self, from: self)
        return object
    }
}


extension Dictionary {
    func decode <Generic: Codable> () -> Generic? {
        let data = try? JSONSerialization.data(withJSONObject: self,
                                               options: JSONSerialization.WritingOptions.prettyPrinted)
        guard let d = data else {
            return nil
        }
        return d.decode()
    }
}

Then you can create a protocol to help you to build your objects:

protocol Encode: Codable {
    init(with dictionary: [String: Any])
    init(with data: Data)
}

with a default implementation:

extension Encode {
    init(with data: Data) {
        let object: Self? = data.decode()
        guard let obj = object else {
            fatalError("fail to init object with \(data)")
        }
        self = obj
    }

    init(with dictionary: [String: Any]) {
        let object: Self? = dictionary.decode()
        guard let obj = object else {
            fatalError("fail to init object with \(dictionary)")
        }
        self = obj
    }

Then create your objects as struct which conform to y Codable protocol. It will look like:

struct User: Codable {
    var name: String?
    var nickname: String?
    ...
    // If needed declare CodingKey here
    // enum CodingKeys: String, CodingKey {
    //     case date = "dateOfBirth"
    //     ...
    // }
}

struct Movies: Codable {
    var name: String?
    var year: Int?
}

Now you need to extract your dictionary of data from your response and to apply your new init methods:

if let dic = json["data"] as? [String: Any] {
    let user: User = User(with: dic)
    // Do stuff here
}

If data become suddenly an Array, you will have to handle it differently (as an array of User in this example)

Related