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

如何解析JSON中类型不固定的rating属性?

解决Swift Codable兼容Int/String类型rating的解析问题

你遇到的这个问题很典型——JSON里同一个字段时而返回字符串、时而返回数值,直接用固定类型定义模型会导致整个解析失败。下面给你两种实用的解决方法,都能完美兼容这两种情况:

方法一:自定义解码逻辑,统一转成String类型

如果你只需要把rating当成字符串使用,可以直接在DetailModel里自定义解码初始化方法,针对rating字段做容错处理:

struct DetailModel : Decodable {
    var id : Int?
    var name : String?
    var permalink : String?
    var url : String?
    var description : String
    var description_source : String?
    var start_date : String?
    var end_date : String?
    var country : String?
    var status : String?
    var runtime : Int?
    var network : String?
    var youtube_link : String?
    var image_path : String?
    var image_thumbnail_path : String?
    var rating: String
    var rating_count : String?
    var countdown : String?
    var genres : [String]?
    var pictures : [String]?
    var episodes : [EpisodesModel]?
    
    // 自定义解码初始化
    init(from decoder: Decoder) throws {
        let container = try decoder.container(keyedBy: CodingKeys.self)
        
        // 逐个解码其他属性,可选类型用try?避免单个字段失败导致整个模型解析失败
        id = try? container.decode(Int.self, forKey: .id)
        name = try? container.decode(String.self, forKey: .name)
        permalink = try? container.decode(String.self, forKey: .permalink)
        url = try? container.decode(String.self, forKey: .url)
        // description是必填字段,用try确保如果它不存在会抛出错误
        description = try container.decode(String.self, forKey: .description)
        description_source = try? container.decode(String.self, forKey: .description_source)
        start_date = try? container.decode(String.self, forKey: .start_date)
        end_date = try? container.decode(String.self, forKey: .end_date)
        country = try? container.decode(String.self, forKey: .country)
        status = try? container.decode(String.self, forKey: .status)
        runtime = try? container.decode(Int.self, forKey: .runtime)
        network = try? container.decode(String.self, forKey: .network)
        youtube_link = try? container.decode(String.self, forKey: .youtube_link)
        image_path = try? container.decode(String.self, forKey: .image_path)
        image_thumbnail_path = try? container.decode(String.self, forKey: .image_thumbnail_path)
        rating_count = try? container.decode(String.self, forKey: .rating_count)
        countdown = try? container.decode(String.self, forKey: .countdown)
        genres = try? container.decode([String].self, forKey: .genres)
        pictures = try? container.decode([String].self, forKey: .pictures)
        episodes = try? container.decode([EpisodesModel].self, forKey: .episodes)
        
        // 重点处理rating字段
        do {
            // 先尝试按String解析
            rating = try container.decode(String.self, forKey: .rating)
        } catch DecodingError.typeMismatch {
            // 如果是类型不匹配(比如JSON里是Int),转成Int再转成String
            let intRating = try container.decode(Int.self, forKey: .rating)
            rating = String(intRating)
        } catch {
            // 其他错误情况,给个默认值(根据你的需求调整)
            rating = "0"
        }
    }
    
    // 必须定义CodingKeys,保证和JSON字段一一对应
    enum CodingKeys: String, CodingKey {
        case id, name, permalink, url, description, description_source, start_date, end_date, country, status, runtime, network, youtube_link, image_path, image_thumbnail_path, rating, rating_count, countdown, genres, pictures, episodes
    }
}

这个方法的核心是捕获typeMismatch错误,当解析String失败时,自动尝试解析Int并转成String,确保无论JSON里是哪种类型,rating都能正确赋值。

方法二:用枚举兼容原始类型,保留类型信息

如果你之后需要对rating做数值计算(比如排序、比较),可以用枚举来同时兼容Int和String类型,这样能保留原始数据的类型:

// 定义兼容Int和String的Rating枚举
enum Rating: Decodable {
    case string(String)
    case int(Int)
    
    init(from decoder: Decoder) throws {
        let container = try decoder.singleValueContainer()
        // 先尝试解析成Int,失败再尝试String
        if let intValue = try? container.decode(Int.self) {
            self = .int(intValue)
        } else if let stringValue = try? container.decode(String.self) {
            self = .string(stringValue)
        } else {
            // 两种类型都解析失败时抛出错误
            throw DecodingError.typeMismatch(
                Rating.self,
                DecodingError.Context(
                    codingPath: decoder.codingPath,
                    debugDescription: "Rating字段只能是Int或String类型"
                )
            )
        }
    }
    
    // 计算属性:转成String方便展示
    var stringValue: String {
        switch self {
        case .string(let str): return str
        case .int(let num): return String(num)
        }
    }
    
    // 计算属性:转成Double方便数值计算
    var doubleValue: Double? {
        switch self {
        case .string(let str): return Double(str)
        case .int(let num): return Double(num)
        }
    }
}

// 修改DetailModel里的rating类型为Rating
struct DetailModel : Decodable {
    // ...其他所有属性保持不变
    var rating: Rating
    // ...其他所有属性保持不变
}

使用的时候,你可以通过detail.rating.stringValue获取字符串格式,或者detail.rating.doubleValue获取数值格式,非常灵活。

另外要注意:你原来的解析函数用了try?,这会导致任何解码错误都返回nil。现在自定义了解码逻辑后,除非必填字段(比如description)解析失败,否则其他字段即使解析出错也只会是nil,不会导致整个模型变成nil,这样更符合业务需求。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.29 06:49:47