-1

I am using below code to parse json from API


struct ResourceInfo: Decodable {
    let id: String
    let type: String
    // let department: String. -> Unable to get the value for department
}

struct CustomerInfo: Decodable {
    let name: String
    let country: String
    let resources: [ResourceInfo]

    enum CodingKeys: CodingKey {
        case name
        case country
        case resources
    }

    init(from decoder: Decoder) throws {
        let container = try decoder.container(keyedBy: CodingKeys.self)
        self.name = try container.decode(String.self, forKey: .name)
        self.country = try container.decode(String.self, forKey: .country)

        let resourcesDict = try container.decode([String: ResourceInfo].self, forKey: .resources)
        //print(resourcesDict.map { $0.key })
        self.resources = resourcesDict.map { $0.value }
    }
}

static func parseJson() {
        let json = """
        {
          "name": "John",
          "country": "USA",
          "resources": {
            "electronics": {
              "id": "101",
              "type": "PC"
            },
            "mechanical": {
              "id": "201",
              "type": "CAR"
            },
            "science": {
              "id": "301",
              "type": "CHEM"
            }
          }
        }
        """
        let result = try? JSONDecoder().decode(CustomerInfo.self, from: json.data(using: .utf8)!)
        dump(result)
    }

Output:

Optional(JsonSample.CustomerInfo(name: "John", country: "USA", resources: [JsonSample.ResourceInfo(id: "201", type: "CAR"), JsonSample.ResourceInfo(id: "301", type: "CHEM"), JsonSample.ResourceInfo(id: "101", type: "PC")]))
  ▿ some: JsonSample.CustomerInfo
    - name: "John"
    - country: "USA"
    ▿ resources: 3 elements
      ▿ JsonSample.ResourceInfo
        - id: "201"
        - type: "CAR"
      ▿ JsonSample.ResourceInfo
        - id: "301"
        - type: "CHEM"
      ▿ JsonSample.ResourceInfo
        - id: "101"
        - type: "PC"

Could someone help me get the value department like electronics, mechanical & science? Thank you

iOSAppDev
  • 2,755
  • 4
  • 39
  • 77

3 Answers3

1

You throw away the information in the line resourcesDict.map { $0.value } because you ignore the keys.

My suggestion is to add a temporary helper struct, decode that and map the dictionary to ResourceInfo with both key and value

struct Temp : Decodable {
    let id, type: String
}

struct ResourceInfo {
    let id: String
    let type: String
    let department: String
}

struct CustomerInfo: Decodable {
    let name: String
    let country: String
    let resources: [ResourceInfo]

    enum CodingKeys: CodingKey { case name, country, resources }

    init(from decoder: Decoder) throws {
        let container = try decoder.container(keyedBy: CodingKeys.self)
        self.name = try container.decode(String.self, forKey: .name)
        self.country = try container.decode(String.self, forKey: .country)

        let resourcesDict = try container.decode([String: Temp].self, forKey: .resources)
        self.resources = resourcesDict.map {
            ResourceInfo(id: $1.id, type: $1.type, department: $0)
        }
    }
}
vadian
  • 274,689
  • 30
  • 353
  • 361
0

You could try this approach, where you decode ResourceInfo initially with just its CodingKeys, then remap the results with the dictionary keys as the department, as shown in the SwiftUI example code:

struct ContentView: View {
    @State var customerInfo: CustomerInfo?
    
    var body: some View {
        VStack {
            if let customer = customerInfo {
                Text(customer.name)
                List(customer.resources) { resourse in
                    Text(resourse.department)
                }
            }
        }
        .onAppear {
            parseJson()
        }
    }
    
    func parseJson() {
        let json = """
            {
              "name": "John",
              "country": "USA",
              "resources": {
                "electronics": {
                  "id": "101",
                  "type": "PC"
                },
                "mechanical": {
                  "id": "201",
                  "type": "CAR"
                },
                "science": {
                  "id": "301",
                  "type": "CHEM"
                }
              }
            }
            """

        do {
            let result = try JSONDecoder().decode(CustomerInfo.self, from: json.data(using: .utf8)!)
            print("\n---> result: \(result)\n")
            customerInfo = result
        } catch {
            print(error)
        }
    }
    
}

struct ResourceInfo: Identifiable, Decodable { // <-- here
    let id: String
    let type: String
    var department: String = "" // <-- here
    
    enum CodingKeys: CodingKey {  // <-- here
        case id
        case type
    }
}

struct CustomerInfo: Decodable {
    let name: String
    let country: String
    let resources: [ResourceInfo]

    enum CodingKeys: CodingKey {
        case name
        case country
        case resources
    }

    init(from decoder: Decoder) throws {
        let container = try decoder.container(keyedBy: CodingKeys.self)
        self.name = try container.decode(String.self, forKey: .name)
        self.country = try container.decode(String.self, forKey: .country)

        let resourcesDict = try container.decode([String: ResourceInfo].self, forKey: .resources)

        // -- here
        resources = resourcesDict.map { ResourceInfo(id: $0.value.id, type: $0.value.type, department: $0.key) }
    }
}
0

The difficulty lies in the fact that the data structure you want doesn’t map directly with the hierarchy in the JSON. The department name is a key to a dictionary containing the id and type values. If you want to collapse that information into a single struct then this is one solution. There may be smarter ways to do this. I just added a separate structure for decoding the id and type:


struct ResourceInfoDetails: Decodable {
    let id: String
    let type: String
}

struct ResourceInfo: Decodable {
    let department: String
    let id: String
    let type: String
}

struct CustomerInfo: Decodable {
    let name: String
    let country: String
    let resources: [ResourceInfo]

    enum CodingKeys: CodingKey {
        case name
        case country
        case resources
    }

    init(from decoder: Decoder) throws {
        let container = try decoder.container(keyedBy: CodingKeys.self)
        self.name = try container.decode(String.self, forKey: .name)
        self.country = try container.decode(String.self, forKey: .country)

        let resourcesDict = try container.decode([String: ResourceInfoDetails].self, forKey: .resources)
        self.resources = resourcesDict.map { ResourceInfo(department: $0, id: $1.id, type: $1.type) }
    }
}
Geoff Hackworth
  • 2,673
  • 1
  • 16
  • 16