iamVishal16
iamVishal16

Reputation: 1780

Swift Codable not working as expected?

{
"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"
            ],

Upvotes: 8

Views: 4945

Answers (1)

vadian
vadian

Reputation: 285039

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.

Upvotes: 10

Related Questions