2

I am retrieving a JSON from an API and I wanted to make a Model for each endpoints I use.

All the endpoints use this format:

{
  "id": "xxxxxx",
  "result": {…},
  "error": null
}

The keys are:

  • id is always a string
  • error can be null or an object with keys in it
  • result can be either null; an object or an array.

The problem I am encountering is that on one of the endpoints the results are arrays of array:

{
  "id": "xxxxxx",
  "result": [
      [
          "client_id",
          "name",
          50,
          "status"
      ]
  ],
  "error": null
}

As you can see, I have arrays of array where the values can be either a String or Int.

How do you decode this using Decodable protocol and then using those decoded values as String or Int depending on their origin values?

4
  • What object can "error" be? Commented Dec 23, 2018 at 20:43
  • 1
    Check this answer: stackoverflow.com/a/48388443/8447312 Commented Dec 23, 2018 at 20:45
  • 1
    Show us the Decodable struct or class that you are using. We can't fix your code if you don't show your code. Commented Dec 23, 2018 at 20:53
  • The simplest solution is to use two separate models. It's normal that different endpoints return different data models. Commented Dec 23, 2018 at 21:28

1 Answer 1

5
import Foundation

let string =  """
{
    "id": "xxxxxx",
    "result": [
        [
            "client_id",
            "name",
            50,
            "status"
        ]
    ],
    "error": null
}
"""

struct Container: Codable {
    let id: String
    let result: [[Result]]
    let error: String?
}

enum Result: Codable {
    case integer(Int)
    case string(String)

    init(from decoder: Decoder) throws {
        let container = try decoder.singleValueContainer()
        if let x = try? container.decode(Int.self) {
            self = .integer(x)
            return
        }
        if let x = try? container.decode(String.self) {
            self = .string(x)
            return
        }
        throw DecodingError.typeMismatch(Result.self, DecodingError.Context(codingPath: decoder.codingPath, debugDescription: "Wrong type for Result"))
    }

    func encode(to encoder: Encoder) throws {
        var container = encoder.singleValueContainer()
        try container.encode(self)
    }
}

let jsonData = string.data(using: .utf8)!
let container = try? JSONDecoder().decode(Container.self, from: jsonData)

print(container)

improved @ArinDavoodian's answer.

To read the data:

container?.result.first?.forEach { object in
    switch object {
    case let .integer(intValue):
        print(intValue)
        break
    case let .string(stringValue):
        print(stringValue)
        break
    }
}

a simple solution:

let yourInsideArray = container?.result.first!
for index in 0..<yourInsideArray.count {
let yourObjectInsideThisArray = yourInsideArray[i]
//do some
 switch yourObjectInsideThisArray {
    case let .integer(intValue):
        print(intValue)
        break
    case let .string(stringValue):
        print(stringValue)
        break
    }
}
Sign up to request clarification or add additional context in comments.

11 Comments

why not simply func encode(to encoder: Encoder) throws { var container = encoder.singleValueContainer() try container.encode(self) }
Funny think playground works but shows (12716 times) instead of 4
you can simplify your decoder also init(from decoder: Decoder) throws { let container = try decoder.singleValueContainer() try self = (try? .integer(container.decode(Int.self))) ?? .string(container.decode(String.self)) }
In my playground just 4 times
I might need a new playground file kkk to many code on it probablyt
|

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.