Enum & custom type from primitive JSON type

with Swift Codable


Last year I wrote a review of Codable protocol in Swift 4 and how it competed with JSON encoder/decoder out there. I happy to say that Codable served me well over the year, so today I’m going to share some more use cases that I used throughout the year which also use Codable.

Enum

If your enum has a raw value that can be represented as Int or String (which I think that should cover most cases). We can make enum Codable by assigning a raw value to it.

Let’s say you have json object like this:

{"name": "iOS developer", "status": "open"}

You can create Swift struct like this:

struct Job: Codable {

  enum Status: String, Codable {
    case open
    case close
  }

  let name: String
  let status: Status
}

If you have enum which isn’t String or Int representable you can still make it conform toCodable as long as that raw value is Codable.

Custom type from primitive type

If you have custom type that can derived from basic JSON type, for example you have object with image sending as base64 encoded string:

{
  "name": "XXX",
  "image": "data:image/png;base64,iVBORw0KGgo......."
}

Since UIImage doesn’t conform toCodable and we can’t conform it with an extension, we have 2 ways to handle this:

  1. Create another wrapper class around this image.

Code above should be familiar to you with one key take away.

Instead of :

let values = try decoder.container(keyedBy: CodingKeys.self)

we use:

let container = try decoder.singleValueContainer()

since we are dealing with one primitive value, base64 string, in this case.

Then we can use this wrapper like this:

struct ModelUsingImageWrapper: Codable {
  let name: String
  let image: Base64ImageWrapper
}
  1. Add custom encode/decode for UIImage in KeyedEncodingContainer :

And use this with explicit encode and decode.

struct ModelUsingKeyedEncodingContainer: Codable {
  let name: String
  let image: UIImage

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

  public init(from decoder: Decoder) throws {
    let container = try decoder.container(keyedBy: CodingKeys.self)
    name = try container.decode(String.self, forKey: .name)
    image = try container.decode(UIImage.self, forKey: .image)
  }

  public func encode(to encoder: Encoder) throws {
    var container = encoder.container(keyedBy: CodingKeys.self)
    try container.encode(name, forKey: .name)
    try container.encode(image, forKey: .image)
  }
}

These 2 approaches should suffice for most of your use cases.