Swift解析RapidAPI TikTok音乐JSON失败,求修复及替代API方案
TikTok音乐信息获取与Codable解析问题
问题描述
我尝试通过TikTok歌曲ID从RapidAPI获取音乐信息用于APP播放,但无法创建有效的Codable模型解析返回数据,用quicktype.io生成的模型也无效。获取歌曲ID的API正常,问题仅出在RapidAPI的请求解析上。
当前请求代码
func fetch(){ let headers = [ "X-RapidAPI-Key": "0a5efc1659mshadcf7e7123f8554p1e0527jsn74096e7ef6dd", "X-RapidAPI-Host": "tiktok-info.p.rapidapi.com" ] let musicURL = "https://tiktok-info.p.rapidapi.com/music/6661867221164821250" let request = NSMutableURLRequest(url: NSURL(string: musicURL)! as URL,cachePolicy: .useProtocolCachePolicy, timeoutInterval: 10.0) request.httpMethod = "GET" request.allHTTPHeaderFields = headers let session = URLSession.shared let dataTask = session.dataTask(with: request as URLRequest){ [weak self] data, _ , error in guard let data = data, error == nil else{ return } do{ let songId = try JSONDecoder().decode(MusicSongModel.self, from: data) DispatchQueue.main.async { [self] in print(songId) } } catch{ print(error) } } dataTask.resume() }
当前模型代码
struct MusicSongModelElement: Codable { let id, secretID, text: String let createTime: Int let authorMeta: AuthorMeta let musicMeta: MusicMeta let covers: Covers let webVideoURL, videoURL: String let videoURLNoWaterMark, videoAPIURLNoWaterMark: String let videoMeta: VideoMeta let diggCount, shareCount, playCount, commentCount: Int let downloaded: Bool let mentions: [JSONAny] let hashtags: [Hashtag] enum CodingKeys: String, CodingKey { case id, secretID, text, createTime, authorMeta, musicMeta, covers case webVideoURL = "webVideoUrl" case videoURL = "videoUrl" case videoURLNoWaterMark = "videoUrlNoWaterMark" case videoAPIURLNoWaterMark = "videoApiUrlNoWaterMark" case videoMeta, diggCount, shareCount, playCount, commentCount, downloaded, mentions, hashtags } } // MARK: - AuthorMeta struct AuthorMeta: Codable { let id, secUid, name, nickName: String let verified: Bool let signature: String let avatar: String let following, fans, heart, video: Int let digg: Int } // MARK: - Covers struct Covers: Codable { let coversDefault, origin, coversDynamic: String enum CodingKeys: String, CodingKey { case coversDefault = "default" case origin case coversDynamic = "dynamic" } } // MARK: - Hashtag struct Hashtag: Codable { let id, name, title: String let cover: String } // MARK: - MusicMeta struct MusicMeta: Codable { let musicID, musicName, musicAuthor: String let musicOriginal: Bool let musicAlbum: String let playURL: String let coverThumb, coverMedium, coverLarge: String let duration: Int enum CodingKeys: String, CodingKey { case musicID = "musicId" case musicName, musicAuthor, musicOriginal, musicAlbum case playURL = "playUrl" case coverThumb, coverMedium, coverLarge, duration } } // MARK: - VideoMeta struct VideoMeta: Codable { let height, width, duration: Int } typealias MusicSongModel = [MusicSongModelElement] // MARK: - Encode/decode helpers class JSONNull: Codable, Hashable { public static func == (lhs: JSONNull, rhs: JSONNull) -> Bool { return true } public var hashValue: Int { return 0 } public init() {} public required init(from decoder: Decoder) throws { let container = try decoder.singleValueContainer() if !container.decodeNil() { throw DecodingError.typeMismatch(JSONNull.self, DecodingError.Context(codingPath: decoder.codingPath, debugDescription: "Wrong type for JSONNull")) } } public func encode(to encoder: Encoder) throws { var container = encoder.singleValueContainer() try container.encodeNil() } } class JSONCodingKey: CodingKey { let key: String required init?(intValue: Int) { return nil } required init?(stringValue: String) { key = stringValue } var intValue: Int? { return nil } var stringValue: String { return key } } class JSONAny: Codable { let value: Any static func decodingError(forCodingPath codingPath: [CodingKey]) -> DecodingError { let context = DecodingError.Context(codingPath: codingPath, debugDescription: "Cannot decode JSONAny") return DecodingError.typeMismatch(JSONAny.self, context) } static func encodingError(forValue value: Any, codingPath: [CodingKey]) -> EncodingError { let context = EncodingError.Context(codingPath: codingPath, debugDescription: "Cannot encode JSONAny") return EncodingError.invalidValue(value, context) } static func decode(from container: SingleValueDecodingContainer) throws -> Any { if let value = try? container.decode(Bool.self) { return value } if let value = try? container.decode(Int64.self) { return value } if let value = try? container.decode(Double.self) { return value } if let value = try? container.decode(String.self) { return value } if container.decodeNil() { return JSONNull() } throw decodingError(forCodingPath: container.codingPath) } static func decode(from container: inout UnkeyedDecodingContainer) throws -> Any { if let value = try? container.decode(Bool.self) { return value } if let value = try? container.decode(Int64.self) { return value } if let value = try? container.decode(Double.self) { return value } if let value = try? container.decode(String.self) { return value } if let value = try? container.decodeNil() { if value { return JSONNull() } } if var container = try? container.nestedUnkeyedContainer() { return try decodeArray(from: &container) } if var container = try? container.nestedContainer(keyedBy: JSONCodingKey.self) { return try decodeDictionary(from: &container) } throw decodingError(forCodingPath: container.codingPath) } static func decode(from container: inout KeyedDecodingContainer<JSONCodingKey>, forKey key: JSONCodingKey) throws -> Any { if let value = try? container.decode(Bool.self, forKey: key) { return value } if let value = try? container.decode(Int64.self, forKey: key) { return value } if let value = try? container.decode(Double.self, forKey: key) { return value } if let value = try? container.decode(String.self, forKey: key) { return value } if let value = try? container.decodeNil(forKey: key) { if value { return JSONNull() } } if var container = try? container.nestedUnkeyedContainer(forKey: key) { return try decodeArray(from: &container) } if var container = try? container.nestedContainer(keyedBy: JSONCodingKey.self, forKey: key) { return try decodeDictionary(from: &container) } throw decodingError(forCodingPath: container.codingPath) } static func decodeArray(from container: inout UnkeyedDecodingContainer) throws -> [Any] { var arr: [Any] = [] while !container.isAtEnd { let value = try decode(from: &container) arr.append(value) } return arr } static func decodeDictionary(from container: inout KeyedDecodingContainer<JSONCodingKey>) throws -> [String: Any] { var dict = [String: Any]() for key in container.allKeys { let value = try decode(from: &container, forKey: key) dict[key.stringValue] = value } return dict } static func encode(to container: inout UnkeyedEncodingContainer, array: [Any]) throws { for value in array { if let value = value as? Bool { try container.encode(value) } else if let value = value as? Int64 { try container.encode(value) } else if let value = value as? Double { try container.encode(value) } else if let value = value as? String { try container.encode(value) } else if value is JSONNull { try container.encodeNil() } else if let value = value as? [Any] { var container = container.nestedUnkeyedContainer() try encode(to: &container, array: value) } else if let value = value as? [String: Any] { var container = container.nestedContainer(keyedBy: JSONCodingKey.self) try encode(to: &container, dictionary: value) } else { throw encodingError(forValue: value, codingPath: container.codingPath) } } } static func encode(to container: inout KeyedEncodingContainer<JSONCodingKey>, dictionary: [String: Any]) throws { for (key, value) in dictionary { let key = JSONCodingKey(stringValue: key)! if let value = value as? Bool { try container.encode(value, forKey: key) } else if let value = value as? Int64 { try container.encode(value, forKey: key) } else if let value = value as? Double { try container.encode(value, forKey: key) } else if let value = value as? String { try container.encode(value, forKey: key) } else if value is JSONNull { try container.encodeNil(forKey: key) } else if let value = value as? [Any] { var container = container.nestedUnkeyedContainer(forKey: key) try encode(to: &container, array: value) } else if let value = value as? [String: Any] { var container = container.nestedContainer(keyedBy: JSONCodingKey.self, forKey: key) try encode(to: &container, dictionary: value) } else { throw encodingError(forValue: value, codingPath: container.codingPath) } } } static func encode(to container: inout SingleValueEncodingContainer, value: Any) throws { if let value = value as? Bool { try container.encode(value) } else if let value = value as? Int64 { try container.encode(value) } else if let value = value as? Double { try container.encode(value) } else if let value = value as? String { try container.encode(value) } else if value is JSONNull { try container.encodeNil() } else { throw encodingError(forValue: value, codingPath: container.codingPath) } } public required init(from decoder: Decoder) throws { if var arrayContainer = try? decoder.unkeyedContainer() { self.value = try JSONAny.decodeArray(from: &arrayContainer) } else if var container = try? decoder.container(keyedBy: JSONCodingKey.self) { self.value = try JSONAny.decodeDictionary(from: &container) } else { let container = try decoder.singleValueContainer() self.value = try JSONAny.decode(from: container) } } public func encode(to encoder: Encoder) throws { if let arr = self.value as? [Any] { var container = encoder.unkeyedContainer() try JSONAny.encode(to: &container, array: arr) } else if let dict = self.value as? [String: Any] { var container = encoder.container(keyedBy: JSONCodingKey.self) try JSONAny.encode(to: &container, dictionary: dict) } else { var container = encoder.singleValueContainer() try JSONAny.encode(to: &container, value: self.value) } } }
返回错误信息
dataCorrupted(Swift.DecodingError.Context(codingPath: [], debugDescription: "The given data was not valid JSON.", underlyingError: Optional(Error Domain=NSCocoaErrorDomain Code=3840 "Invalid value around line 1, column 0." UserInfo={NSDebugDescription=Invalid value around line 1, column 0., NSJSONSerializationErrorIndex=0})))
此前可用但已达限额的代码
func fetchSong() { let headers = [ "X-RapidAPI-Key": "0a5efc1659mshadcf7e7123f8554p1e0527jsn74096e7ef6dd", "X-RapidAPI-Host": "tiktok-all-in-one.p.rapidapi.com" ] let request = NSMutableURLRequest(url: NSURL(string: "https://tiktok-all-in-one.p.rapidapi.com/music?id=6661867221164821250")! as URL, cachePolicy: .useProtocolCachePolicy, timeoutInterval: 10.0) request.httpMethod = "GET" request.allHTTPHeaderFields = headers let session = URLSession.shared let dataTask = session.dataTask(with: request as URLRequest, completionHandler: { (data, response, error) -> Void in if (error != nil) { } else { let httpResponse = response as? HTTPURLResponse } if let data = data { if let json = try? JSONSerialization.jsonObject(with: data, options: JSONSerialization.ReadingOptions.mutableContainers) as? [String : Any] { print(json) } } }) dataTask.resume() }
解决方案
1. 先排查API返回的实际内容
dataCorrupted错误核心原因是返回的不是有效JSON,可能是API返回了HTML错误页(比如限额提示、密钥失效、接口变更)。先修改请求代码打印原始返回内容:
let dataTask = session.dataTask(with: request as URLRequest){ [weak self] data, response, error in guard let data = data, error == nil else{ print(error ?? "Unknown error") return } // 打印原始响应字符串,确认返回格式 if let rawResponse = String(data: data, encoding: .utf8) { print("Raw API response: \(rawResponse)") } do{ let song = try JSONDecoder().decode(MusicSongModel.self, from: data) DispatchQueue.main.async { print(song) } } catch{ print(error) } }
如果返回的是HTML,说明当前API请求已经失效,需要更换接口或检查密钥额度。
2. 修复Codable模型
原模型存在两个核心问题:
- 根类型错误:定义为数组
[MusicSongModelElement],但实际API返回的是单个对象 - 字段强制非可选:API返回的部分字段可能为空,导致解析失败
调整后的模型:
struct MusicSongModel: Codable { let id, secretID, text: String? let createTime: Int? let authorMeta: AuthorMeta? let musicMeta: MusicMeta? let covers: Covers? let webVideoURL, videoURL: String? let videoURLNoWaterMark, videoAPIURLNoWaterMark: String? let videoMeta: VideoMeta? let diggCount, shareCount, playCount, commentCount: Int? let downloaded: Bool? let mentions: [AnyCodable]? let hashtags: [Hashtag]? enum CodingKeys: String, CodingKey { case id, secretID, text, createTime, authorMeta, music
相关产品推荐
相关产品推荐

