2

Note: I have already looked at this question -> How do I use custom keys with Swift 4's Decodable protocol? But it does not explain how to Encode/Decode enums

Here is the structure I want:

struct MyStruct: Decodable {
    let count: PostType
}

enum PostType: Decodable {
    case fast(value: Int, value2: Int)
    case slow(string: String, string2: String)
}

Now i know how i want my struct to look, the problem is:

  1. I do not know what the init function should look like inside of the PostType enum.

I use the code below to help me construct the JSON quickly.

    let jsonData = """
    {
       "count": {
          "fast" :
           {
               "value": 4,
               "value2": 5
           }
       }
    }
    """.data(using: .utf8)!

    // Decoding
    do {
        let decoder = JSONDecoder()
        let response = try decoder.decode(MyStruct.self, from: jsonData)
        print(response)
    } catch {
        print(error)
    }

Can anyone help me with this?

Edit1 my JSON looks like this

   {
        "count": {
           "fast" :
            {
                "value": 4,
                "value2": 5
            }
        }
    }

What should the init look like in the PostType enum ?

swift nub
  • 2,747
  • 3
  • 17
  • 39
  • This is not a duplicate. That question is decoding a struct. This is decoding an Enum with a tuple. I know how to decode structs, but with an enum it is very different. – swift nub Jun 16 '17 at 17:12
  • What is your expected JSON representation of the associated enum values? – vadian Jun 16 '17 at 17:28
  • @vadian thats the thing. I know that i want my struct to look like `MyStruct`, But i do not know what the JSON should look like. If someone can tell me then i can make the JSON be that value. – swift nub Jun 16 '17 at 18:49
  • JSON has a limited set of types: `Array`, `Dictionary` as collection types and `String`, `Number`, `Bool` and `null` as value types. You can represent your enum as array of String / Int or each value as key / value pair or as dictionary with the case as key. – vadian Jun 16 '17 at 18:53
  • @vadian I have edited my question and included what my JSON looks like. How do i get the PostType enum to parse this? – swift nub Jun 16 '17 at 19:05
  • I wrote an answer providing several options. – vadian Jun 16 '17 at 19:12

1 Answers1

5

Since an enum with associated types does not match any JSON type you need a bit more handwork and write a custom mapping.

The following code covers three options.

  • A dictionary with the case as key and an containing dictionary with parameter labels as keys
  • Encoding / Decoding each associated value as separate key / value pair
  • Encoding / Decoding all associated values in an array with an arbitrary key.

First of all the enum must not conform to Codable

enum PostType {
    case fast(value: Int, value2: Int)
    case middle(bool: Bool)
    case slow(string: String, string2: String)
}

The case fast uses an array, middle a sub-dictionary, slow separate key / value pairs.


Then declare the MyStruct struct, adopt Codable and declare the type

struct MyStruct : Codable {
    var type : PostType

This solution requires custom keys

  enum CodingKeys: String, CodingKey {
      case value, string, string2, middle
  }

The encode method switches on the cases and creates the appropriate types

  func encode(to encoder: Encoder) throws {
     var container = encoder.container(keyedBy: CodingKeys.self)
      switch type {
      case .fast(let value, let value2) :
          try container.encode([value, value2], forKey: .value)

      case .slow(let string, let string2) :
          try container.encode(string, forKey: .string)
          try container.encode(string2, forKey: .string2)

      case .middle(let bool):
          try container.encode(["bool" : bool], forKey: .middle)
      }
  }

In the decode method you can distinguish the cases by the passed keys, make sure that they are unique.

  init(from decoder: Decoder) throws {
      let values = try decoder.container(keyedBy: CodingKeys.self)
      let allKeys = values.allKeys
      if allKeys.contains(.middle) {
          let value = try values.decode([String:Bool].self, forKey: .middle)
          type = PostType.middle(bool: value["bool"]!)
      } else if allKeys.contains(.value) {
          let value = try values.decode([Int].self, forKey: .value)
          type = PostType.fast(value: value[0], value2: value[1])
      } else {
          let string = try values.decode(String.self, forKey: .string)
          let string2 = try values.decode(String.self, forKey: .string2)
          type = PostType.slow(string: string, string2: string2)
      }
   }
}

Although some keys are hard-coded the first option seems to be the most suitable one.


Finally an example to use it:

let jsonString = "[{\"value\": [2, 6]}, {\"string\" : \"foo\", \"string2\" : \"bar\"}, {\"middle\" : {\"bool\" : true}}]"

let jsonData = jsonString.data(using: .utf8)!

do {
    let decoded = try JSONDecoder().decode([MyStruct].self, from: jsonData)
    print("decoded:", decoded)

    let newEncoded = try JSONEncoder().encode(decoded)
    print("re-encoded:", String(data: newEncoded, encoding: .utf8)!)

} catch {
    print(error)
}
vadian
  • 274,689
  • 30
  • 353
  • 361