gnarlybracket
gnarlybracket

Reputation: 1720

flat JSON to struct with nested properties

All questions I have found so far on the searches I've done is about decoding nested JSON to some struct with nested properties. I want to do the opposite: decode flat JSON to a struct with nested properties.

Here's example JSON:

{
    "id":"ABC123",
    "cell":"test",
    "qty":24
}

which I'd like to decode to this struct:

struct InventoryItem {
    let id: String
    let mfgInfo: MfgInfo
}

extension InventoryItem {
    struct MfgInfo {
        let cell: String
        let qty: Int
    }
}

I have tried adding CodingKeys for each struct:

struct InventoryItem: Decodable {
    let id: String
    let mfgInfo: MfgInfo

    enum CodingKeys: String, CodingKey {
        case id, mfgInfo
    }
}


struct MfgInfo: Decodable {
    let cell: String
    let qty: Int

    enum CodingKeys: String, CodingKey {
        case cell, qty
    }
}

But this doesn't work. I get this error:

No value associated with key CodingKeys(stringValue: \"mfgInfo\", intValue: nil) (\"mfgInfo\"), converted to mfg_info.

How can I make this work without a custom initializer? Or do I need to write a custom init(with: Decoder) initializer?

Upvotes: 0

Views: 252

Answers (2)

gnarlybracket
gnarlybracket

Reputation: 1720

I wasn't able to do this without implementing the init(from: Decoder) initializer, but it was easier than I thought. For nested structs that just have primitive types, you can just use the automatically synthesized init(from: Decoder) method.

Final struct setup:

struct InventoryItem: Decodable {
    let id: String
    let mfgInfo: MfgInfo

    enum CodingKeys: String, CodingKey {
        case id
    }

    struct MfgInfo: Decodable {
        let cell: String
        let qty: Int

        enum CodingKeys: String, CodingKey {
            case cell, qty
        }
    }

    init(from decoder: Decoder) throws {
        let container = try decoder.container(keyedBy: CodingKeys.self)

        id = try container.decode(String.self, forKey: .id)
        mfgInfo = try MfgInfo(from: decoder)
    }
}

Upvotes: 1

Gereon
Gereon

Reputation: 17844

This is only possible with a custom init(with: Decoder) implementation, or by having e.g. a FlatInventoryItem type that conforms to Decodable and then providing conversion methods between that and your desired InventoryItem type.

Upvotes: 0

Related Questions