如何解析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
相关产品推荐
相关产品推荐

