2

I am currently working on a project where I am calling a webservice that returns me a JSON that I parse using Codable, like so :

My Struct:

struct User: Codable {
    var name: String
    var age: Int
}

API response :

{ "name": "Romiro", "age": 27 }

Decoding code :

let decoded = try! JSONDecoder().decode(User.self, from: data)

We decided to extend the User infos by adding new fields like so :

struct User: Codable {
    var name: String
    var age: Int
    var detail: Detail
}

struct Detail: Codable {
    var id: Int 
    var dob: Date 
}

However the backend is not developed yet, so the API response is still

{ "name": "Romiro", "age": 27 }

Is there a proper way to mock up only the var detail: Detail part, by loading it from a detail-mock.json file in project resources that matches the structure of Detail, but in the mean time keeping the API call for the pre-existing User part ?

By doing this, I would be able to keep all the logic of calling the endpoint, and shunting the only part that is under development, still by calling

let decoded = try! JSONDecoder().decode(User.self, from: data)

Furthermore, is there a way to do so without altering the json response from the API? I do not want to manually append the detail part to je json response.

Note: Obviously, the User struct is an example, on my project this is a much more complex struct

Olympiloutre
  • 2,268
  • 3
  • 28
  • 38
  • Do you want to add the value of detail manually after User object os created? – PGDev Jun 27 '19 at 05:31
  • you can do something like this var detail: Detail?. The keys which can be nil or keys which are likely to be not present in json mark them as optional – Vikky Jun 27 '19 at 05:31
  • you mean `var detail: Detail?`, then decode the `Detail` from the file and setting it to my object ? – Olympiloutre Jun 27 '19 at 05:33

2 Answers2

3

You can implement custom decoding on User, like this:

struct User: Codable {
    var name: String
    var age: Int
    var detail: Detail

    enum CodingKeys: CodingKey {
        case name, age, detail
    }

    init(from decoder: Decoder) throws {
        let container = try decoder.container(keyedBy: CodingKeys.self)
        name = try container.decode(String.self, forKey: .name)
        age = try container.decode(Int.self, forKey: .age)
        if let detail = try container.decodeIfPresent(Detail.self, forKey: .detail) {
            self.detail = detail
        } else {
            let data = try Data(contentsOf: Bundle.main.url(forResource: "mockupDetail", withExtension: "json")!)
            self.detail = try JSONDecoder().decode(Detail.self, from: data)
        }
    }
}

Notice the if statement in init. That is where I decide whether to read the detail from the actual json, or the mocked json.

This way, you don't need to make detail optional, but you would need to manually decode the other properties.

Sweeper
  • 213,210
  • 22
  • 193
  • 313
1

First of all set detail as Detail? type, i.e.

struct User: Codable {
    var name: String
    var age: Int
    var detail: Detail?
}

You can create 2 separate objects for User and Detail, and the set the detail object as user.detail, i.e.

do {
    var user = try JSONDecoder().decode(User.self, from: userData)
    let detailData = Data() //replace this with the data obtained from Detail api
    let detail = try JSONDecoder().decode(Detail.self, from: detailData)
    user.detail = detail
} catch  {
    print(error)
}
PGDev
  • 23,751
  • 6
  • 34
  • 88