Skip to content

Instantly share code, notes, and snippets.

@proxpero proxpero/Barcode.swift
Last active May 12, 2019

Embed
What would you like to do?
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

This comment has been minimized.

Copy link

commented Jul 15, 2017

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

This comment has been minimized.

Copy link

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
You can’t perform that action at this time.