blog
blog copied to clipboard
How to not encode with Enum key in Swift
If you use enum case as key in Dictionary, JSONEncoder will encode it as Array. For example
enum Vehicle: String, Codable {
case car
case truck
}
struct Container: Codable {
var map: [Vehicle: String]
}
struct Container2: Codable {
var map: [String: String]
}
let container = Container(map: [
.car: "Car 1"
])
let container2 = Container2(map: [
"car": "Car 1"
])
let data = try! JSONEncoder().encode(container)
print(String(data: data, encoding: .utf8)) // {"map":["car","Car 1"]}
let data2 = try! JSONEncoder().encode(container2)
print(String(data: data2, encoding: .utf8)) // {"map":{"car":"Car 1"}}
The reason is how JSONEncoder checks for type https://github.com/apple/swift/blob/d2085d8b0e/stdlib/public/core/Codable.swift.gyb#L1967
If the dictionary uses
String
orInt
keys, the contents are encoded in a keyed container. Otherwise, the contents are encoded as alternating key-value pairs in an unkeyed container.
extension Dictionary : Encodable where Key : Encodable, Value : Encodable {
/// Encodes the contents of this dictionary into the given encoder.
///
/// If the dictionary uses `String` or `Int` keys, the contents are encoded
/// in a keyed container. Otherwise, the contents are encoded as alternating
/// key-value pairs in an unkeyed container.
///
/// This function throws an error if any values are invalid for the given
/// encoder's format.
///
/// - Parameter encoder: The encoder to write data to.
@inlinable // FIXME(sil-serialize-all)
public func encode(to encoder: Encoder) throws {
if Key.self == String.self {
// Since the keys are already Strings, we can use them as keys directly.
var container = encoder.container(keyedBy: _DictionaryCodingKey.self)
for (key, value) in self {
let codingKey = _DictionaryCodingKey(stringValue: key as! String)!
try container.encode(value, forKey: codingKey)
}
} else if Key.self == Int.self {
// Since the keys are already Ints, we can use them as keys directly.
var container = encoder.container(keyedBy: _DictionaryCodingKey.self)
for (key, value) in self {
let codingKey = _DictionaryCodingKey(intValue: key as! Int)!
try container.encode(value, forKey: codingKey)
}
} else {
// Keys are Encodable but not Strings or Ints, so we cannot arbitrarily
// convert to keys. We can encode as an array of alternating key-value
// pairs, though.
var container = encoder.unkeyedContainer()
for (key, value) in self {
try container.encode(key)
try container.encode(value)
}
}
}
}
Read more
- https://forums.swift.org/t/json-encoding-decoding-weird-encoding-of-dictionary-with-enum-values/12995/2