Codable enum with multiple keys and associated values
Use the assumed raw string values of the init method as (string) value of the enum case
enum EmployeeClassification : Codable, Equatable {
case aaa
case bbb
case ccc(Int) // (year)
init?(rawValue: String?) {
guard let val = rawValue?.lowercased() else {
return nil
}
switch val {
case "aaa", "a":
self = .aaa
case "bbb":
self = .bbb
case "ccc":
self = .ccc(0)
default: return nil
}
}
// Codable
private enum CodingKeys: String, CodingKey { case aaa, bbb, ccc }
init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
if let value = try? container.decode(Int.self, forKey: .ccc) {
self = .ccc(value)
} else if let aaaValue = try? container.decode(String.self, forKey: .aaa), ["aaa", "AAA", "a"].contains(aaaValue) {
self = .aaa
} else if let bbbValue = try? container.decode(String.self, forKey: .bbb), bbbValue == "bbb" {
self = .bbb
} else {
throw DecodingError.dataCorrupted(DecodingError.Context(codingPath: container.codingPath, debugDescription: "Data doesn't match"))
}
}
func encode(to encoder: Encoder) throws {
var container = encoder.container(keyedBy: CodingKeys.self)
switch self {
case .aaa: try container.encode("aaa", forKey: .aaa)
case .bbb: try container.encode("bbb", forKey: .bbb)
case .ccc(let year): try container.encode(year, forKey: .ccc)
}
}
}
The Decoding Error is quite generic. You can throw more specific errors for each CodingKey
Making an enum with associated values conform to decodable?
You need to create a custom init(from decoder: Decoder) throws
, and treat your associated values like they are normal fields in the object, using CodingKeys
to decode them. Details depend on how your associated values are actually represented in encoded object.
For example if your values are encoded as:
{ "team1": "aaa", "team2": "bbb" }
and
{ "individual": ["x", "y", "z"]}
You can:
enum SportType: Decodable {
case team(String, String) //Two team names
case individual([String]) //List of player names
// Define associated values as keys
enum CodingKeys: String, CodingKey {
case team1
case team2
case individual
}
init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
// Try to decode as team
if let team1 = try container.decodeIfPresent(String.self, forKey: .team1),
let team2 = try container.decodeIfPresent(String.self, forKey: .team2) {
self = .team(team1, team2)
return
}
// Try to decode as individual
if let individual = try container.decodeIfPresent([String].self, forKey: .individual) {
self = .individual(individual)
return
}
// No luck
throw DecodingError.dataCorruptedError(forKey: .individual, in: container, debugDescription: "No match")
}
}
Test:
let encoded = """
{ "team1": "aaa", "team2": "bbb" }
""".data(using: .utf8)
let decoded = try? JSONDecoder().decode(SportType.self, from: encoded!)
switch decoded {
case .team(let a, let b):
print("I am a valid team with team1=\(a), team2=\(b)")
case .individual(let a):
print("I am a valid individual with individual=\(a)")
default:
print("I was not parsed :(")
}
Prints:
I am a valid team with team1=aaa, team2=bbb
Make Enum (with associated type) Codable, successfully
I think you should first check whether a key exists in the decoder's container or not, before decoding it. Currently, you are doing this:
do {
let value = try values.decode(Double.self, forKey: .pulse)
self = .pulse(value)
} catch (let error) {
print(error)
}
do {
let value = try values.decode(Double.self, forKey: .draw)
self = .draw(value)
} catch (let error) {
print(error)
}
do {
let value = try values.decode(Double.self, forKey: .flash)
self = .flash(value)
} catch (let error) {
print(error)
}
There is no way that the decoder container is going to have all three keys in there, isn't it?
So, check before decoding:
if values.contains(.pulse) {
do {
let value = try values.decode(Double.self, forKey: .pulse)
self = .pulse(value)
return // remember to return here, so you don't set self back to .draw(0.0) again!
} catch (let error) {
print(error)
}
} else if values.contains(.draw) {
do {
let value = try values.decode(Double.self, forKey: .draw)
self = .draw(value)
return
} catch (let error) {
print(error)
}
} else if values.contains(.flash) {
do {
let value = try values.decode(Double.self, forKey: .flash)
self = .flash(value)
return
} catch (let error) {
print(error)
}
}
How to use Codable protocol for an enum with other enum as associated value (nested enum)
The associated value
is LoggedInState
or LoggedOutState
but you have to encode its rawValue
(String):
case let .LoggedIn(value):
try container.encode(value.rawValue, forKey: .loggedIn)
case let .LoggedOut(value):
try container.encode(value.rawValue, forKey: .loggedOut)
}
according to the decode
method where you're creating the enum cases from the rawValue
.
Swift codable recursive enum with dynamic keys
You can use singleValueContainer
to decode/encode the each case of EntryData
, without using any hardcoded keys. When decoding, we can try to decode as all three cases, and see which one succeeded.
enum EntryData: Codable {
case string(String)
case array([EntryData])
case nested([String: EntryData])
init(from decoder: Decoder) throws {
let container = try decoder.singleValueContainer()
let nested = try? container.decode([String: EntryData].self)
let array = try? container.decode([EntryData].self)
let string = try? container.decode(String.self)
switch (string, array, nested) {
case (let s?, nil, nil):
self = .string(s)
case (nil, let a?, nil):
self = .array(a)
case (nil, nil, let n?):
self = .nested(n)
default:
throw DecodingError.valueNotFound(
EntryData.self,
.init(codingPath: decoder.codingPath,
debugDescription: "Value must be either string, array or a dictionary!",
underlyingError: nil))
}
}
func encode(to encoder: Encoder) throws {
var container = encoder.singleValueContainer()
switch self {
case .string(let s):
try container.encode(s)
case .array(let a):
try container.encode(a)
case .nested(let n):
try container.encode(n)
}
}
}
Now you can do:
let entry = try JSONDecoder().decode(Entry.self, from: data)
How to make a Swift Enum with Associated values conform to Codable?
You just need to switch your enumeration associated values:
enum Value: Codable {
case str(String)
case formField(FormField)
private enum CodingKeys: String, CodingKey {
case str, formField
}
func encode(to encoder: Encoder) throws {
var container = encoder.container(keyedBy: CodingKeys.self)
switch self {
case let .str(string):
try container.encode(string, forKey: .str)
case let .formField(field):
try container.encode(field, forKey: .formField)
}
}
init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
switch container.allKeys.first {
case .str:
self = try .str(container.decode(String.self, forKey: .str))
case .formField:
self = try .formField(container.decode(FormField.self, forKey: .formField))
default:
throw DecodingError.dataCorrupted(
.init(
codingPath: container.codingPath,
debugDescription: "invalid data"
)
)
}
}
}
Swift enum associated value with different types
I would solve this with a Kind
enum type, for each kind of variation.
public enum AnimationType {
public enum Kind<Value> {
case scalar(Value)
case keyframes([Keyframe<Value>])
}
case position(Kind<Float>)
case scale(Kind<Float>)
case rect(Kind<CGRect>)
case transform(Kind<CGAffineTransform>)
}
Usage:
let anim1 = AnimationType.position(.scalar(10))
let anim2 = AnimationType.position(.keyframes([Keyframe(10)]))
Getting values:
switch anim1 {
case .position(let kind):
switch kind {
case .scalar(let value):
print("value: \(value)")
case .keyframes(let keyframes):
print("keyframes: \(keyframes)")
}
default: // You would implement the rest
break
}
switch anim1 {
case .position(.scalar(let value)):
print("value: \(value)")
case .position(.keyframes(let keyframes)):
print("keyframes: \(keyframes)")
default: // You would implement the rest
break
}
if case .position(.scalar(let value)) = anim1 {
print("value: \(value)")
}
You can also add Codable
conformance:
public struct Keyframe<Value: Codable> {
let value: Value
init(_ value: Value) {
self.value = value
}
}
extension Keyframe: Codable {
public func encode(to encoder: Encoder) throws {
var container = encoder.singleValueContainer()
try container.encode(value)
}
public init(from decoder: Decoder) throws {
let container = try decoder.singleValueContainer()
value = try container.decode(Value.self)
}
}
public enum AnimationType {
public enum Kind<Value: Codable> {
case scalar(Value)
case keyframes([Keyframe<Value>])
}
case position(Kind<Float>)
case scale(Kind<Float>)
case rect(Kind<CGRect>)
case transform(Kind<CGAffineTransform>)
}
extension AnimationType.Kind: Codable {
public func encode(to encoder: Encoder) throws {
var container = encoder.singleValueContainer()
switch self {
case .scalar(let value): try container.encode(value)
case .keyframes(let keyframes): try container.encode(keyframes)
}
}
public init(from decoder: Decoder) throws {
let container = try decoder.singleValueContainer()
if let scalar = try? container.decode(Value.self) {
self = .scalar(scalar)
return
}
if let keyframes = try? container.decode([Keyframe<Value>].self) {
self = .keyframes(keyframes)
return
}
// You should throw error here instead
fatalError("Failed to decode")
}
}
extension AnimationType: Codable {
private enum CodingKeys: CodingKey {
case position
case scale
case rect
case transform
}
public func encode(to encoder: Encoder) throws {
var container = encoder.container(keyedBy: CodingKeys.self)
switch self {
case .position(let kind): try container.encode(kind, forKey: .position)
case .scale(let kind): try container.encode(kind, forKey: .scale)
case .rect(let kind): try container.encode(kind, forKey: .rect)
case .transform(let kind): try container.encode(kind, forKey: .transform)
}
}
public init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
if let position = try? container.decode(Kind<Float>.self, forKey: .position) {
self = .position(position)
return
}
if let scale = try? container.decode(Kind<Float>.self, forKey: .scale) {
self = .scale(scale)
return
}
if let rect = try? container.decode(Kind<CGRect>.self, forKey: .rect) {
self = .rect(rect)
return
}
if let transform = try? container.decode(Kind<CGAffineTransform>.self, forKey: .transform) {
self = .transform(transform)
return
}
// You should throw error here instead
fatalError("Failed to decode")
}
}
Example encoding:
do {
let data = try JSONEncoder().encode(anim1)
if let str = String(data: data, encoding: .utf8) {
print(str)
// Prints: {"position":10}
}
} catch {
print(error)
}
The same sort of thing with anim2
returns {"position":[10]}
.
How to make a Codable class with multiple possible enum types?
You need to make the Status
type generic so that compiler knows you will pass a concrete type that conforms to protocol Status
so it is decodable & encodable.
class ProdEvent<T: Status> : Codable {
let status : T
}
class MyCollection<T: Status> : Codable {
let arr_events : [ProdEvent<T>]
}
Now, you can make ProdEvent
and MyCollection
object of any kind of Status
type as below,
var aEvent: ProdEvent<StatusA>!
var bEvent: ProdEvent<StatusB>!
var aCollection: MyCollection<StatusA>!
var bCollection: MyCollection<StatusB>!
You can check this thread as well on why you can't use protocol as a concrete type.
Related Topics
How to Capture Multiple Arguments Weakly in a Swift Closure
How to Restrict an Enum to Certain Cases of Another Enum
Constant 'Result' Inferred to Have Type (), Which May Be Unexpected
How to Trigger Updateuiview of a Uiviewrepresentable
Phase 1 and Phase 2 Initialization in Swift
Make Nstextfield in Nstablecellview Firstresponder()
Notification Extension Access Core Data
Sidebar Menu for MACos in Swiftui
Maccatalyst App: How to Close a Window Without Terminating The App
Expression Resolves to an Unused Function
How to Switch to Swift 4.0 in Xcode 9.3
Command Failed Due to Signal: Segmentation Fault: 11 While Emitting Ir Sil Function
Send Mail with File Attachment
Swiftui Share Sheet Crashes iPad
How to Disable "Save to Files" in iOS 11
Reading Data from Excel Document in a Swift App
How to Implement a 'Next' Property to a Caseiterable Enum in Swift