205
votes
enum PostType: Decodable {

    init(from decoder: Decoder) throws {

        // What do i put here?
    }

    case Image
    enum CodingKeys: String, CodingKey {
        case image
    }
}

What do i put to complete this? Also, lets say i changed the case to this:

case image(value: Int)

How do i make this conform to Decodable ?

EDit Here is my full code (which does not work)

let jsonData = """
{
    "count": 4
}
""".data(using: .utf8)!

        do {
            let decoder = JSONDecoder()
            let response = try decoder.decode(PostType.self, from: jsonData)

            print(response)
        } catch {
            print(error)
        }
    }
}

enum PostType: Int, Codable {
    case count = 4
}

Final Edit Also, how will it handle an enum like this?

enum PostType: Decodable {
    case count(number: Int)
}
9

9 Answers

341
votes

It's pretty easy, just use String or Int raw values which are implicitly assigned.

enum PostType: Int, Codable {
    case image, blob
}

image is encoded to 0 and blob to 1

Or

enum PostType: String, Codable {
    case image, blob
}

image is encoded to "image" and blob to "blob"


This is a simple example how to use it:

enum PostType : Int, Codable {
    case count = 4
}

struct Post : Codable {
    var type : PostType
}

let jsonString = "{\"type\": 4}"

let jsonData = Data(jsonString.utf8)

do {
    let decoded = try JSONDecoder().decode(Post.self, from: jsonData)
    print("decoded:", decoded.type)
} catch {
    print(error)
}
146
votes

How to make enums with associated types conform to Codable

This answer is similar to @Howard Lovatt's but avoids creating a PostTypeCodableForm struct and instead uses the KeyedEncodingContainer type provided by Apple as a property on Encoder and Decoder, which reduces boilerplate.

enum PostType: Codable {
    case count(number: Int)
    case title(String)
}

extension PostType {

    private enum CodingKeys: String, CodingKey {
        case count
        case title
    }

    enum PostTypeCodingError: Error {
        case decoding(String)
    }

    init(from decoder: Decoder) throws {
        let values = try decoder.container(keyedBy: CodingKeys.self)
        if let value = try? values.decode(Int.self, forKey: .count) {
            self = .count(number: value)
            return
        }
        if let value = try? values.decode(String.self, forKey: .title) {
            self = .title(value)
            return
        }
        throw PostTypeCodingError.decoding("Whoops! \(dump(values))")
    }

    func encode(to encoder: Encoder) throws {
        var container = encoder.container(keyedBy: CodingKeys.self)
        switch self {
        case .count(let number):
            try container.encode(number, forKey: .count)
        case .title(let value):
            try container.encode(value, forKey: .title)
        }
    }
}

This code works for me on Xcode 9b3.

import Foundation // Needed for JSONEncoder/JSONDecoder

let encoder = JSONEncoder()
encoder.outputFormatting = .prettyPrinted
let decoder = JSONDecoder()

let count = PostType.count(number: 42)
let countData = try encoder.encode(count)
let countJSON = String.init(data: countData, encoding: .utf8)!
print(countJSON)
//    {
//      "count" : 42
//    }

let decodedCount = try decoder.decode(PostType.self, from: countData)

let title = PostType.title("Hello, World!")
let titleData = try encoder.encode(title)
let titleJSON = String.init(data: titleData, encoding: .utf8)!
print(titleJSON)
//    {
//        "title": "Hello, World!"
//    }
let decodedTitle = try decoder.decode(PostType.self, from: titleData)
51
votes

Swift would throw a .dataCorrupted error if it encounters unknown enum value. If your data is coming from a server, it can send you an unknown enum value at any time (bug server side, new type added in an API version and you want the previous versions of your app to handle the case gracefully, etc), you'd better be prepared, and code "defensive style" to safely decode your enums.

Here is an example on how to do it, with or without associated value

    enum MediaType: Decodable {
       case audio
       case multipleChoice
       case other
       // case other(String) -> we could also parametrise the enum like that

       init(from decoder: Decoder) throws {
          let label = try decoder.singleValueContainer().decode(String.self)
          switch label {
             case "AUDIO": self = .audio
             case "MULTIPLE_CHOICES": self = .multipleChoice
             default: self = .other
             // default: self = .other(label)
          }
       }
    }

