3

This is the JSON I am trying to decode. The value of objectType decides what object to create.

{
  "options": [
    {
      "objectType": "OptionTypeA",
      "label": "optionALabel1",
      "value": "optionAValue1"
    },
    {
      "objectType": "OptionTypeB",
      "label": "optionBLabel",
      "value": "optionBValue"
    },
    {
      "objectType": "OptionTypeA",
      "label": "optionALabel2",
      "value": "optionAValue2"
    }
  ]
}

Say I have the 2 Option Types defined like so

public protocol OptionType {
  var label: String { get }
  var value: String { get }
}

struct OptionTypeA: Decodable {
  let label: String
  let value: String
  
  // and some others...
}

struct OptionTypeB: Decodable {
  let label: String
  let value: String
  
  // and some others...
}

struct Option: Decodable {
  let options: [OptionType]
  
  enum CodingKeys: String, CodingKey {
    case options
    case label
    case value
    case objectType
  }
  
  init(from decoder: Decoder) throws {
    let values = try decoder.container(keyedBy: CodingKeys.self)
    var optionsContainer = try values.nestedUnkeyedContainer(forKey: .options)
    var options = [OptionType]()
    
    while !optionsContainer.isAtEnd {
      let itemContainer = try optionsContainer.nestedContainer(keyedBy: CodingKeys.self)
      
      switch try itemContainer.decode(String.self, forKey: .objectType) {
      // What should I do here so that I do not have to manually decode `OptionTypeA` and `OptionTypeB`?
      case "OptionTypeA": options.append() 
      case "OptionTypeB": options.append()
      default: fatalError("Unknown type")
      }
    }
    
    self.options = options
  }
}

I know I can then manually decode each key in itemContainer and create the individual option type objects in the switch case. But I do not want to do that. How can I just decode these objects?

1 Answer 1

7

A swiftier way than a protocol for the common properties is an enum with associated values.

The Option enum decodes first the objectType – which can even be decoded as an enum – and depending on the value it decodes the different structs.

enum OptionType : String, Decodable {
    case a = "OptionTypeA", b = "OptionTypeB"
}

struct Root : Decodable {
    let options : [Option]
}

enum Option : Decodable {
    private enum CodingKeys : String, CodingKey { case objectType }
    
    case typeA(OptionTypeA)
    case typeB(OptionTypeB)
    
    init(from decoder : Decoder) throws {
        let container = try decoder.container(keyedBy: CodingKeys.self)
        let typeContainer = try decoder.singleValueContainer()
        let optionType = try container.decode(OptionType.self, forKey: .objectType)
        switch optionType {
            case .a: self = .typeA(try typeContainer.decode(OptionTypeA.self))
            case .b: self = .typeB(try typeContainer.decode(OptionTypeB.self))
        }
    }
}

struct OptionTypeA: Decodable {
  let label: String
  let value: String
  
  // and some others...
}

struct OptionTypeB: Decodable {
  let label: String
  let value: String
  
  // and some others...
}

let jsonString = """
{
  "options": [
    {
      "objectType": "OptionTypeA",
      "label": "optionALabel1",
      "value": "optionAValue1"
    },
    {
      "objectType": "OptionTypeB",
      "label": "optionBLabel",
      "value": "optionBValue"
    },
    {
      "objectType": "OptionTypeA",
      "label": "optionALabel2",
      "value": "optionAValue2"
    }
  ]
}
"""

let data = Data(jsonString.utf8)

do {
    let result = try JSONDecoder().decode(Root.self, from: data)
    for option in result.options {
        switch option {
            case .typeA(let optionTypeA): print(optionTypeA)
            case .typeB(let optionTypeB): print(optionTypeB)
        }
    }
} catch {
    print(error)
}
Sign up to request clarification or add additional context in comments.

1 Comment

Thanks! This helped.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.