swiftDevStart
swiftDevStart

Reputation: 101

How to Decode Nested dictionary with nested arrays using Swift Decodable property?

The actual JSON,that I need to parse in swift4 is,

{
    "class": {
        "semester1": [
            {
                "name": "Kal"
            },
            {
                "name": "Jack"
            },
            {
                "name": "Igor"
            }
        ],
        "subjects": [
            "English",
            "Maths"
        ]
    },
    "location": {
        "Dept": [
            "EnglishDept",
            ],
        "BlockNo": 1000
    },
    "statusTracker": {
        "googleFormsURL": "beacon.datazoom.io",
        "totalCount": 3000
    }
}

The code that I'd tried but failed to execute is,

struct Class: Decodable {
    let semester: [internalComponents]
    let location: [location]
    let statusTracker: [statusTracker]
    enum CodingKeys: String, CodingKey {
        case semester = "semester1"
        case location = "location"
        case statusTracker = "statusTracker"
    }
}
struct location: Decodable {
    let Dept: [typesSubIn]
    let BlockNo: Int
}
struct statusTracker: Decodable {
    let googleFormsURL: URL
    let totalCount: Int
}


struct internalComponents: Decodable {
    let semester1: [semsIn]
    let subjects: [subjectsIn]
}
struct semsIn: Decodable {
    let nameIn: String
}
struct subjectsIn: Decodable {
    let subjects: String
}
struct Dept: Decodable {
    let Depts: String
}

I know it's completely wrong can someone give the actual format? I'm actually confused with the format for "subjects".It's not compiling as a whole too.

Upvotes: 1

Views: 1422

Answers (4)

vadian
vadian

Reputation: 285260

There are many issues.

You are making a common mistake by ignoring the root object partially.

Please take a look at the JSON: On the top level there are 3 keys class, location and statusTracker. The values for all 3 keys are dictionaries, there are no arrays.

Since class (lowercase) is a reserved word, I'm using components. By the way please conform to the naming convention that struct names start with a capital letter.

struct Root : Decodable {
    let components : Class
    let location: Location
    let statusTracker: StatusTracker

    enum CodingKeys: String, CodingKey { case components = "class", location, statusTracker }
}

There are many other problems. Here a consolidated version of the other structs

struct Class: Decodable {
    let semester1: [SemsIn]
    let subjects : [String]
}

struct Location: Decodable {
    let dept : [String]
    let blockNo : Int

    enum CodingKeys: String, CodingKey { case dept = "Dept", blockNo = "BlockNo" }
}

struct SemsIn: Decodable {
    let name: String
}

struct StatusTracker: Decodable {
    let googleFormsURL: String // URL is no benefit
    let totalCount: Int
}

Now decode Root

do {
    let result = try decoder.decode(Root.self, from: data)
} catch { print(error) }

Upvotes: 3

user9359038
user9359038

Reputation:

Another approach is to create an intermediate model that closely matches the JSON, let Swift generate the methods to decode it, and then pick off the pieces that you want in your final data model:

// snake_case to match the JSON
fileprivate struct RawServerResponse: Decodable {
    struct User: Decodable {
        var user_name: String
        var real_info: UserRealInfo
    }

    struct UserRealInfo: Decodable {
        var full_name: String
    }

    struct Review: Decodable {
        var count: Int
    }

    var id: Int
    var user: User
    var reviews_count: [Review]
}

struct ServerResponse: Decodable {
    var id: String
    var username: String
    var fullName: String
    var reviewCount: Int

    init(from decoder: Decoder) throws {
        let rawResponse = try RawServerResponse(from: decoder)

        // Now you can pick items that are important to your data model,
        // conveniently decoded into a Swift structure
        id = String(rawResponse.id)
        username = rawResponse.user.user_name
        fullName = rawResponse.user.real_info.full_name
        reviewCount = rawResponse.reviews_count.first!.count
    }
}

Upvotes: 0

Scriptable
Scriptable

Reputation: 19758

There are a few things here causing your issue.

  • You have no top level item, I added Response struct
  • Location, class and statusTracker are both at the same level, not under class.
  • In your class struct, your items are set as arrays but they aren't arrays
  • To debug these types of issues, wrap your decode in a do catch block and print out the error. it will tell you the reason it failed to parse

Try this code from my playground:

let jsonData = """
{
    "class": {
        "semester1": [{
            "name": "Kal"
        }, {
            "name": "Jack"
        }, {
            "name": "Igor"
        }],
        "subjects": [
            "English",
            "Maths"
        ]
    },
    "location": {
        "Dept": [
            "EnglishDept"
        ],
        "BlockNo": 1000
    },
    "statusTracker": {
        "googleFormsURL": "beacon.datazoom.io",
        "totalCount": 3000
    }
}
""".data(using: .utf8)!

struct Response: Decodable {
    let cls: Class
    let location: Location
    let statusTracker: statusTracker

    enum CodingKeys: String, CodingKey {
        case cls = "class"
        case location
        case statusTracker
    }
}

struct Class: Decodable {
    let semester: [SemesterStudents]
    let subjects: [String]

    enum CodingKeys: String, CodingKey {
        case semester = "semester1"
        case subjects
    }
}

struct Location: Decodable {
    let dept: [String]
    let blockNo: Int

    enum CodingKeys: String, CodingKey {
        case dept = "Dept"
        case blockNo = "BlockNo"
    }
}

struct statusTracker: Decodable {
    let googleFormsURL: URL
    let totalCount: Int
}

struct SemesterStudents: Decodable {
    let name: String
}

struct Dept: Decodable {
    let Depts: String
}

do {
    let result = try JSONDecoder().decode(Response.self, from: jsonData)
    print(result)
} catch let error {
    print(error)
}

Upvotes: 0

LakaLe_
LakaLe_

Reputation: 454

It looks like you sterilize Class object in wrong way. It should looks like:

struct Class: Decodable {
    let class: [internalComponents]
    let location: [location]
    let statusTracker: [statusTracker]
}

Upvotes: 0

Related Questions