简体   繁体   中英

Decoding an Enum in Swift with Associated Values

I am trying to decode an Enum which consists of associated values. I am trying the following but it keeps throwing exceptions.

let jsonString = """
    {
        "route": "petDetails"
    }
"""

let jsonData = jsonString.data(using: .utf8)

struct Post: Decodable {
    let route: Route
}

enum Route: Decodable, Equatable {
    
    case petDetails(String)

    init?(rawValue: String) {
        switch rawValue {
            case "petDetails":
                self = .petDetails("")
            default:
                return nil
        }
    }
    
    private enum CodingKeys: String, CodingKey {
        case petDetails
    }
    
    init(from decoder: Decoder) throws {
        let container = try decoder.container(keyedBy: CodingKeys.self)
        if let value = try? container.decode(String.self, forKey: .petDetails) {
            self = .petDetails(value)
        } else {
            throw DecodingError.dataCorrupted(DecodingError.Context(codingPath: container.codingPath, debugDescription: "Data doesn't match"))
        }
    }
}


try! JSONDecoder().decode(Post.self, from: jsonData!)

I get the following error:

Fatal error: 'try!' expression unexpectedly raised an error: Swift.DecodingError.typeMismatch(Swift.Dictionary<Swift.String, Any>, Swift.DecodingError.Context(codingPath: [CodingKeys(stringValue: "route", intValue: nil)], debugDescription: "Expected to decode Dictionary<String, Any> but found a string/data instead.", underlyingError: nil))

Any ideas what I am missing?

Suppose we have an enum with an associated value:

enum Route: Codable, Equatable {
    case petDetails(name: String)
    case petListing(count: Int)
    ...

The JSON that represents each Route should contain two values:

  • a string that tells us the type of the route ( petDetails vs petListing ), and
  • the associated value (the name or the count).

Our coding keys will be

private enum CodingKeys: String, CodingKey {
    case type
    case associatedValue
}

and the decoding function will be

init(from decoder: Decoder) throws {
    let container = try decoder.container(keyedBy: CodingKeys.self)
    let type = try container.decode(String.self, forKey: .type)
    switch type {
        case "petDetails":
            // The associated value is a String
            let name = try container.decode(String.self, forKey: .associatedValue)
            self = .petDetails(name: name)
        case "petListing":
            // The associated value is an Int
            let count = try container.decode(Int.self, forKey: .associatedValue)
            self = .petListing(count: count)
        default:
            throw DecodingError.dataCorruptedError(forKey: .type, in: container, debugDescription: "Invalid type")
    }
}

We first decode the type, and that tells us what kind of data the associated value will be.


The JSON for a Post would look like

{
    "route": {
        "type": "petDetails",
        "associatedValue": "Rex"
    }
}

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM