I have a JSON object that looks something like this:
{
"name": "Acid Arrow",
"school": {
"name": "Evocation",
"url": "http://www.dnd5eapi.co/api/magic-schools/5"
}
}
that I would like to model in Swift as the following:
struct Spell: Decodeable {
let name: String
let school: MagicSchool
}
enum MagicSchool: String {
case abjuration = "Abjuration"
case abjuration = "Abjuration"
case conjuration = "Conjuration"
case divination = "Divination"
case enchantment = "Enchantment"
case evocation = "Evocation"
case illusion = "Illusion"
case necromancy = "Necromancy"
case transmutation = "Transmutation"
}
The only ways I can find to reduce the JSON school
dictionary down to a single enumeration value is to implement the entire Decodeable
by providing a custom init(from decoder: Decoder)
initializer that would look something like this:
extension Spell: Decodeable {
init(from decoder: Decoder) {
let values = try decoder.container(keyedBy: CodingKeys.self)
// manually map to the spell name
name = try values.decode(String.self, forKey: .name)
// manually decode **school** into a dictionary
let jsonSchool = try values.decode(Dictionary<String,String>.self, forKey: .school)
// extract the "name" property from the dict and assign it as `MagicSchool` enum
school = MagicSchool(rawValue: jsonSchool["name"])
}
}
But it doesn't like it because of a type conflict on the key type for Spell.school
Am I trying to do this the wrong way? Is there a simpler way to transform a complex type into a basic type or to specify a path in the mapping?
CodePudding user response:
Using init(from decoder: Decoder) throws {
initializer is the appropriate way of dealing with this scenario.
There are probably different ways of dealing with the school
entity. I prefer decoding it into a struct to get type safety.
And as pointed out in the comments the code you provided is full of typos. After cleaning those up it was just a case of an optional beeing assigned to a non optional field.
struct Spell: Decodable {
let name: String
let school: MagicSchool
}
enum MagicSchool: String {
case abjuration = "Abjuration"
case conjuration = "Conjuration"
case divination = "Divination"
case enchantment = "Enchantment"
case evocation = "Evocation"
case illusion = "Illusion"
case necromancy = "Necromancy"
case transmutation = "Transmutation"
}
extension Spell {
struct InternalSchool: Decodable{
let name: String
let url: String
}
enum CodingKeys: CodingKey{
case name, school
}
init(from decoder: Decoder) throws {
let values = try decoder.container(keyedBy: CodingKeys.self)
// manually map to the spell name
name = try values.decode(String.self, forKey: .name)
// manually decode **school** into the custom type
let school = try values.decode(InternalSchool.self, forKey: .school)
// check if you can create an enum from the given string and throw appropriate error
guard let schoolEnum = MagicSchool(rawValue: school.name) else{
throw DecodingError.dataCorrupted(.init(codingPath: [CodingKeys.school], debugDescription: "school has unknown value"))
}
// assign enum
self.school = schoolEnum
}
}