8
{
"responseBody": {
    "table": {
        "data": [
            [
                "Forth Record",
                null,
                0,
                "2018-08-23T18:30:01.000+0000",
                0,
                0,
                "HCL",
                "b74d10ef4fe246948cd036071787ff25"
            ],
            [
                "Third Record",
                "Testing custom object record 3",
                348,
                "2018-08-22T18:30:01.000+0000",
                36.45,
                4545.45,
                "HCL",
                "139fdba94bb143849fef220f105d66d0"
            ],
            [
                "Second Record",
                "Testing custom object record 2",
                56,
                "2018-08-22T18:30:01.000+0000",
                6577.67,
                567.67,
                "HAL",
                "606a06c93ea2473fb832e5daafa02df9"
            ],
            [
                "First Record",
                "Testing custom object record",
                75,
                "2018-08-22T18:30:01.000+0000",
                47.54,
                67.63,
                "HBL",
                "29c4125f3fa947b9b252318305e986c7"
            ]
        ]
    }
}
}

I want to parse above JSON using swift 4 Codable. Please see my objects hierarchy below

//ViewRecordResponse.swift
import Foundation
struct ViewRecordResponse : Codable {
    let responseBody : ViewRecord?

    enum CodingKeys: String, CodingKey {
        case responseBody = "responseBody"
    }

    init(from decoder: Decoder) throws {
        let values = try decoder.container(keyedBy: CodingKeys.self)
        responseBody = try values.decodeIfPresent(ViewRecord.self, forKey: .responseBody)
    }
}

//ViewRecord.swift
import Foundation
struct ViewRecord : Codable {
    let table : Table?

    enum CodingKeys: String, CodingKey {
        case table = "table"
    }

    init(from decoder: Decoder) throws {
        let values = try decoder.container(keyedBy: CodingKeys.self)
        table = try values.decodeIfPresent(Table.self, forKey: .table)
    }
}

//Table.swift
import Foundation
struct Table : Codable {
    let data : [[String?]]?

    enum CodingKeys: String, CodingKey {
        case data = "data"
    }

    init(from decoder: Decoder) throws {
        let values = try decoder.container(keyedBy: CodingKeys.self)
        data = try values.decodeIfPresent([[String?]].self, forKey: .data)
    }
}

but when I try to decode the JSON using Codeable Mapping I got an error saying

The data couldn't be read because it is missing.

The data couldn’t be read because it isn’t in the correct format.

code for decode to JSON object

do {
    let jsonDecoder = JSONDecoder()
    let response = try jsonDecoder.decode(ViewRecordResponse.self, from: data)
} catch let error {
    print(error.localizedDescription)
}

Edit 1 - My Data value

Printing description of data:
▿ 557 bytes
  - count : 557
▿ pointer : 0x0000000104a23005
  - pointerValue : 4372705285

Edit 2 - data objects not follow any specific pattern issue

"data": [
            [
                456,
                31.04,
                10000,
                "Dummy Data",
                "text area dummy",
                "2018-08-27T18:30:01.000+0000",
                "UE",
                "4e67d5c02b0147b1bcfc00f459c0c612"
            ],
iamVishal16
  • 1,780
  • 18
  • 40

1 Answers1

10

The main issue is that the nested array in data is not [[String?]], there are also Int and Double values. That's most likely the cause of the error.

My suggestion is to use the (rather underestimated) unkeyedContainer to decode the inner array into a struct. decodeIfPresent handles the null value.

Your structs can be simplyfied, the coding keys and initializers can be omitted

struct ViewRecordResponse : Codable {
    let responseBody : ViewRecord
}

struct ViewRecord : Codable {
    let table : Table
}

struct Table : Codable {
    let data : [Record]
}

struct Record : Codable {
    let name : String
    let description : String?
    let index : Int
    let date : String
    let double1 : Double
    let double2 : Double
    let abbrev : String
    let sha : String

    init(from decoder: Decoder) throws {
        var arrayContrainer = try decoder.unkeyedContainer()
        name = try arrayContrainer.decode(String.self)
        description = try arrayContrainer.decodeIfPresent(String.self)
        index = try arrayContrainer.decode(Int.self)
        date = try arrayContrainer.decode(String.self)
        double1 = try arrayContrainer.decode(Double.self)
        double2 = try arrayContrainer.decode(Double.self)
        abbrev = try arrayContrainer.decode(String.self)
        sha = try arrayContrainer.decode(String.self)
    }
}

I discourage from putting each struct in a separate file as they belong together.

vadian
  • 274,689
  • 30
  • 353
  • 361
  • One more problem the record does not follow any specific format it can be int, int, double, double and string and your solution only working when JSON record data comes in a specific format? – iamVishal16 Aug 27 '18 at 05:42
  • The more flexibility the more code you have to write. If the record doesn't have a specific format there is no benefit from using `Codable`. On the other hand it's very very bad habit to send such inconsistent JSON data in an array. – vadian Aug 27 '18 at 06:16
  • I know that sir but some time we need to deal with this situations because there is no point to argue with teams if you are surrounded by some unhealthy seniors. The sad part of corporate. – iamVishal16 Aug 27 '18 at 06:19
  • 1
    Then I suggest to drop `Codable` and use conventional `JSONSerialization` and check the types. You can only take advantage of the magic of `Codable` if you get consistent data. – vadian Aug 27 '18 at 06:23
  • oh... It will be beneficial if codable allows properties accept a string in respective to it's data type. – iamVishal16 Aug 27 '18 at 06:29
  • That requires key-value observing which could be expensive regarding performance and defeats the generic setting of `Codable` – vadian Aug 27 '18 at 06:36
  • I am using JSONSerialization for now and it's just working fine. Thanks for your help, sir. – iamVishal16 Aug 27 '18 at 06:41