在Swift中将JSON双精度浮点数解码为CGFloat

p5cysglq  于 2023-02-06  发布在  Swift
关注(0)|答案(1)|浏览(124)

编辑:代码工作,问题是与导入的数据集,它没有将空字段分类为数字,而是作为字符串代替。
我的应用程序需要从JSON文件导入类似结构的值,如下所示

[
  {
    "id":          1,
    "string":      "Text String",
    "int":         6,
    "cgfloat":     1.1,
  }
]

进口:

id       = try container.decode(Int.self,        forKey: .id)
string   = try container.decode(String.self,     forKey: .string)
int      = try container.decode(Int.self,        forKey: .int)

我不知道如何读取值1.1,Double、Float、CGFloat、Int和String在这里都不起作用。我是否需要更改数据结构,或者是否有办法在Swift中正确解释该值?
这会使应用程序崩溃:

let cgfloat = try container.decode(CGFloat.self, forKey: . cgfloat)

根据要求的完整代码:

struct Monster: Codable, Comparable {
    
    enum MonsterStatus: String, Identifiable, Codable, Hashable {
        
        var id: Self {
            return self
        }
 
        case Normal = "Normal"
        case Attack = "Attack"
        case Hit    = "Hit"
        case Defend = "Defend"
        case Dead   = "Dead"

    }
    
    enum MonsterType: String, Identifiable, Codable, Hashable {
        
        var id: Self {
            return self
        }
        
        case Undead     = "Undead"
        case Human      = "Human"
        case Dinosaur   = "Dinosaur"
        case Dwarf      = "Dwarf"
        case Elf        = "Elf"
        case Wisp       = "Wisp"
        case Ghost      = "Ghost"
        case Beast      = "Beast"
        case Snake      = "Snake"
        case Giant      = "Giant"
        case Demon      = "Demon"
        case Dragon     = "Dragon"
        case Error      = "Error"
    }
    
    struct ImagesCatalog: Equatable, Codable {
        var Normal: String
        var Attack: String
        var Defend: String
        var Hit: String
        var Dead: String
    }
    
    
    static func < (lhs: Monster, rhs: Monster) -> Bool {
          return lhs.id < rhs.id
      }

      static func == (lhs: Monster, rhs: Monster) -> Bool {
          return lhs.id == rhs.id
      }
    var id:         Int
    let name:       String
    let image:      String          = ""
    var imagePrefix:String          = ""
    var strength:   Int             = 4
    var life:       Int             = 1
    var fleeChance: Int             = 0
    var isBoss:     Bool            = false
    var flying:     Bool            = false
    var mage:       Bool            = false
    var venomous:  Bool             = false
    var giant:      Bool            = false
    var carnivore:  Bool            = false
    var herbivore:  Bool            = false
    var type:       MonsterType
    var location:   HordeGameData.Location
    var isFaceUp:   Bool            = false
    var soundAppear: String         = "skeletonWalk4.mp3"
    var soundHit:    String         = "skeletonHit1.mp3"
    var soundDefend: String         = "skeletonEmerge1.mp3"
    var soundAttack: String         = "skeletonHit4.mp3"
    var soundDead:   String         = "skeletonShatter1.mp3"
    var playSound:   Bool           = true
    let images:      ImagesCatalog
    var imagePic:    String
    var scaleFactor: CGFloat        = 0.5
    var active:      Bool           = false
    var slash:       Bool           = false
    var description: String         = ""
    var status:      MonsterStatus = .Normal
    
    
    func returnColor() -> Color {
        if isFaceUp && isBoss {
            return .red
        } else {
            return .red
        }
    }
    
    func provideID() -> String {
        return String(self.id)
    }
    
    mutating func playSoundToggle() {
        self.playSound.toggle()
    }
    
