0

How can I implement a solution where I could set the type of a parameter on the go while parsing a JSON by analyzing the parameter at the same level as the other parameter?

let sampleData = Data("""
[
  {
  "type": "one",
  "body": {
    "one": 1
  },
  .
  .
  .
},
  {
  "type": "two",
  "body": {
    "two": 2,
    "twoData": "two"
  },
  .
  .
  .
}
]
""".utf8)

struct MyObject: Codable {
  let type: String
  let body: Body
}

struct Body: Codable {
  let one, two: Int?
  let twoData: String?
}

print(try JSONDecoder().decode([MyObject].self, from: sampleData))

Here you can see that the keys in Body are all optionals. My solution requires they being parsed into different types according to the value given in the parameter type. How can I parse body into the following 2 separate types according to the value I receive in type?

struct OneBody: Decodable {
  let one: Int
}

struct TwoBody: Decodable {
  let two: Int
  let twoData: String
}
Frankenstein
  • 15,732
  • 4
  • 22
  • 47
  • @Sh_Khan The above post was helpful. Can you clarify if there is a way to determine type using a parameter instead of just trying with different objects until it succeeds? – Frankenstein Jun 22 '22 at 05:14
  • Similar question (and similar answer) here https://stackoverflow.com/q/54700409/669586 – Sulthan Jun 28 '22 at 08:34

1 Answers1

0

The approach that I finally went with is with a custom init(from decoder: Decoder) method where the type is determined first and then using a switch statement iterate through each case to decode each type and assign it to the body as another enum. Here is the code:

struct MyObject: Decodable {
  let body: MyBody

  init(from decoder: Decoder) throws {
    let container = try decoder.container(keyedBy: CodingKeys.self)
    let type = try container.decode(MyType.self, forKey: .type)
    switch type {
    case .one:
      body = .one(try container.decode(OneBody.self, forKey: .body))
    case .two:
      body = .two(try container.decode(TwoBody.self, forKey: .body))
    }
  }

  enum CodingKeys: String, CodingKey {
    case type, body
  }
}

enum MyType: String, Decodable {
  case one, two
}

enum MyBody: Decodable {
  case one(OneBody)
  case two(TwoBody)
}

struct OneBody: Decodable {
  let one: Int
}

struct TwoBody: Decodable {
  let two: Int
  let twoData: String
}

print(try JSONDecoder().decode([MyObject].self, from: sampleData))

PS: Please post an answer or a comment if there are any ideas for a better approach.

Frankenstein
  • 15,732
  • 4
  • 22
  • 47