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

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
相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.17 09:00:50