Hello people I have a little question related with the Encodable protocol in Swift.
I have the following json file:
let magicJson = """
{
"value": [
{
"scheduleId": "[email protected]",
"somethingEventMoreMagical": "000220000"
}
]
}
""".data(using: .utf8)!
For decoding I tried to avoid having to create two objects that both go with Decodable, and the first one has an array of the second object. I would like to flatten that object into something like this:
struct MagicalStruct: Decodable {
private enum CodingKeys: String, CodingKey {
case value
}
private enum ScheduleCodingKeys: String, CodingKey {
case roomEmail = "scheduleId"
}
let roomEmail: String
init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
let magicContainer = try container.nestedContainer(keyedBy: ScheduleCodingKeys.self, forKey: .value)
roomEmail = try magicContainer.decode(String.self, forKey: ScheduleCodingKeys.roomEmail)
}
}
However when I try the following code: JSONDecoder().decode(MagicalStruct.self, magicJson)
I get that it expects an array but gets a dictionary. On the other hand when I go with JSONDecoder().decode([MagicalStruct].self, magicJson)
, I get that it receives an array but expects a dictionary.
Does anyone know why this is happening ?
First when you're decoding your struct using:
JSONDecoder().decode(MagicalStruct.self, magicJson)
you're trying to extract a single object: let roomEmail: String
.
However, your input JSON contains an array of objects with emails. Which means your code:
init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
let magicContainer = try container.nestedContainer(keyedBy: ScheduleCodingKeys.self, forKey: .value)
roomEmail = try magicContainer.decode(String.self, forKey: ScheduleCodingKeys.roomEmail)
}
tries to decode a single email and there is a collection instead (in your example with one element - that's why it may be confusing).
Also your error Expected to decode Dictionary<String, Any> but found an array instead
is on the line:
let magicContainer = try container.nestedContainer(keyedBy: ScheduleCodingKeys.self, forKey: .value)
You need to decode an array:
var magicContainer = try container.nestedUnkeyedContainer(forKey: .value)
But then you have an array of objects with scheduleId
and somethingEventMoreMagical
keys. How do you want to assign all the values to your one let roomEmail: String
variable?
You can decode a dictionary instead:
let result = try JSONDecoder().decode([String: [MagicalStruct]].self, from: magicJson)
print(result["value"]!) // prints [MagicalStruct(roomEmail: "[email protected]")]
And you can simplify your MagicalStruct
:
struct MagicalStruct: Decodable {
enum CodingKeys: String, CodingKey {
case roomEmail = "scheduleId"
}
let roomEmail: String
}