微信公众号搜"智元新知"关注
微信扫一扫可直接关注哦!

解码 JSON 错误:“预期解码 Array<Any> 但找到了字典”,underlyingError:nil 处理单对象和多对象结果

如何解决解码 JSON 错误:“预期解码 Array<Any> 但找到了字典”,underlyingError:nil 处理单对象和多对象结果

我正在尝试使用 SwiftUI/Combine 解析一些 JSON 数据,但我对遇到的错误有些困惑。我对结合真的很陌生,所以我可能会完全忽略一些东西。我确定这与真正的问题无关,因为如果我使用 urlsession/@escaping 以正常方式解析,这可能会发生。

代码如下:

struct FilmModel: Identifiable,Codable {
let adult: Bool
let backdropPath: String
let budget: Int
let genres: [Genre]
let homepage: String
let id: Int
let imdbID,originalLanguage,originalTitle,overview: String
let popularity: Double
let posterPath: String
let productionCompanies: [ProductionCompany]
let productionCountries: [ProductionCountry]
let releaseDate: String
let revenue,runtime: Int
let spokenLanguages: [SpokenLanguage]
let status,tagline,title: String
let video: Bool
let VoteAverage: Double
let VoteCount: Int

enum CodingKeys: String,CodingKey {
    case adult
    case backdropPath = "backdrop_path"
    case budget
    case genres
    case homepage
    case id
    case imdbID = "imbd_id"
    case originalLanguage = "original_language"
    case originalTitle = "original_title"
    case overview
    case popularity
    case posterPath = "poster_path"
    case productionCompanies = "production_companies"
    case productionCountries = "production_countries"
    case releaseDate = "release_date"
    case revenue
    case runtime
    case spokenLanguages = "spoken_languages"
    case status,title
    case video
    case VoteAverage = "Vote_average"
    case VoteCount = "Vote_count"
}

struct Genre: Identifiable,Codable {
    let id: Int
    let name: String
}

struct ProductionCompany: Codable {
    let id: Int
    let logoPath: String?
    let name,originCountry: String
}

struct ProductionCountry: Codable {
    let iso3166_1,name: String
}

struct SpokenLanguage: Codable {
    let englishName,iso639_1,name: String
}

JSON 响应:

{
"adult": false,"backdrop_path": "/rr7E0NoGKxvbkb89eR1GwfoYjpA.jpg","belongs_to_collection": null,"budget": 63000000,"genres": [
    {
        "id": 18,"name": "Drama"
    }
],"homepage": "http://www.foxmovies.com/movies/fight-club","id": 550,"imdb_id": "tt0137523","original_language": "en","original_title": "fight Club","overview": "A ticking-time-bomb insomniac and a slippery soap salesman channel primal male aggression into a shocking new form of therapy. Their concept catches on,with underground \"fight clubs\" forming in every town,until an eccentric gets in the way and ignites an out-of-control spiral toward oblivion.","popularity": 46.456,"poster_path": "/pB8BM7pdSp6B6Ih7QZ4DrQ3PmJK.jpg","production_companies": [
    {
        "id": 508,"logo_path": "/7PzJdsLglr7oW4J0J5Xcd0pHGRg.png","name": "Regency Enterprises","origin_country": "US"
    },{
        "id": 711,"logo_path": "/tEiIH5QesdheJmDAqQwvtN60727.png","name": "Fox 2000 Pictures",{
        "id": 20555,"logo_path": "/hD8yEGUBlHOcfHYbujp71vD8gZp.png","name": "Taurus Film","origin_country": "DE"
    },{
        "id": 54051,"logo_path": null,"name": "Atman Entertainment","origin_country": ""
    },{
        "id": 54052,"name": "Knickerbocker Films",{
        "id": 25,"logo_path": "/qZCc1lty5FzX30aOCVRBLzaVmcp.png","name": "20th Century Fox",{
        "id": 4700,"logo_path": "/A32wmjrs9Psf4zw0uaixF0GXfxq.png","name": "The Linson Company","origin_country": "US"
    }
],"production_countries": [
    {
        "iso_3166_1": "DE","name": "Germany"
    },{
        "iso_3166_1": "US","name": "United States of America"
    }
],"release_date": "1999-10-15","revenue": 100853753,"runtime": 139,"spoken_languages": [
    {
        "english_name": "English","iso_639_1": "en","name": "English"
    }
],"status": "Released","tagline": "Mischief. Mayhem. Soap.","title": "fight Club","video": false,"Vote_average": 8.4,"Vote_count": 22054

数据服务:

class FilmDataService {
@Published var films: [FilmModel] = []

var filmSubscription: AnyCancellable?

init() {
    getFilms()
}

private func getFilms() {
    guard let url = URL(string: "https://api.themoviedb.org/3/movie/550?api_key=<key>") else { return }
    
    filmSubscription = URLSession.shared.dataTaskPublisher(for: url)
        .subscribe(on: dispatchQueue.global(qos: .default))
        .tryMap { (output) -> Data in
            guard let response = output.response as? HTTPURLResponse,response.statusCode >= 200 && response.statusCode < 300 else {
                throw URLError(.badServerResponse)
            }
            return output.data
        }
        .decode(type: [FilmModel].self,decoder: JSONDecoder())
        .receive(on: dispatchQueue.main)
        .sink { (completion) in
            switch completion {
            case .finished:
                break
            case .failure(let error):
                print(error)
            }
        } receiveValue: { [weak self] (returnedFilms) in
            self?.films = returnedFilms
            self?.filmSubscription?.cancel()
        }

}

查看模型:

class Filmviewmodel: ObservableObject {
@Published var tabBarImageNames = ["house","rectangle.stack","clock.arrow.circlepath","square.and.arrow.down"]
@Published var films: [FilmModel] = []

private let dataService = FilmDataService()
private var cancellables = Set<AnyCancellable>()

init() {
    addSubscribers()
}

func addSubscribers() {
    dataService.$films
        .sink { [weak self] (returnedFilms) in
            self?.films = returnedFilms
        }
        .store(in: &cancellables)
}

解决方法

我的观察。您的错误可能与Combine 无关。

您正在尝试解码“[FilmModel].self”,但响应仅针对一部电影 FilmModel.self。

此外,我还会在您的 FilmModel 等中设置大部分/所有变量...可选,添加“?”。 它在我的测试中运行良好。

编辑:

这是我用来测试我的答案的代码。对我来说效果很好:

import Foundation
import SwiftUI
import Combine


@main
struct TestApp: App {
    var body: some Scene {
        WindowGroup {
            ContentView()
        }
    }
}

struct ContentView: View {
    @StateObject var movies = FilmViewModel()
    
    var body: some View {
        VStack (spacing: 50) {
            Text("movie test")
            ForEach(movies.films,id: \.id) { movie in
                Text(movie.title ?? "no title").foregroundColor(.red)
            }
        }
    }
}


class FilmDataService {
    @Published var films: [FilmModel] = []
    
    var filmSubscription: AnyCancellable?
    
    init() {
        getFilms()
    }
    
    private func getFilms() {
        guard let url = URL(string: "https://api.themoviedb.org/3/movie/550?api_key=1f632307cea6ce33f288f9a232b9803b") else { return }
        
        filmSubscription = URLSession.shared.dataTaskPublisher(for: url)
            .subscribe(on: DispatchQueue.global(qos: .default))
            .tryMap { (output) -> Data in
                guard let response = output.response as? HTTPURLResponse,response.statusCode >= 200 && response.statusCode < 300 else {
                          throw URLError(.badServerResponse)
                      }
                return output.data
            }
            .decode(type: FilmModel.self,decoder: JSONDecoder())  // <--- here
            .receive(on: DispatchQueue.main)
            .sink { (completion) in
                switch completion {
                case .finished:
                    break
                case .failure(let error):
                    print(error)
                }
            } receiveValue: { [weak self] (returnedFilms) in
                self?.films.append(returnedFilms)  // <--- here
                self?.filmSubscription?.cancel()
            }
    }
    
}

class FilmViewModel: ObservableObject {
    @Published var tabBarImageNames = ["house","rectangle.stack","clock.arrow.circlepath","square.and.arrow.down"]
    @Published var films: [FilmModel] = []
    
    private let dataService = FilmDataService()
    private var cancellables = Set<AnyCancellable>()
    
    init() {
        addSubscribers()
    }
    
    func addSubscribers() {
        dataService.$films
            .sink { [weak self] (returnedFilms) in
                self?.films = returnedFilms
            }
            .store(in: &cancellables)
    }
    
}

struct FilmModel: Identifiable,Codable {
    let adult: Bool?
    let backdropPath: String?
    let budget: Int?
    let genres: [Genre]?
    let homepage: String?
    let id: Int
    let imdbID,originalLanguage,originalTitle,overview: String?
    let popularity: Double?
    let posterPath: String?
    let productionCompanies: [ProductionCompany]?
    let productionCountries: [ProductionCountry]?
    let releaseDate: String?
    let revenue,runtime: Int?
    let spokenLanguages: [SpokenLanguage]?
    let status,tagline,title: String?
    let video: Bool?
    let voteAverage: Double?
    let voteCount: Int?
    
    enum CodingKeys: String,CodingKey {
        case adult
        case backdropPath = "backdrop_path"
        case budget
        case genres
        case homepage
        case id
        case imdbID = "imbd_id"
        case originalLanguage = "original_language"
        case originalTitle = "original_title"
        case overview
        case popularity
        case posterPath = "poster_path"
        case productionCompanies = "production_companies"
        case productionCountries = "production_countries"
        case releaseDate = "release_date"
        case revenue
        case runtime
        case spokenLanguages = "spoken_languages"
        case status,title
        case video
        case voteAverage = "vote_average"
        case voteCount = "vote_count"
    }
}

struct Genre: Identifiable,Codable {
    let id: Int
    let name: String?
}

struct ProductionCompany: Codable {
    let id: Int
    let logoPath: String?
    let name,originCountry: String?
}

struct ProductionCountry: Codable {
    let iso3166_1,name: String?
}

struct SpokenLanguage: Codable {
    let englishName,iso639_1,name: String?
}
,

首先,切勿在线编写您的api 密钥(或任何其他密钥)!

其次:

您正在调用的端点似乎正在返回单个 FilmModel。因此,您必须将其解码为单个

改变这个:

.decode(type: [FilmModel].self,decoder: JSONDecoder())

为此:

.decode(type: FilmModel.self,decoder: JSONDecoder())

然后改变这个:

.sink { [weak self] (returnedFilms) in
    self?.films = returnedFilms
}

到:

.sink { [weak self] (returnedFilm) in
    self?.films = [returnedFilm]
}

处理单对象和多对象结果

有时您不知道服务器是否会返回单个或多个对象(并且您无法控制服务器来修复该问题)。 您可以实现自定义解码器来处理单对象和多对象响应:

enum FilmsResult {
    case single(FilmModel)
    case array([FilmModel])
}

extension FilmsResult: Decodable {
    init(from decoder: Decoder) throws {
        let container = try decoder.singleValueContainer()
        if let singleFilm = try? container.decode(FilmModel.self) {
            self = .single(singleFilm)
        } else {
            try self = .array(container.decode([FilmModel].self))
        }
    }
}

extension FilmsResult {
    var values: [FilmModel] {
        switch self {
        case .single(let film): return [film]
        case .array(let films): return films
        }
    }
}

您可以将结果解码为:

.decode(type: FilmsResult.self,decoder: JSONDecoder())

并像这样使用它:

.sink { [weak self] filmsResult in
    self?.films = filmsResult.values
}

版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。

相关推荐


Selenium Web驱动程序和Java。元素在(x,y)点处不可单击。其他元素将获得点击?
Python-如何使用点“。” 访问字典成员?
Java 字符串是不可变的。到底是什么意思?
Java中的“ final”关键字如何工作?(我仍然可以修改对象。)
“loop:”在Java代码中。这是什么,为什么要编译?
java.lang.ClassNotFoundException:sun.jdbc.odbc.JdbcOdbcDriver发生异常。为什么?
这是用Java进行XML解析的最佳库。
Java的PriorityQueue的内置迭代器不会以任何特定顺序遍历数据结构。为什么?
如何在Java中聆听按键时移动图像。
Java“Program to an interface”。这是什么意思?