Декодер JSON — ошибка декодирования со строкой

При создании iOS-приложения DotA2 в Swift 4 с использованием протокола Decodable я столкнулся с этой ошибкой.

keyNotFound(DotaPal.MatchPlayerDetail.CodingKeys.personaname, Swift.DecodingError.Context(codingPath: [DotaPal.MatchDetail.CodingKeys.players, Foundation.(_JSONKey in _12768CA107A31EF2DCE034FD75B541C9)(stringValue: "Index 0", intValue: Optional(0))] , debugDescription: "Нет значения, связанного с ключевым именем пользователя (\"personaname\").", baseError: nil))

Похоже, у него проблема с декодированием «имя пользователя», хотя я понятия не имею, почему. Я использовал декодер с более сложным json в прошлом, но, похоже, не могу найти решение, посмотрев на него некоторое время:/.

Вот структуры, задействованные во всей полноте, а также ссылка на json, который пытается быть декодирован. JSON пытается быть декодированным

import Foundation

struct MatchDetail {
    var match_id: Int?
    var barracks_status_dire: Int?
    var barracks_status_radiant: Int?
    var cluster: Int?
    var dire_score: Int?
    var duration: Int?
    var first_blood_time: Int?
    var game_mode: Int?
    var radiant_score: Int?
    var radiant_win: Int?
    var skill: Int?
    var start_time: Int?
    var tower_status_dire: Int?
    var tower_status_radiant: Int?
    var players: [MatchPlayerDetail]?
}

extension MatchDetail: Decodable {
    enum CodingKeys: String, CodingKey {
        case match_id
        case barracks_status_dire
        case barracks_status_radiant
        case cluster
        case dire_score
        case duration
        case first_blood_time
        case game_mode
        case radiant_score
        case radiant_win
        case skill
        case start_time
        case tower_status_dire
        case tower_status_radiant
        case players
    }

    init(from decoder: Decoder) throws {
        let values = try decoder.container(keyedBy: CodingKeys.self)
        match_id = try values.decode(Int?.self, forKey: .match_id)
        barracks_status_dire = try values.decode(Int?.self, forKey: .barracks_status_dire)
        barracks_status_radiant = try values.decode(Int?.self, forKey: .barracks_status_radiant)
        cluster = try values.decode(Int?.self, forKey: .cluster)
        dire_score = try values.decode(Int?.self, forKey: .dire_score)
        duration = try values.decode(Int?.self, forKey: .duration)
        first_blood_time = try values.decode(Int?.self, forKey: .first_blood_time)
        game_mode = try values.decode(Int?.self, forKey: .game_mode)
        radiant_score = try values.decode(Int?.self, forKey: .radiant_score)
        radiant_win = try values.decode(Int?.self, forKey: .radiant_win)
        skill = try values.decode(Int?.self, forKey: .skill)
        start_time = try values.decode(Int?.self, forKey: .start_time)
        tower_status_dire = try values.decode(Int?.self, forKey: .tower_status_dire)
        tower_status_radiant = try values.decode(Int?.self, forKey: .tower_status_radiant)
        players = try values.decode([MatchPlayerDetail]?.self, forKey: .players)
    }
}

struct MatchPlayerDetail {
    var match_id: Int?
    var player_slot: Int?
    var ability_upgrades_arr: [Int?]
    var account_id: Int?
    var assists: Int?
    var backpack_0: Int?
    var backpack_1: Int?
    var backpack_2: Int?
    var deaths: Int?
    var denies: Int?
    var gold: Int?
    var gold_per_min: Int?
    var gold_spent: Int?
    var hero_damage: Int?
    var hero_healing: Int?
    var hero_id: Int?
    var item_0: Int?
    var item_1: Int?
    var item_2: Int?
    var item_3: Int?
    var item_4: Int?
    var item_5: Int?
    var kills: Int?
    var last_hits: Int?
    var leaver_status: Int?
    var level: Int?
    var xp_per_min: Int?
    var personaname: String?
    var radiant_win: Bool?
    var isRadiant: Bool?
    var win: Int?
    var lose: Int?
    var total_gold: Int?
    var total_xp: Int?
    var kda: Int?
    var abandons: Int?
}

extension MatchPlayerDetail: Decodable {
    enum CodingKeys: String, CodingKey {
        case match_id
        case player_slot
        case ability_upgrades_arr
        case account_id
        case assists
        case backpack_0
        case backpack_1
        case backpack_2
        case deaths
        case denies
        case gold
        case gold_per_min
        case gold_spent
        case hero_damage
        case hero_healing
        case hero_id
        case item_0
        case item_1
        case item_2
        case item_3
        case item_4
        case item_5
        case kills
        case last_hits
        case leaver_status
        case level
        case xp_per_min
        case personaname
        case radiant_win
        case isRadiant
        case win
        case lose
        case total_gold
        case total_xp
        case kda
        case abandons
    }

