The following code builds and runs, and prints ["key": "value"], as expected:
typealias Dict = [String: String]
do {
let decoder = JSONDecoder()
let string = Data("""
{
"key": "value"
}
""".utf8)
let decoded = try decoder.decode(Dict.self, from: string)
print(decoded)
}
When I change the type of the key as follows:
struct String2: Decodable, Hashable {
let value: String
init(from decoder: Decoder) throws {
let container = try decoder.singleValueContainer()
value = try container.decode(String.self)
}
}
typealias Dict = [String2: String]
do {
let decoder = JSONDecoder()
let string = Data("""
{
"key": "value"
}
""".utf8)
let decoded = try decoder.decode(Dict.self, from: string)
print(decoded)
}
I would expect it to behave similarly. The reality is that the code builds, but when running shows the following error:
Fatal error: Error raised at top level: Swift.DecodingError.typeMismatch(Swift.Array<Any>, Swift.DecodingError.Context(codingPath: [], debugDescription: "Expected to decode Array<Any> but found a dictionary instead.", underlyingError: nil)): file /AppleInternal/BuildRoot/Library/Caches/com.apple.xbs/Sources/swiftlang/swiftlang-1103.8.25.8/swift/stdlib/public/core/ErrorType.swift, line 200
Such code does not even reach the content of the init(from decoder: Decoder) throws function inside the String2 type.
Am I missing something? Any idea why this happens?
Ran into this same issue. I wanted a Dictionary instead of a struct because order doesn't matter. Thus I went with this solution which is a bit clunky but does the job.
enum ToolbarMark: String {
case bold
case italic
case strikethrough
case ul
case ol
case h1
case h2
case h3
case p
case action_item
}
// ...
if let legitString = messageBody["toolbarState"]?.data(using: .utf8) {
do {
// Swift does not support parsing of JSON into dictionaries where keys
// are not strings or integers. Thus we first parse as strings then cast
// appropriately. We are not using structs for toolbar state because
// order doesn't matter and that's not something we want to enforce yet
let jsonObject = try JSONSerialization.jsonObject(with: legitString) as? [String: Bool]
var nextToolbarState: [ToolbarMark:Bool] = [:]
jsonObject?.forEach({ (key: String, value: Bool) in
if let mark = ToolbarMark.init(rawValue: key) {
nextToolbarState[mark] = value
}
})
textEditor.setToolbarState(nextToolbarState)
} catch (let err) {
print(err)
}
}
I come from a JS background so I may be missing much of the context of why I might be wrong but thought I'd share either way.
As of SE-0320: Allow coding of non String / Int keyed Dictionary into a KeyedContainer and Swift 5.6, you should now be able to conform your ToolbarMark enum to CodingKeyRepresentable (benefitting from the default implementation that String- and Int-backed RawRepresentable types get) and be able to decode [ToolbarMark: Bool] directly:
enum ToolbarMark: String, Decodable, CodingKeyRepresentable {
case bold
case italic
case strikethrough
case ul
case ol
case h1
case h2
case h3
case p
case action_item
}
let data = """
{ "bold": true,
"italic": false,
"action_item": true }
""".data(using: .utf8)!
let marks = try JSONDecoder().decode([ToolbarMark: Bool].self, from: data)
print(marks)
// => [ToolbarMark.action_item: true, ToolbarMark.bold: true, ToolbarMark.italic: false]
This also works with assigning explicit raw values to your enum cases, so instead of having
enum ToolbarMark: ... {
...
case action_item
}
you can write
enum ToolbarMark: ... {
...
case actionItem = "action_item"
}