Skip to content

Instantly share code, notes, and snippets.

@proxpero
Last active May 12, 2019 21:26
Show Gist options
  • Save proxpero/189a723fb96bb88fac5bf9e11d6cf9e2 to your computer and use it in GitHub Desktop.
Save proxpero/189a723fb96bb88fac5bf9e11d6cf9e2 to your computer and use it in GitHub Desktop.
Encoding and Decoding Enums with Associated Values
// See http://proxpero.com/2017/07/11/encoding-and-decoding-custom-enums-with-associated-values-in-swift-4/
enum Barcode {
case upc(Int, Int, Int, Int)
case qrCode(String)
}
extension Barcode: Codable {
init(from decoder: Decoder) throws {
self = try Barcode.Coding.init(from: decoder).barcode()
}
func encode(to encoder: Encoder) throws {
try Barcode.Coding.init(barcode: self).encode(to: encoder)
}
}
extension Barcode {
fileprivate struct Coding: Codable {
private struct UPCDigits: Codable {
let numberSystem: Int
let manufacturer: Int
let product: Int
let check: Int
}
private enum CodingError: Error {
case barcodeCodingError(String)
}
private var upc: UPCDigits?
private var qrCode: String?
fileprivate init(barcode: Barcode) {
switch barcode {
case .upc(let numberSystem, let manufacturer, let product, let check):
self.upc = UPCDigits(numberSystem: numberSystem, manufacturer: manufacturer, product: product, check: check)
case .qrCode(let productCode):
self.qrCode = productCode
}
}
fileprivate func barcode() throws -> Barcode {
switch (upc, qrCode) {
case (.some(let upcDigits), nil):
return Barcode.upc(upcDigits.numberSystem, upcDigits.manufacturer, upcDigits.product, upcDigits.check)
case (nil, .some(let productCode)):
return Barcode.qrCode(productCode)
default:
throw CodingError.barcodeCodingError("Could not convert \(self) into either a upc or a qrCode")
}
}
}
}
import Foundation
let encoder = JSONEncoder()
encoder.outputFormatting = .prettyPrinted
let decoder = JSONDecoder()
var productBarcode: Barcode
var data: Data
var json: String
var expectation: String
var result: Barcode
productBarcode = .upc(8, 85909, 51226, 3)
data = try encoder.encode(productBarcode)
json = String.init(data: data, encoding: .utf8)!
expectation = """
{
"upc" : {
"check" : 3,
"manufacturer" : 85909,
"product" : 51226,
"numberSystem" : 8
}
}
"""
result = try decoder.decode(Barcode.self, from: data)
assert(json == expectation)
//assert(result == productBarcode)
productBarcode = .qrCode("ABCDEFGHIJKLMNOP")
data = try encoder.encode(productBarcode)
json = String.init(data: data, encoding: .utf8)!
expectation = """
{
"qrCode" : "ABCDEFGHIJKLMNOP"
}
"""
result = try decoder.decode(Barcode.self, from: data)
assert(json == expectation)
//assert(result == productBarcode)
@stephencelis
Copy link

You can avoid the intermediate Codable struct by defining some CodingKeys and encoding/decoding values from the encoder/decoder container.

enum Barcode {
  case upc(Int, Int, Int, Int)
  case qrCode(String)
}

extension Barcode: Codable {
  enum CodingKeys: String, CodingKey {
    case upc
    case qrCode
  }

  init(from decoder: Decoder) throws {
    let values = try decoder.container(keyedBy: CodingKeys.self)
    do {
      let codes = try values.decode([Int].self, forKey: .upc)
      self = .upc(codes[0], codes[1], codes[2], codes[3])
    } catch {
      self = .qrCode(try values.decode(String.self, forKey: .qrCode))
    }
  }

  func encode(to encoder: Encoder) throws {
    var container = encoder.container(keyedBy: CodingKeys.self)
    switch self {
    case let .upc(numberSystem, manufacturer, product, check):
      try container.encode([numberSystem, manufacturer, product, check], forKey: .upc)
    case let .qrCode(productCode):
      try container.encode(productCode, forKey: .upc)
    }
  }
}

@Kordikk
Copy link

Kordikk commented Sep 11, 2017

@stephencelis your way would result in creating different JSON - without names for enum's associated values. so sending that to some kind of API may would result in parsing error since you've passed wrong JSON.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment