-1

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?

ra9r
  • 4,528
  • 4
  • 42
  • 52
  • There is `let school: MagicSchool` & `enum DnDMagicSchool`. Are they supposed to be the same? – Larme Aug 28 '22 at 13:50
  • And there are double cases in the enum and the protocol is misspelled, please make an effort to clean up your code so that it compiles. – Joakim Danielson Aug 28 '22 at 13:53
  • There is also twice the same value in the enum. Could you clean everything and provide compiling code? Because then, it's unclear what's the real error you get (and share it too)? – Larme Aug 28 '22 at 13:56
  • @Larme I've corrected the typos ... thanks for pointing it out. – ra9r Aug 29 '22 at 11:53

1 Answers1

1

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
    }
}
burnsi
  • 6,194
  • 13
  • 17
  • 27