What is wrong with my enum decoding in Swift?

61 Views Asked by At

I just tried this:

let test = "{ \"de\": \"Bisasam\", \"en\": \"Bulbasaur\" }"
let data = test.data(using: .utf8)!
do {
    let result = try JSONDecoder().decode([String: String].self, from: data)
    print("") // breakpoint here works
} catch {
    print("")
}

This works fine, however when I try to do this instead:

enum Lg: String, CaseIterable, Decodable {
    case deutsch = "de"
    case english = "en"
}

let test = "{ \"de\": \"Bisasam\", \"en\": \"Bulbasaur\" }"
let data = test.data(using: .utf8)!
do {
    let result = try JSONDecoder().decode([Lg: String].self, from: data)
    print("")
} catch {
    print("") // breakpoint here
}

With this error:

▿ DecodingError ▿ typeMismatch : 2 elements

  • .0 : Swift.Array ▿ .1 : Context
    • codingPath : 0 elements
    • debugDescription : "Expected to decode Array but found a dictionary instead."
    • underlyingError : nil

What am I doing wrong?

Thanks for your help

1

There are 1 best solutions below

0
On BEST ANSWER

When you use a non-String non-Int type as the dictionary key, Dictionary's Decodable implementation expects an array of key value pairs, i.e.:

["de", "foo", "en": "bar"]

This decodes to [.deutsch: "foo", .english: "bar"].

JSON keys can only be strings after all, and the Decodable implementation doesn't know how to convert a random Decodable value into a string. It could have been made to check for RawRepresentable with RawValue == String, but it wasn't.

One thing that it does check for though, is CodingKeyRepresentable, if you just conform Lg to that, then you can decode a JSON dictionary correctly.

Conforming a type to CodingKeyRepresentable lets you opt in to encoding and decoding Dictionary values keyed by the conforming type to and from a keyed container, rather than encoding and decoding the dictionary as an unkeyed container of alternating key-value pairs.

extension Lg: CodingKeyRepresentable, CodingKey {
    init?<T>(codingKey: T) where T : CodingKey {
        self.init(rawValue: codingKey.stringValue)
    }
    
    var codingKey: CodingKey {
        self
    }
}

Note that I also conformed it to CodingKey to make it easier to write the codingKey property required by CodingKeyRepresentable.