0

Other suggested solutions handle type of structure they are part of, While I am not able to figure out how to parse nested part of that same structure based on the value within outer structure.

I have a JSON response whose structure change based on one of the values within outer part of JSON.

For example:

{
  "reports": [
    {
      "reportType": "advance",
      "reportData": {
        "value1": "true"
      }
    },
    {
      "reportType": "simple",
      "reportData": {
        "value3": "false",
        "value": "sample"
      }
    }
  ]
}

Using Codable with string as Type for 'report' key fails to parse this json. I want this report value to be either parsed later and store it as it is or atleast parse it based on the reportType value as this have different structure for each value of reportType.

I have written code based on the the suggested solutions.

enum ReportTypes: String {
    case simple, advance
}

struct Reports: Codable {
    let reportArray = [Report]
}

struct Report: Decodable {
    let reportType: String
    let reportData: ReportTypes
    enum CodingKeys: String, CodingKey {
        case reportType, reportData
    }
    init(from decoder: Decoder) {
        let container = try decoder.container(keyedBy: CodingKeys.self)
        self.reportType = try container.decode(String.self, forKey: .reportType)

        switch ReportTypes(rawValue: self.reportType) {
            case .simple:
            ???
            case .advance:
            ???
        }
    }
    
}

Please look at the switch cases and i'm not sure what to do. I need a solution similar to do this.

Workaround: The workaround is that to mode that reportType inside the report {} structure and then follow this question How can i parse an Json array of a list of different object using Codable?

New Structure

{
  "reports": [
    {
      "reportType": "advance",
      "reportData": {
        "reportType": "advance",
        "value1": "true"
      }
    },
    {
      "reportType": "simple",
      "reportData": {
        "reportType": "simple",
        "value3": "false",
        "value": "sample"
      }
    }
  ]
}

So it worked out for me this way.

But if changing the structure is not what you can afford then this will not work.

Other possible solution I see and later Question: How to Access value of Codable Parent struct in a nested Codable struct is storing reportType in variable currentReportType from init(from decoder: Decoder) and then write another decoder for struct reportData that will handle decoding based on the value stored in var currentReportType. Write it by following the first link shared.

Dushyant Deshwal
  • 388
  • 4
  • 17
  • 1
    This question is too generic to be answered. You should provide the structs/classes implementing the Codable protocol which you want the structure to be paresed into. Then we will be able to help you with your problems. As it stands the question does not state a problem a programmer should answer. – Patru Nov 14 '22 at 07:01

1 Answers1

1

A reasonable way to decode this JSON is an enum with associated values because the type is known.

Create two structs for the objects representing the data in reportData

struct AdvancedReport: Decodable {
    let value1: String
}

struct SimpleReport: Decodable {
    let value3, value: String
}

and adopt Decodable in ReportType

enum ReportType: String, Decodable {
    case simple, advance
}

The two main structs are the struct Response which is the root object (I renamed it because there are too many occurrences of the term Report) and the mentioned enum with associated values. The key reportType in the child dictionary is not being decoded because it's redundant.

struct Response: Decodable {
    let reports : [Report]
}

enum Report: Decodable {
    
    case simple(SimpleReport)
    case advance(AdvancedReport)
    
    private enum CodingKeys: String, CodingKey {
        case reportType, reportData
    }

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

        switch reportType {
            case .simple: self = .simple(try container.decode(SimpleReport.self, forKey: .reportData))
            case .advance: self = .advance(try container.decode(AdvancedReport.self, forKey: .reportData))
        }
    }
}

After decoding the JSON you can switch on reports

for report in response.reports {
    switch report {
        case .simple(let simpleReport): print(simpleReport)
        case .advance(let advancedReport): print(advancedReport)

    }
}
vadian
  • 274,689
  • 30
  • 353
  • 361
  • How should I do it if I also want the type reportType value or lets say some other value such as "name" is there ? "name":"John" "reportType": "advance", "reportData": { "reportType": "advance", "value1": "true" } – Dushyant Deshwal Nov 19 '22 at 12:08
  • 1
    You can add a property `reportType` and omit the `let` in the decoding line. And you can add other properties and make them optional. – vadian Nov 19 '22 at 12:26