Swift 将 JSON 从 [String: Int] 解码为元组或自定义类型

Swift decoding JSON from [String: Int] into tuple or custom type

提问人:kbartlett 提问时间:11/16/2023 更新时间:11/16/2023 访问量:57

问:

我正在编写一个应用程序来计算女神异闻录 5 融合(如果您不熟悉游戏,请考虑口袋妖怪,但使用世界神话生物而不是口袋怪物)。

我将应用程序的数据本地存储在我从 github 中其他人的项目获取的 json 文件中。但是由于这种结构,我在解码对象时遇到了问题:

[
    {
        "name": "Abaddon",
        "inherits": "Curse",
        "item": "Makarakarn",
        "skillCard": true,
        "arcana": "Judgement",
        "level": 74,
        "stats": {
            "strength": 51,
            "magic": 38,
            "endurance": 58,
            "agility": 43,
            "luck": 39
        },
        "elements": {
            "physical": "ab",
            "gun": "ab",
            "fire": "-",
            "ice": "-",
            "electric": "-",
            "wind": "-",
            "psychic": "wk",
            "nuclear": "wk",
            "bless": "-",
            "curse": "ab"
        },
        "skills": {
            "Absorb Phys": 79,
            "Deathbound": 0,
            "Gigantomachia": 80,
            "Makarakarn": 0,
            "Spirit Drain": 0,
            "Survival Trick": 77
        }
    }
]

具体来说,底部的技能不是我希望的格式。有没有办法将它们解码为像这样的元组或自定义结构?(String, Int)

struct PersonaSkill: Codable, Hashable {
    let skill: String
    let level: Int
}

由于我在本地存储了数据,因此我可以手动更改结构,但是有 ~400 个条目需要更新,因此需要相当长的时间并且非常容易出错,所以我宁愿不要这样做。

以下是 Persona 的完整模型:


import Foundation
import SwiftUI

struct Fusion: Codable {
    let sources: [Persona]
    let result: Persona
    let cost: Int
}

struct Persona: Codable {
    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 elements: ElementReactions
    let skills: [PersonaSkill]?
    let personality: PersonalityType?
    let mementosArea: [AreaType]?
    let floor: String?
    let trait: String?
    let rare: Bool?
    let dlc: Bool?
}

struct Stats: Codable {
    let strength: Int
    let magic: Int
    let endurance: Int
    let agility: Int
    let luck: Int
}

struct PersonaSkill: Codable, Hashable {
    let skill: String
    let level: Int
}

struct ElementReactions: Codable {
    let physical: ReactionType
    let gun: ReactionType
    let fire: ReactionType
    let ice: ReactionType
    let electric: ReactionType
    let wind: ReactionType
    let psychic: ReactionType
    let nuclear: ReactionType
    let bless: ReactionType
    let curse: ReactionType
}

enum ElementType: String, Codable {
    case physical, gun, fire, ice, electric, wind, nuclear, psychic, bless, curse
}
enum ReactionType: String, Codable {
    
    case weak = "wk", neutral = "-", resist = "rs", null = "nu", repel = "rp", absorb = "ab"
    
    var description: String {
        switch self {
        case .weak:
            "Weak"
        case .neutral:
            "-"
        case .resist:
            "Resist"
        case .null:
            "Null"
        case .repel:
            "Repel"
        case .absorb:
            "Absorb"
        }
    }
    
    var colorName: Color {
        switch self {
        case .weak:
            Color(.red)
        case .neutral:
            Color(.black)
        case .resist:
            Color(.blue)
        case .null:
            Color(.gray)
        case .repel:
            Color(.yellow)
        case .absorb:
            Color(.green)
        }
    }
}

enum AreaType: String, Codable {
    case Aiyatsbus, Akzeriyyuth, Adyeshach, Chemdah, Daat = "Da'at", Kaitul, Sheriruth, Qimranut
}

enum ArcanaType: String, Codable {
    case chariot = "Chariot"
    case death = "Death"
    case devil = "Devil"
    case emperor = "Emperor"
    case empress = "Empress"
    case faith = "Faith"
    case fool = "Fool"
    case fortune = "Fortune"
    case hangedMan = "Hanged Man"
    case hermit = "Hermit"
    case hierophant = "Hierophant"
    case judgement = "Judgement"
    case justice = "Justice"
    case lovers = "Lovers"
    case magician = "Magician"
    case moon = "Moon"
    case priestess = "Priestess"
    case sun = "Sun"
    case star = "Star"
    case strength = "Strength"
    case temperance = "Temperance"
    case tower = "Tower"
    case world = "World"
    
}

enum PersonalityType: String, Codable {
    case gloomy = "Gloomy"
    case irritable = "Irritable"
    case timid = "Timid"
    case upbeat = "Upbeat"
    case unknown = "Unknown"
}

这是我用来解码 JSON 的代码:

import Foundation
import UIKit

class AppModel: ObservableObject {
    @Published var royal: Bool = false {
        didSet {
            loadData()
        }
    }
    @Published var personae: [Persona]
    @Published var skills: [Skill]
    @Published var items: [Item]
    
    init() {
        self.royal = false
        self.personae = Bundle.main.decode([Persona].self, from: "PersonaTestData.json")
        self.skills = Bundle.main.decode([Skill].self, from: "SkillTestData.json")
        self.items = Bundle.main.decode([Item].self, from: "ItemData.json")
    }
    
    func loadData() {
        if self.royal {
            self.personae = Bundle.main.decode([Persona].self, from: "PersonaDataRoyal.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: "PersonaData.json")
            self.skills = Bundle.main.decode([Skill].self, from: "SkillData.json")
            self.items = Bundle.main.decode([Item].self, from: "ItemData.json")
        }
    }
}

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()
        guard let loaded = try? decoder.decode(T.self, from: data) else {
            fatalError("Failed to decode \(file)")
        }
        return loaded
    }
}

JSON 斯威夫特

评论

0赞 lorem ipsum 11/16/2023
你可以进入任何你想要的东西,但一个模型,就像你所拥有的是最实用的map
0赞 kbartlett 11/16/2023
有没有一种好方法可以只映射技能,同时让其余的技能自动解码?
0赞 lorem ipsum 11/16/2023
从字面上调用“map”在你拥有的数组上

答:

0赞 Sweeper 11/16/2023 #1

如果要避免手动解码 的所有其他属性,可以编写自己的类型来包装 .PersonaSkillsCollection[PersonaSkill]

struct PersonaSkill: Hashable {
    let skill: String
    let level: Int
}

struct SkillsCollection: Codable {
    let contents: [PersonaSkill]
    
    init(from decoder: Decoder) throws {
        let dictionary = try [String: Int](from: decoder)
        contents = dictionary.map { PersonaSkill(skill: $0, level: $1) }
    }
    
    func encode(to encoder: Encoder) throws {
        let dictionary = Dictionary(uniqueKeysWithValues: contents.map { ($0.skill, $0.level) })
        try dictionary.encode(to: encoder)
    }
}

在 中,您将拥有以下类型的属性:PersonaSkillsCollection?

// you would need to do skills?.contents to access the actual array
let skills: SkillsCollection?

请注意,不会保留技能的顺序,因为这是一个 JSON 字典。

评论

0赞 kbartlett 11/17/2023
太棒了,谢谢!我可以稍后处理它们,所以顺序没什么大不了的。