    mutating func reduceStrengthBy(_ amount: Int) {
        if strength > amount {
            self.strength -= amount
        }
    }
    
    
    mutating func defineImage(status: MonsterStatus, slash: Bool) {
        self.status = status
        switch status {
        case .Normal: imagePic = images.Normal
            if playSound {
                Sound.play(file: self.soundAppear)
            }
        case .Attack: imagePic = images.Attack
            if playSound {
                Sound.play(file: self.soundAttack)
            }
        case .Defend: imagePic = images.Defend
            if playSound {
                Sound.play(file: self.soundDefend)
            }
        case .Hit   : imagePic = images.Hit
            if playSound {
                Sound.play(file: self.soundHit)
            }
        case .Dead  : imagePic = images.Dead
            if playSound {
                Sound.play(file: self.soundDead)
            }
        }
        
        self.slash = slash
    }
    
    mutating func nextImage() {
        switch self.status {
        case .Normal: defineImage(status: .Attack,  slash: false)
        case .Attack: defineImage(status: .Defend,  slash: false)
        case .Defend: defineImage(status: .Hit,     slash: false)
        case .Hit   : defineImage(status: .Dead,    slash: false)
        case .Dead  : defineImage(status: .Normal,  slash: false)
        }
    }
    
    mutating func setID(_ id: Int) {
        self.id = id
    }
    
    mutating func reduceLife() {
        life -= 1
    }
    
    mutating func addLife() {
        life += 1
    }
    
    
    func provideLife() -> String {
        
        var lifeString = ""
        for _ in 0..<life {
            lifeString.append("💜")
        }
        return lifeString
    }

    ///
    private enum CodingKeys: String, CodingKey {
     
        case id
        case name
        case imagePrefix
        case strength
        case life
        case fleeChance
        case isBoss
        case flying
        case mage
        case venomous
        case giant
        case carnivore
        case herbivore
        case type
        case location
        case soundAppear
        case soundHit
        case soundDefend
        case soundAttack
        case soundDead
        case images
        case scaleFactor
        case description
    }
    
    init(from decoder: Decoder) throws {
        let container = try decoder.container(keyedBy: CodingKeys.self)
        id          = try container.decode(Int.self,        forKey: .id)
        name        = try container.decode(String.self,     forKey: .name)
        imagePrefix = try container.decode(String.self,     forKey: .imagePrefix)
        strength    = try container.decode(Int.self,        forKey: .strength)
        life        = try container.decode(Int.self,        forKey: .life)
        fleeChance  = try container.decode(Int.self,        forKey: .fleeChance)
        isBoss      = try container.decode(Bool.self,       forKey: .isBoss)
        flying      = try container.decode(Bool.self,       forKey: .flying)
        mage        = try container.decode(Bool.self,       forKey: .mage)
        venomous    = try container.decode(Bool.self,       forKey: .venomous)
        giant       = try container.decode(Bool.self,       forKey: .giant)
        carnivore   = try container.decode(Bool.self,       forKey: .carnivore)
        herbivore   = try container.decode(Bool.self,       forKey: .herbivore)
        
        
        let monsterTypeValue = try container.decode(String.self, forKey: .type)
        type = MonsterType(rawValue: monsterTypeValue) ?? .Error
        
        let locationValue = try container.decode(String.self, forKey: .location)
        location = HordeGameData.Location(rawValue: locationValue) ?? .Error
    
        soundAppear        = try container.decode(String.self,     forKey: .soundAppear)
        soundHit           = try container.decode(String.self,     forKey: .soundHit)
        soundDefend        = try container.decode(String.self,     forKey: .soundDefend)
        soundAttack        = try container.decode(String.self,     forKey: .soundAttack)
        soundDead          = try container.decode(String.self,     forKey: .soundDead)

        

    images = .init(Normal: imagePrefix + "Normal",
                  Attack: imagePrefix  + "Attack",
                  Defend: imagePrefix  + "Defend",
                  Hit: imagePrefix     + "Hit",
                  Dead: imagePrefix    + "Dead")
        
    imagePic = imagePrefix + "Normal"

    //    let stringScale = try container.decode(CGFloat.self, forKey: .scaleFactor)
    //    print(stringScale)
      // scaleFactor          = stringScale.CGFloatValue() ?? 0.5
        description          = try container.decode(String.self,     forKey: .description)
        print("Monster \(id): \(name) imported")
    }
}