And how to use it in a enclosing struct:

    struct Question {
       [...]
       let type: MediaType

       enum CodingKeys: String, CodingKey {
          [...]
          case type = "type"
       }


   extension Question: Decodable {
      init(from decoder: Decoder) throws {
         let container = try decoder.container(keyedBy: CodingKeys.self)
         [...]
         type = try container.decode(MediaType.self, forKey: .type)
      }
   }
41
votes

To extend on @Toka's answer, you may too add a raw representable value to the enum, and use the default optional constructor to build the enum without a switch:

enum MediaType: String, Decodable {
  case audio = "AUDIO"
  case multipleChoice = "MULTIPLE_CHOICES"
  case other

  init(from decoder: Decoder) throws {
    let label = try decoder.singleValueContainer().decode(String.self)
    self = MediaType(rawValue: label) ?? .other
  }
}

It may be extended using a custom protocol that allows to refactor the constructor:

protocol EnumDecodable: RawRepresentable, Decodable {
  static var defaultDecoderValue: Self { get }
}

extension EnumDecodable where RawValue: Decodable {
  init(from decoder: Decoder) throws {
    let value = try decoder.singleValueContainer().decode(RawValue.self)
    self = Self(rawValue: value) ?? Self.defaultDecoderValue
  }
}

enum MediaType: String, EnumDecodable {
  static let defaultDecoderValue: MediaType = .other

  case audio = "AUDIO"
  case multipleChoices = "MULTIPLE_CHOICES"
  case other
}

It can also be easily extended for throwing an error if an invalid enum value was specified, rather than defaulting on a value. Gist with this change is available here: https://gist.github.com/stephanecopin/4283175fabf6f0cdaf87fef2a00c8128.
The code was compiled and tested using Swift 4.1/Xcode 9.3.

7
votes

A variant of @proxpero's response that is terser would be to formulate the decoder as:

public init(from decoder: Decoder) throws {
    let values = try decoder.container(keyedBy: CodingKeys.self)
    guard let key = values.allKeys.first else { throw err("No valid keys in: \(values)") }
    func dec<T: Decodable>() throws -> T { return try values.decode(T.self, forKey: key) }

    switch key {
    case .count: self = try .count(dec())
    case .title: self = try .title(dec())
    }
}

func encode(to encoder: Encoder) throws {
    var container = encoder.container(keyedBy: CodingKeys.self)
    switch self {
    case .count(let x): try container.encode(x, forKey: .count)
    case .title(let x): try container.encode(x, forKey: .title)
    }
}

This permits the compiler to exhaustively verify the cases, and also doesn't suppress the error message for the case where the encoded value doesn't match the key's expected value.

7
votes

Actually the answers above are really great, but they are missing some details for what many people need in a continuously developed client/server project. We develop an app while our backend continually evolves over time, which means some enum cases will change that evolution. So we need an enum decoding strategy that is able to decode arrays of enums that contain unknown cases. Otherwise decoding the object that contains the array simply fails.

What I did is quite simple:

enum Direction: String, Decodable {
    case north, south, east, west
}

struct DirectionList {
   let directions: [Direction]
}

extension DirectionList: Decodable {

    public init(from decoder: Decoder) throws {

        var container = try decoder.unkeyedContainer()

        var directions: [Direction] = []

        while !container.isAtEnd {

            // Here we just decode the string from the JSON which always works as long as the array element is a string
            let rawValue = try container.decode(String.self)

            guard let direction = Direction(rawValue: rawValue) else {
                // Unknown enum value found - ignore, print error to console or log error to analytics service so you'll always know that there are apps out which cannot decode enum cases!
                continue
            }
            // Add all known enum cases to the list of directions
            directions.append(direction)
        }
        self.directions = directions
    }
}

Bonus: Hide implementation > Make it a Collection

To hide implementation detail is always a good idea. For this you'll need just a little bit more code. The trick is to conform DirectionsList to Collection and make your internal list array private:

struct DirectionList {

    typealias ArrayType = [Direction]

    private let directions: ArrayType
}

extension DirectionList: Collection {

    typealias Index = ArrayType.Index
    typealias Element = ArrayType.Element

    // The upper and lower bounds of the collection, used in iterations
    var startIndex: Index { return directions.startIndex }
    var endIndex: Index { return directions.endIndex }

    // Required subscript, based on a dictionary index
    subscript(index: Index) -> Element {
        get { return directions[index] }
    }

    // Method that returns the next index when iterating
    func index(after i: Index) -> Index {
        return directions.index(after: i)
    }
}

You can read more about conforming to custom collections in this blog post by John Sundell: https://medium.com/@johnsundell/creating-custom-collections-in-swift-a344e25d0bb0

6
votes

You can do what you want, but it is a bit involved :(

import Foundation

enum PostType: Codable {
    case count(number: Int)
    case comment(text: String)

    init(from decoder: Decoder) throws {
        self = try PostTypeCodableForm(from: decoder).enumForm()
    }

    func encode(to encoder: Encoder) throws {
        try PostTypeCodableForm(self).encode(to: encoder)
    }
}

struct PostTypeCodableForm: Codable {
    // All fields must be optional!
    var countNumber: Int?
    var commentText: String?

    init(_ enumForm: PostType) {
        switch enumForm {
        case .count(let number):
            countNumber = number
        case .comment(let text):
            commentText = text
        }
    }

    func enumForm() throws -> PostType {
        if let number = countNumber {
            guard commentText == nil else {
                throw DecodeError.moreThanOneEnumCase
            }
            return .count(number: number)
        }
        if let text = commentText {
            guard countNumber == nil else {
                throw DecodeError.moreThanOneEnumCase
            }
            return .comment(text: text)
        }
        throw DecodeError.noRecognizedContent
    }

    enum DecodeError: Error {
        case noRecognizedContent
        case moreThanOneEnumCase
    }
}

let test = PostType.count(number: 3)
let data = try JSONEncoder().encode(test)
let string = String(data: data, encoding: .utf8)!
print(string) // {"countNumber":3}
let result = try JSONDecoder().decode(PostType.self, from: data)
print(result) // count(3)
1
votes

Features

  • Simple use. One line in Decodable instance: line eg let enum: DecodableEnum<AnyEnum>
  • Is decoded with standard mapping mechanism: JSONDecoder().decode(Model.self, from: data)
  • covered case of receiving unknown data (for example, mapping a Decodable object will not fail if you receive unexpected data)
  • handle/deliver mapping or decoding errors

Details

  • Xcode 12.0.1 (12A7300)
  • Swift 5.3

Solution

import Foundation

enum DecodableEnum<Enum: RawRepresentable> where Enum.RawValue == String {
    case value(Enum)
    case error(DecodingError)

    var value: Enum? {
        switch self {
        case .value(let value): return value
        case .error: return nil
        }
    }

    var error: DecodingError? {
        switch self {
        case .value: return nil
        case .error(let error): return error
        }
    }

    enum DecodingError: Error {
        case notDefined(rawValue: String)
        case decoding(error: Error)
    }
}

extension DecodableEnum: Decodable {
    init(from decoder: Decoder) throws {
        do {
            let rawValue = try decoder.singleValueContainer().decode(String.self)
            guard let layout = Enum(rawValue: rawValue) else {
                self = .error(.notDefined(rawValue: rawValue))
                return
            }
            self = .value(layout)
        } catch let err {
            self = .error(.decoding(error: err))
        }
    }
}

Usage sample

enum SimpleEnum: String, Codable {
    case a, b, c, d
}

struct Model: Decodable {
    let num: Int
    let str: String
    let enum1: DecodableEnum<SimpleEnum>
    let enum2: DecodableEnum<SimpleEnum>
    let enum3: DecodableEnum<SimpleEnum>
    let enum4: DecodableEnum<SimpleEnum>?
}

let dictionary: [String : Any] = ["num": 1, "str": "blablabla", "enum1": "b", "enum2": "_", "enum3": 1]

let data = try! JSONSerialization.data(withJSONObject: dictionary)
let object = try JSONDecoder().decode(Model.self, from: data)
print("1. \(object.enum1.value)")
print("2. \(object.enum2.error)")
print("3. \(object.enum3.error)")
print("4. \(object.enum4)")

enter image description here

0
votes

A lot of good approaches here, but I have not seen one discussing enums with more than one value, although it can be deduced from examples - maybe someone can find a use for this one:

import Foundation

enum Tup {
  case frist(String, next: Int)
  case second(Int, former: String)
  
  enum TupType: String, Codable {
    case first
    case second
  }
  enum CodingKeys: String, CodingKey {
    case type
    
    case first
    case firstNext
    
    case second
    case secondFormer
  }
  
}

extension Tup: Codable {
  init(from decoder: Decoder) throws {
    let values = try decoder.container(keyedBy: CodingKeys.self)
    let type = try values.decode(TupType.self, forKey: .type)
    switch type {
    case .first:
      let str = try values.decode(String.self, forKey: .first)
      let next = try values.decode(Int.self, forKey: .firstNext)
      self = .frist(str, next: next)
    case .second:
      let int = try values.decode(Int.self, forKey: .second)
      let former = try values.decode(String.self, forKey: .secondFormer)
      self = .second(int, former: former)
    }
  }
  
  func encode(to encoder: Encoder) throws {
    var container = encoder.container(keyedBy: CodingKeys.self)
    
    switch self {
    case .frist(let str, next: let next):
      try container.encode(TupType.first, forKey: .type)
      try container.encode(str, forKey: .first)
      try container.encode(next, forKey: .firstNext)
    case .second(let int, former: let former):
      try container.encode(TupType.second, forKey: .type)
      try container.encode(int, forKey: .second)
      try container.encode(former, forKey: .secondFormer)
    }
    
  }
}

let example1 = Tup.frist("123", next: 90)
do {
  let encoded = try JSONEncoder().encode(example1)
  print(encoded)
  
  let decoded = try JSONDecoder().decode(Tup.self, from: encoded)
  print("decoded 1 = \(decoded)")
}
catch {
  print("errpr = \(error.localizedDescription)")
}


let example2 = Tup.second(10, former: "dantheman")

do {
  let encoded = try JSONEncoder().encode(example2)
  print(encoded)
  
  let decoded = try JSONDecoder().decode(Tup.self, from: encoded)
  print("decoded 2 = \(decoded)")
}
catch {
  print("errpr = \(error.localizedDescription)")
}