You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
[SR-7788] Enum with String or Int RawRepresentable not encoded / decoded properly #3690
When using an enum value that is RawRepresentable with String or Int, the json encoded is an an array of alternating keys and values, Instead of a Dictionary.
reproduced in Swift 4.1, (not tested in Swift 4.2)
let json = "{\"key\": \"value\"}"
enum Key: String, Codable {
case key
}
let jsonData = Data(json.utf8)
let dataToEncode = [Key.key: "value"]
do {
let decoded = try JSONDecoder().decode([Key: String].self, from: jsonData)
print(decoded)
let encoded = try JSONEncoder().encode(dataToEncode)
print(String(data: encoded, encoding: .utf8)!)
} catch {
print(error)
}
expected
Encode and Decode a Dictionary with the enum value rawValue as the key.
actual
gives the following errors for the decoding part
typeMismatch(Swift.Array<Any>, Swift.DecodingError.Context(codingPath: [], debugDescription: "Expected to decode Array<Any> but found a dictionary instead.", underlyingError: nil))
Thanks Zarghol (JIRA User)! Your code inspired me so I wrote this which suits my needs perfectly. code found here
Update: The gist has been modified to use dynamicMemberLookup to transparently access any properties of the underlying Dictionary and it also modified to be able to wrap any Dictionary where the Key is a RawRepresentable. It is now nearly a drop in replacement for a Dictionary in the case of parsing JSON to Dictionary that has string representable enums as dictionary key.
No problem 😉 just a little problem : in you init, when the enum value is not decoded, I think you should throw an error, not just continue silently. Else the result is not really conformed to the input..
Good point! The code is written in the "app layer" and I'm thinking that the network client (which is running this code) shall survive if the backend suddenly sends a JSON containing a newly defined key/case. It's a bit of a project implementation requirement but you do have a valid point in the generic case.
@jarrodldavis Your solution works really good, thanks for this. I had to do a variant to support optional dictionaries too but other than that this is the cleanest solution yet.
I realized that Key does not have to conform to Codable but should conform to CodingKey if we want to let Dictionary be en/decoded as expected. (Code)
(In other words, implementing this in the standard library will break the API.)
Note that this not only happens with Enums, but with other integer primitives, such as UInt8 and Int32, which otherwise act as Int for Codable. This is annoying as I need to implement Codable by hand for each of those structs or use the propertyWrapper dictionary instead...
This was addressed by SE-0320 (CodingKeyRepresentable, Swift 5.6), which cites this very report as its motivation. Conforming the key type is enough — RawRepresentable types with a String/Int raw value get the default implementation, so no custom wrapper is needed anymore:
enumKey:String,Codable,CodingKeyRepresentable{case key
}tryJSONEncoder().encode([Key.key:"value"])
// {"key":"value"} — and it round-trips back through JSONDecoder
As @YOCKOW noted earlier in the thread, changing the unconditional behavior in the stdlib would be source- and format-breaking, which is why the opt-in conformance is the supported answer rather than a fix to Dictionary's Encodable implementation (whose documented contract is: keyed container for String/Int/CodingKeyRepresentable keys, alternating key/value pairs otherwise).
I believe this can be closed as resolved by SE-0320. Same for the swift-foundation copy of this report, swiftlang/swift-foundation#1742.
Activity
swift-ci commentedon May 27, 2018
Comment by Clément Nonn (JIRA)
The code leading to this behavior is located here : apple/swift/blob/d2085d8b0ed69e40a10e555669bb6cc9b450d0b3/stdlib/public/core/Codable.swift.gyb#L1967
itaiferber commentedon May 28, 2018
@swift-ci create
swift-ci commentedon Apr 26, 2019
Comment by Mattias Persson (JIRA)
This issue still exists in Swift 5. For me it's a pretty big problem.
swift-ci commentedon Sep 13, 2019
Comment by Mattias Persson (JIRA)
@itaiferber Are there any workarounds for this, except from the obvious "stop using enums as dictionary keys"?
swift-ci commentedon Sep 16, 2019
Comment by Clément Nonn (JIRA)
you can write you own implementation of conformance to Decodable / Encodable using a new type like `struct EnumCodableDictionary<Key, Value>`.
This sample shows an array, but you can do the same for a dictionary.
swift-ci commentedon Sep 17, 2019
Comment by Mattias Persson (JIRA)
Thanks Zarghol (JIRA User)! Your code inspired me so I wrote this which suits my needs perfectly.
code found here
Update: The gist has been modified to use dynamicMemberLookup to transparently access any properties of the underlying Dictionary and it also modified to be able to wrap any Dictionary where the Key is a RawRepresentable. It is now nearly a drop in replacement for a Dictionary in the case of parsing JSON to Dictionary that has string representable enums as dictionary key.
swift-ci commentedon Sep 17, 2019
Comment by Clément Nonn (JIRA)
No problem 😉 just a little problem : in you init, when the enum value is not decoded, I think you should throw an error, not just continue silently. Else the result is not really conformed to the input..
swift-ci commentedon Sep 17, 2019
Comment by Mattias Persson (JIRA)
Good point! The code is written in the "app layer" and I'm thinking that the network client (which is running this code) shall survive if the backend suddenly sends a JSON containing a newly defined key/case. It's a bit of a project implementation requirement but you do have a valid point in the generic case.
jarrodldavis commentedon Sep 26, 2019
Hey Zarghol (JIRA User) and snoozemoose (JIRA User),
I've come up with an alternative solution using the new Swift 5.1
@propertyWrapper.This allows you to use the dictionary more directly without having to manually deal with forwarding function calls and member lookups.
swift-ci commentedon Oct 2, 2019
Comment by Mattias Persson (JIRA)
@jarrodldavis Your solution works really good, thanks for this. I had to do a variant to support optional dictionaries too but other than that this is the cleanest solution yet.
YOCKOW commentedon Jun 16, 2020
This should be also applicable to
LosslessStringConvertible...YOCKOW commentedon Jun 25, 2020
Yet another workaround for this issue: https://github.com/YOCKOW/SwiftCodableDictionary
I realized that
Keydoes not have to conform toCodablebut should conform toCodingKeyif we want to letDictionarybe en/decoded as expected. (Code)(In other words, implementing this in the standard library will break the API.)
swift-ci commentedon Aug 24, 2020
Comment by Pheki (JIRA)
Note that this not only happens with Enums, but with other integer primitives, such as UInt8 and Int32, which otherwise act as Int for Codable. This is annoying as I need to implement Codable by hand for each of those structs or use the propertyWrapper dictionary instead...
Detect enums map keys and replace them with a plain String
Detect enums map keys and replace them with a plain String
maxches99 commentedon Aug 10, 2026
This was addressed by SE-0320 (
CodingKeyRepresentable, Swift 5.6), which cites this very report as its motivation. Conforming the key type is enough —RawRepresentabletypes with aString/Intraw value get the default implementation, so no custom wrapper is needed anymore:As @YOCKOW noted earlier in the thread, changing the unconditional behavior in the stdlib would be source- and format-breaking, which is why the opt-in conformance is the supported answer rather than a fix to
Dictionary'sEncodableimplementation (whose documented contract is: keyed container forString/Int/CodingKeyRepresentablekeys, alternating key/value pairs otherwise).I believe this can be closed as resolved by SE-0320. Same for the swift-foundation copy of this report, swiftlang/swift-foundation#1742.