    init(from decoder: Decoder) throws {
        let values = try decoder.container(keyedBy: CodingKeys.self)
        match_id = try values.decode(Int?.self, forKey: .match_id)
        player_slot = try values.decode(Int?.self, forKey: .player_slot)
        ability_upgrades_arr = try values.decode([Int?].self, forKey: .ability_upgrades_arr)
        assists = try values.decode(Int?.self, forKey: .assists)
        backpack_0 = try values.decode(Int?.self, forKey: .backpack_0)
        backpack_1 = try values.decode(Int?.self, forKey: .backpack_1)
        backpack_2 = try values.decode(Int?.self, forKey: .backpack_2)
        deaths = try values.decode(Int?.self, forKey: .deaths)
        denies = try values.decode(Int?.self, forKey: .denies)
        gold = try values.decode(Int?.self, forKey: .gold)
        gold_per_min = try values.decode(Int?.self, forKey: .gold_per_min)
        gold_spent = try values.decode(Int?.self, forKey: .gold_spent)
        hero_damage = try values.decode(Int?.self, forKey: .hero_damage)
        hero_healing = try values.decode(Int?.self, forKey: .hero_healing)
        hero_id = try values.decode(Int?.self, forKey: .hero_id)
        item_0 = try values.decode(Int?.self, forKey: .item_0)
        item_1 = try values.decode(Int?.self, forKey: .item_1)
        item_2 = try values.decode(Int?.self, forKey: .item_2)
        item_3 = try values.decode(Int?.self, forKey: .item_3)
        item_4 = try values.decode(Int?.self, forKey: .item_4)
        item_5 = try values.decode(Int?.self, forKey: .item_5)
        kills = try values.decode(Int?.self, forKey: .kills)
        last_hits = try values.decode(Int?.self, forKey: .last_hits)
        leaver_status = try values.decode(Int?.self, forKey: .leaver_status)
        level = try values.decode(Int?.self, forKey: .level)
        xp_per_min = try values.decode(Int?.self, forKey: .xp_per_min)
        personaname = try values.decode(String?.self, forKey: .personaname)
        radiant_win = try values.decode(Bool?.self, forKey: .radiant_win)
        isRadiant = try values.decode(Bool?.self, forKey: .isRadiant)
        win = try values.decode(Int?.self, forKey: .win)
        lose = try values.decode(Int?.self, forKey: .lose)
        total_gold = try values.decode(Int?.self, forKey: .total_gold)
        total_xp = try values.decode(Int?.self, forKey: .total_xp)
        kda = try values.decode(Int?.self, forKey: .kda)
        abandons = try values.decode(Int?.self, forKey: .abandons)
    }
}

person Rykuno    schedule 10.11.2017    source источник
comment
ОМГ, столько вопросительных знаков. Почему вы пишете пользовательские инициализаторы, хотя вы декодируете значения 1: 1 (если я что-то не упустил)?   -  person vadian    schedule 11.11.2017
comment
PS: И какова цель декодирования в необязательные типы, такие как Int?.self? Ни в одном из видео WWDC это не упоминается/предполагается.   -  person vadian    schedule 11.11.2017
comment
Спасибо за комментарий, я перечитаю декодер. Пользовательская инициализация существует, поскольку json не полностью соответствует структуре.   -  person Rykuno    schedule 11.11.2017


Ответы (1)


Это потому, что у вашего третьего игрока (индекс 2) нет атрибута personaname. Сказав это, у вас было много ненужного кода. Не все в модели JSON должно сопоставляться с вашим кодом. Если вам не нужен атрибут в модели Swift, просто не определяйте его.

Вот более короткая версия, в которой все атрибуты определены как необязательные, кроме account_id и personaname:

struct MatchDetail: Decodable {
    var match_id: Int
    var barracks_status_dire: Int
    var barracks_status_radiant: Int
    var cluster: Int
    var dire_score: Int
    var duration: Int
    var first_blood_time: Int
    var game_mode: Int
    var radiant_score: Int
    var radiant_win: Int
    var skill: Int
    var start_time: Int
    var tower_status_dire: Int
    var tower_status_radiant: Int
    var players: [MatchPlayerDetail]
}

struct MatchPlayerDetail: Decodable {
    var match_id: Int
    var player_slot: Int
    var ability_upgrades_arr: [Int]
    var account_id: Int?
    var assists: Int
    var backpack_0: Int
    var backpack_1: Int
    var backpack_2: Int
    var deaths: Int
    var denies: Int
    var gold: Int
    var gold_per_min: Int
    var gold_spent: Int
    var hero_damage: Int
    var hero_healing: Int
    var hero_id: Int
    var item_0: Int
    var item_1: Int
    var item_2: Int
    var item_3: Int
    var item_4: Int
    var item_5: Int
    var kills: Int
    var last_hits: Int
    var leaver_status: Int
    var level: Int
    var xp_per_min: Int
    var personaname: String?
    var radiant_win: Bool
    var isRadiant: Bool
    var win: Int
    var lose: Int
    var total_gold: Int
    var total_xp: Int
    var kda: Int
    var abandons: Int
}

Ваше время лучше потратить на просмотр документации, чтобы увидеть, какой ключ является необязательным, а какой нет. Swift может синтезировать для вас декодирование JSON.

person Code Different    schedule 11.11.2017