Search code examples
arraysswiftdecodableencodable

Decoding an Array in a Swift model (Decodable)


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?


Solution

  • 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
        }
    }