You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Swift解析RetroAchievements API JSON:如何获取多类型属性原始值?

解决RetroAchievements API混合类型属性的直接访问问题

问题场景

在使用RetroAchievements API构建Swift Package时,API返回的JSON中部分数值属性(如NumAchieved、NumAchievedHardcore)存在混合类型:有时是Int,有时是String。通过Quicktype生成的模型用Achieved枚举包装了这些值,导致访问属性时得到的是string("15")或integer(0)这类枚举包装结果,无法直接获取原始的Int/String值。

解决方案

方案1:给枚举添加计算属性(保留原始类型信息)

修改Achieved枚举,新增计算属性直接提取Int或String值,同时保留原始的枚举类型支持:

enum Achieved: Codable {
    case integer(Int)
    case string(String)

    // 获取Int值:字符串类型会尝试转换为Int,转换失败返回nil
    var intValue: Int? {
        switch self {
        case .integer(let value):
            return value
        case .string(let str):
            return Int(str)
        }
    }

    // 获取String值:Int类型会转换为字符串
    var stringValue: String {
        switch self {
        case .integer(let value):
            return String(value)
        case .string(let str):
            return str
        }
    }

    // 原有解码、编码逻辑保持不变
    init(from decoder: Decoder) throws {
        let container = try decoder.singleValueContainer()
        if let x = try? container.decode(Int.self) {
            self = .integer(x)
            return
        }
        if let x = try? container.decode(String.self) {
            self = .string(x)
            return
        }
        throw DecodingError.typeMismatch(Achieved.self, DecodingError.Context(codingPath: decoder.codingPath, debugDescription: "Wrong type for Achieved"))
    }

    func encode(to encoder: Encoder) throws {
        var container = encoder.singleValueContainer()
        switch self {
        case .integer(let x):
            try container.encode(x)
        case .string(let x):
            try container.encode(x)
        }
    }
}

使用方式:

// 获取Int值
if let numAchieved = decodedData.recent.joined().first?.numAchieved.intValue {
    print(numAchieved) // 输出:15
}

// 获取String值
let numAchievedStr = decodedData.recent.joined().first?.numAchieved.stringValue ?? ""
print(numAchievedStr) // 输出:"15"

方案2:统一解码为Int(更简洁,适合数值场景)

由于API返回的这些混合类型值本质都是数值,可直接修改模型解码逻辑,将所有这类属性统一解析为Int类型,彻底去掉枚举包装:

struct Recent: Codable {
    let gameID, consoleID, consoleName, title: String
    let imageIcon, lastPlayed: String
    let numPossibleAchievements, possibleScore: Int
    let numAchieved, scoreAchieved: Int
    let numAchievedHardcore, scoreAchievedHardcore: Int

    enum CodingKeys: String, CodingKey {
        case gameID = "GameID"
        case consoleID = "ConsoleID"
        case consoleName = "ConsoleName"
        case title = "Title"
        case imageIcon = "ImageIcon"
        case lastPlayed = "LastPlayed"
        case numPossibleAchievements = "NumPossibleAchievements"
        case possibleScore = "PossibleScore"
        case numAchieved = "NumAchieved"
        case scoreAchieved = "ScoreAchieved"
        case numAchievedHardcore = "NumAchievedHardcore"
        case scoreAchievedHardcore = "ScoreAchievedHardcore"
    }

    init(from decoder: Decoder) throws {
        let container = try decoder.container(keyedBy: CodingKeys.self)
        
        // 解码普通字符串属性
        gameID = try container.decode(String.self, forKey: .gameID)
        consoleID = try container.decode(String.self, forKey: .consoleID)
        consoleName = try container.decode(String.self, forKey: .consoleName)
        title = try container.decode(String.self, forKey: .title)
        imageIcon = try container.decode(String.self, forKey: .imageIcon)
        lastPlayed = try container.decode(String.self, forKey: .lastPlayed)
        
        // 定义通用解码函数:先尝试Int,失败则解码String转Int
        func decodeInt(forKey key: CodingKeys) throws -> Int {
            if let intValue = try? container.decode(Int.self, forKey: key) {
                return intValue
            }
            let stringValue = try container.decode(String.self, forKey: key)
            guard let intValue = Int(stringValue) else {
                throw DecodingError.typeMismatch(Int.self, DecodingError.Context(codingPath: container.codingPath + [key], debugDescription: "无法将字符串转换为整数"))
            }
            return intValue
        }
        
        // 解码所有数值属性
        numPossibleAchievements = try decodeInt(forKey: .numPossibleAchievements)
        possibleScore = try decodeInt(forKey: .possibleScore)
        numAchieved = try decodeInt(forKey: .numAchieved)
        scoreAchieved = try decodeInt(forKey: .scoreAchieved)
        numAchievedHardcore = try decodeInt(forKey: .numAchievedHardcore)
        scoreAchievedHardcore = try decodeInt(forKey: .scoreAchievedHardcore)
    }
}

使用方式:
直接访问Int属性即可,无需额外处理:

print(decodedData.recent.joined().first!.numAchieved) // 输出:15

方案选择

  • 如果需要保留原始数据类型(比如要区分API返回的是字符串还是数字),选择方案1;
  • 如果业务上这些属性都是数值,不需要区分原始类型,方案2更简洁,代码维护成本更低。

内容的提问来源于stack exchange,提问作者user11640506

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.14 02:50:27