解码功能

class MonsterInventory {
    static let shared = MonsterInventory()
    
    var staticItems: [Monster] = []
    
    private init() {
       self.parseMonsterJson()
    }
    
    private func parseMonsterJson() {

        
        
        guard let monsterJsonFileUrl = Bundle.main.url(forResource: "Monsters", withExtension: "json") else {
            fatalError("Unable to find file")
        }
        
        do {
            let content = try Data(contentsOf: monsterJsonFileUrl)
            let jsonDecoder = JSONDecoder()
            staticItems = try jsonDecoder.decode([Monster].self, from: content)
        } catch let error {
            print(error.localizedDescription)
            staticItems = []
        }
    }
    
    func fetchMonsterWithID(_ id: Int) -> Monster {
        return staticItems.filter{$0.id == id}.first ?? staticItems.filter{$0.id == 10}.first!
    }
    
    
    func fetchMonsterWithName(_ name: String) -> Monster {
        return staticItems.filter{$0.name == name}.first ?? staticItems.filter{$0.id == 10}.first!
    }
    
    
}

下面是JSON的一个摘录:

[
  {
    "id": 1,
    "name": "Skeleton Warrior",
    "strength": 6,
    "life": 1,
    "fleeChance": 0,
    "isBoss": false,
    "flying": false,
    "mage": false,
    "venomous": false,
    "giant": false,
    "carnivore": false,
    "herbivore": false,
    "type": "Undead",
    "location": "Plains",
    "soundAppear": "skeletonWalk4.mp4",
    "soundHit": "skeletonHit1.mp3",
    "soundDefend": "swordSwoosh2.m4a",
    "soundAttack": "swordSwoosh1.m4a",
    "soundDead": "skeletonShatter2.mp3",
    "imagePrefix": "SkeletonWarrior",
    "imageNormal": "SkeletonWarriorNormal",
    "imageAttack": "SkeletonWarriorAttack",
    "imageDefend": "SkeletonWarriorDefend",
    "imageHit": "SkeletonWarriorHit",
    "imageDead": "SkeletonWarriorDead",
    "scaleFactor": 1.1,
    "description": "A weak skeleton fighter."
  },
...
]
p8h8hvxi

p8h8hvxi1#

我不确定为什么要逐个密钥地解码...我认为您应该创建一个struct,除非您真的需要逐个密钥地解码(例如
侧翼提到的评论这个答案)。
如果您确实需要逐个键进行解码,则需要提供有关如何解码JSON的更多详细信息
这是适合JSON的结构体

struct Elm: Codable {
    var id: Int
    var string: String
    var int: Int
    var cgfloat: CGFloat // also can be Double and Float
}

您还可以更改变量名称以遵循camelCase命名。

struct Elm: Codable {
    var id: Int
    var string: String
    var int: Int
    var cgFloat: Double

    private enum CodingKeys : String, CodingKey {
        case id, string, int, cgFloat = "cgfloat"
    }
}

这就是解码对象的方法。

let decoder = JSONDecoder()
let obj = try! decoder.decode([Elm].self, from: json.data(using: .utf8)!)

在本例中,JSON的根是一个数组,因此在这种情况下,我们不使用Elm.self,而是使用[Elm].self解码到数组
这样Swift将JSON KeyMap到匹配的变量名(或者使用编码键,就像我在第二个结构体中演示的那样)
这在Playground进行了测试

    • 更新1**

根据您更新的问题,我现在明白了为什么个别解码,但假设您有问题的关键是scaleFactor,这是唯一的浮点数在样本中,您提供的代码工作没有任何问题。

// [...]

    // uncommented your code
    scaleFactor = try container.decode(CGFloat.self, forKey: .scaleFactor) 
    let _scaleFactor = try container.decode(CGFloat.self, forKey: .scaleFactor) // both work
      
        description          = try container.decode(String.self,     forKey: .description)
        print("Monster \(id): \(name) imported")

现在您可以仔细检查文件的读取是否正确(如果您可以读取文件的其余部分,则可能正确)

相关问题