JSON decoding failing for only one field

I have JSON data, where the structure looks like this:

{
        "name": "Abaddon",
        "inherits": "Curse",
        "item": "Megaton Raid Belt",
        "itemr": "God's Hand Belt",
        "level": 75,
        "arcana": "Judgement",
        "elems": {
            "physical": "ab",
            "gun": "ab",
            "fire": "-",
            "ice": "-",
            "electric": "-",
            "wind": "-",
            "psychic": "-",
            "nuclear": "-",
            "bless": "rs",
            "curse": "ab"
        },
        "skills": {
            "Mabufudyne": 0,
            "Megaton Raid": 0,
            "Enduring Soul": 0,
            "Flash Bomb": 78,
            "Ailment Boost": 79,
            "Absorb Phys": 80,
            "Gigantomachia": 81
        },
        "stats": {
            "strength": 51,
            "magic": 42,
            "endurance": 58,
            "agility": 38,
            "luck": 43
        },
        "trait": "Mouth of Savoring",
        "area": "Da'at",
        "floor": "All"
    }

I'm trying to decode it into this model:

struct Persona: Codable, Identifiable {
    var id = UUID()
    
    let name: String
    let special: Bool?
    let inherits: String?
    let item: String
    let itemR: String
    let skillCard: Bool?
    let arcana: ArcanaType
    let level: Int
    let stats: Stats
    let elems: ElementReactions
    let skills: SkillsCollection?
    let personality: PersonalityType?
    let mementosArea: [AreaType]?
    let floor: String?
    let trait: String?
    let rare: Bool?
    let dlc: Bool?

    
    enum CodingKeys: String, CodingKey {
        case name, special, inherits, item, itemR = "itemr", skillCard, arcana, level, stats, elems, skills, personality, mementosArea, floor, trait, rare, dlc
    }
}

However, I'm getting this error:

Key 'CodingKeys(stringValue: "itemr", intValue: nil)' not found: No value associated with key CodingKeys(stringValue: "itemr", intValue: nil) ("itemr").

I'm sure I'm missing something simple, but I can't figure out why itemr isn't decoding, and where the "intValue: nil" is coming from in the error. All the other fields decode perfectly.

Here's the code I'm using to decode:

extension Bundle {
    func decode<T: Decodable>(_ type: T.Type, from file: String) -> T {
        guard let url = self.url(forResource: file, withExtension: nil) else {
            fatalError("Failed to locate \(file)")
        }
        
        guard let data = try? Data(contentsOf: url) else {
            fatalError("Failed to load \(file)")
        }
        
        let decoder = JSONDecoder()
        do {
            let loaded = try decoder.decode(T.self, from: data)
            return loaded
        } catch DecodingError.dataCorrupted(let context) {
            print(context)
        } catch DecodingError.keyNotFound(let key, let context) {
            print("Key '\(key)' not found:", context.debugDescription)
            print("codingPath:", context.codingPath)
        } catch DecodingError.valueNotFound(let value, let context) {
            print("Value '\(value)' not found:", context.debugDescription)
            print("codingPath:", context.codingPath)
        } catch DecodingError.typeMismatch(let type, let context) {
            print("Type '\(type)' mismatch:", context.debugDescription)
            print("codingPath:", context.codingPath)
        } catch {
            print("error: ", error)
        }
        fatalError("Failed to decode \(file)")
    }
}

Hi,

I'm thinking that the error you're facing it's because of missing value itemr on JSON, so I would make it optional to see what happens.
let itemR: String?

If the error changes to Type x mismatch: maybe your JSON is sending more than one type for itemr.

Coding keys have a string representation and may optionally have an int representation. Since this key has no int representation, it says "intValue: nil". It's not related to the decoding error.

Is there a particular reason you use ‘itemR’ and not just ‘itemr’?

I'd recommend you to provide a short standalone snippet that fails for you for us to see. I tried to run your example to see the failure but it is working for me fine so I'm doing something different. When distilling your sample get rid of all unnecessary parts (e.g. initialise from a hardcoded string rather than from a resource. In fact if you continue the "distillation" process by removing things (perhaps down a few lines of code and a couple of lines in JSON) you'd see what's triggering the failure for you.

I had it as itemR: String? before (and that's what it ultimately needs to be because I have another set of data that doesn't have that key), but when I had it as an optional it didn't give an error - it just didn't decode anything into that field, so it was always nil.

Just because "itemr" is hard to read and visually distinguish from "item". I'll use "itemr" if I have to, though.

I figured it out - thanks for your help!

I had this code in the model:

@Published var royal: Bool = false {
        didSet {
            loadData()
        }
    }
    @Published var personae: [Persona]
    @Published var skills: [Skill]
    @Published var items: [Item]
    @Published var sortType: SortType = .name
    @Published var ascendingSort: Bool = true
    
    init() {
        self.personae = Bundle.main.decode([Persona].self, from: "PersonaDataRoyal.json")
        self.skills = Bundle.main.decode([Skill].self, from: "SkillData.json")
        self.items = Bundle.main.decode([Item].self, from: "ItemData.json")
    }
    
    func loadData() {
        if self.royal {
            self.personae = Bundle.main.decode([Persona].self, from: "PersonaData.json")
            self.skills = Bundle.main.decode([Skill].self, from: "SkillDataRoyal.json")
            self.items = Bundle.main.decode([Item].self, from: "ItemDataRoyal.json")
        } else {
            self.personae = Bundle.main.decode([Persona].self, from: "PersonaDataRoyal.json")
            self.skills = Bundle.main.decode([Skill].self, from: "SkillData.json")
            self.items = Bundle.main.decode([Item].self, from: "ItemData.json")
        }
    }

The "PersonaDataRoyal" file is the one with itemr in the JSON - I changed the first one in the loadData function instead of the one in init, as I intended.

1 Like