Swift: ОШИБКА: keyNotFound (CodingKeys (stringValue: "adult", intValue: nil) - PullRequest
0 голосов
/ 11 марта 2020

Я вижу много подобных вопросов в переполнении стека, но, похоже, никто не похож на мой случай. Я новичок в совмещении работы с фреймами, и мне потребовался весь этот день, чтобы выяснить, в чем дело, но все еще застрял здесь ...

Xcode выдает мне ошибку ниже, что я делаю, используя API TMDB и декодировать его в мою модель актера. И это не удалось на этой линии let result = try self.decoder.decode(TMDBActorsResult.self, from: output.data). Не могли бы вы дать мне подсказку, что происходит с этим adult?

ОШИБКА: keyNotFound (CodingKeys (stringValue: "adult", intValue: nil), Swift.DecodingError.Context (codingPath: [CodingKeys (stringValue: «результаты», intValue: nil), _JSONKey (stringValue: «Index 0», intValue: 0), CodingKeys (stringValue: «knownFor», intValue: nil), _JSONKey (stringValue: «Index 0», intValue: 0)], debugDescription: "Нет значения, связанного с ключом CodingKeys (stringValue: \" adult \ ", intValue: nil) (\" adult \ ").", underError: nil))

Также я проверяю, работает ли URL, и вот возвращаемые данные из TMDB API:

enter image description here

enter image description here

/// Модель для актера

import SwiftUI


struct TMDBActorsResult: Codable {
    let page: Int?
    let results: [Actor]
    let totalResults: Int?
    let totalPages: Int?
}

struct Actor: Codable {
    let profilePath: String?
    let adult: Bool
    let id: Int?
    let name: String?
    let popularity: CGFloat?
    let knownFor: [Production]?
    let knownForDepartment: String?
    let gender: Int?
}

// MARK: Used for two objects with media type = (Movie or TV)
struct Production: Codable {
    let posterPath: String?
    let adult: Bool
    let overview: String?
    let releaseDate: String?
    let originalTitle: String?
    let genreIds: [Int]?
    let id: Int?
    let mediaType: String?
    let originalLanguage: String?
    let title: String?
    let backdropPath: String?
    let popularity: Double?
    let voteCount: Int?
    let video: Bool
    let voteAverage: Double?
    let firstAirDate: String?
    let originCountry: [String]?
    let name: String?
    let originalName: String?
}

/// Декодер JSON.

import Foundation
import Combine

enum HTTPError: LocalizedError {
    case statusCode
    case post
}

struct WebService {

    private var decoder: JSONDecoder = {
        let decoder = JSONDecoder()
        decoder.keyDecodingStrategy = .convertFromSnakeCase
        return decoder
    }()

    private var session: URLSession = {
        let config = URLSessionConfiguration.default
        config.urlCache = URLCache.shared
        config.waitsForConnectivity = true
        config.requestCachePolicy = .reloadIgnoringLocalCacheData
        return URLSession(configuration: config, delegate: nil, delegateQueue: nil)
    }()

    private func createPublisher<T: Codable>(for url: URL) -> AnyPublisher<T, Error> {
        print("Pblisher URL: \(url)")
        return session.dataTaskPublisher(for: url)
            .tryMap { output in
                guard let response = output.response as? HTTPURLResponse, response.statusCode == 200 else {
                    print("Response: \(output.response)")

                    do {
                        let ss = try self.decoder.decode(Response.self, from: output.data)
                        print("ss:  \(ss)")
                    } catch {
                        print(error)
                    }
                    throw HTTPError.statusCode
                }

                do {
                    let result = try self.decoder.decode(TMDBActorsResult.self, from: output.data)
                    print("Result: \(result)")
                } catch {
                    print("ERROR: \(error)")
                }
                return output.data
            }
            .decode(type: T.self, decoder: decoder)
            .eraseToAnyPublisher()
    }

    func getSectionsPublisher() -> AnyPublisher<TMDBActorsResult, Error> {
        createPublisher(for: TMDBClient.Endpoints.popularActors.url).eraseToAnyPublisher()
    }

}


/ / / Обновление, Как комментарий vadian, я пытаюсь изменить свою модель данных на использование значения, связанного с перечислением, как показано в коде ниже, но это дает мне ошибку Instance method 'decode(_:forKey:)' requires that 'MediaType' conform to 'Decodable'. Я сообщаю, что отличается то, что моя модель использует type в элементах, но не в структуре root Media.

let jsonString = """
[{"name": "Popular Movies",
"description": "Basic movie description",
"items": [ { "id": 15, "budget": 10, "type": "movies","name": "Sample movie", "movieSPT": ""}]
},
{"name": "Popular TV Shows",
"description": "Basic shows description",
"items": [ { "id": 15, "adult": false, "type": "tvshows","title": "Sample show", "showSPT": ""}]
}
]
"""
let data = Data(jsonString.utf8)


struct Movie : Decodable {
    let id: Int
    let name, movieSPT: String
    let type: String
    let budget: Int
}

struct TVShow : Decodable {
    let id: Int
    let title, showSPT: String
    let type: String
    let adult: Bool
}

enum MediaType {
    case movie([Movie]), tvShow([TVShow])
}

struct Media : Decodable {
    let name : String
    let description : String
    let items : MediaType

    private enum CodingKeys : String, CodingKey { case name, description, type, items }

    init(from decoder : Decoder) throws {
        let container = try decoder.container(keyedBy: CodingKeys.self)
        self.name = try container.decode(String.self, forKey: .name)
        self.description = try container.decode(String.self, forKey: .description)
        self.items = try container.decode(MediaType.self, forKey: .items)
        // type = try container.decode(String.self, forKey: .type)
        if items.type == "movies" {
            let movieData = try container.decode([Movie].self, forKey: .items)
            // print("AAA: \(movieData)")
            items = .movie(movieData)
        } else { // add better error handling
            let showData = try container.decode([TVShow].self, forKey: .items)
            items = .tvShow(showData)
        }

    }
}

do {
    let result = try JSONDecoder().decode([Media].self, from: data)
    print(result)
} catch {
    print(error)
}

Добавить JSON Данные , known_for - это [объект], объект которого может быть типом * Mov ie или типом TV.

{
  "page": 1,
  "total_results": 10000,
  "total_pages": 500,
  "results": [
    {
      "popularity": 57.168,
      "known_for_department": "Acting",
      "name": "Thassapak Hsu",
      "id": 1910848,
      "profile_path": "/1fmjgN8EvDj1TiEJk2Zs4y0T40O.jpg",
      "adult": false,
      "known_for": [
        {
          "original_name": "萌妻食神",
          "genre_ids": [
            35,
            10765,
            10766
          ],
          "media_type": "tv",
          "name": "Cinderella Chef",
          "origin_country": [],
          "vote_count": 5,
          "first_air_date": "2018-04-23",
          "backdrop_path": "/rnzmWKEiWPb8GC1mlqQojj8ccWj.jpg",
          "original_language": "zh",
          "id": 79574,
          "vote_average": 9,
          "overview": "",
          "poster_path": "/xb40Li6ff1BK0pVOxV4lutssCrR.jpg"
        },
        {
          "original_name": "外星女生柴小七",
          "genre_ids": [
            35,
            10765
          ],
          "media_type": "tv",
          "name": "My Girlfriend is an Alien",
          "origin_country": [
            "CN"
          ],
          "vote_count": 2,
          "first_air_date": "2019-08-19",
          "backdrop_path": "/kCl7piWv3pypgYfyLFi7ZgFGlYV.jpg",
          "original_language": "zh",
          "id": 92779,
          "vote_average": 9,
          "overview": "The alien girl Chai Xiaoqi tells the story of Fang Xiaoqi, the overbearing president of the alien girl who died from the \"Cape Town Planet\", who was suffering from the \"rainy weather heterosexual amnesia\". A high-energy hilarious and romantic cross-star love story. The female host Chai Xiaoqi is not only an alien, but also a true-handed witch. Once she inhales the hormones emitted by the males in the earth, she will fall into the \"flowery state\" and suffer from various diseases. The fun and ridiculously ridiculous romance will restore the singularity of the girl in the perfection of the girl. In order to survive on the human earth, Chai Xiaoqi will use his various super powers to solve one accident after another, like a roller coaster. The ups and downs will make the audience hooked. The male lord is cold and is an alternative overbearing president. When it rains, he will forget the opposite sex that appears around him. For this reason, he and the female host will launch various \"fighting and fighting\" laughter dramas. The experience of high sweetness and romance is expected to be Strongly slammed the girl's heart when it was broadcast.",
          "poster_path": "/5e2owvs9TWVsuIacTFxJGPp6KVW.jpg"
        },
        {
          "original_name": "Devil Lover เผลอใจ..ให้นายปีศาจ",
          "id": 74640,
          "media_type": "tv",
          "name": "Devil Lover เผลอใจ..ให้นายปีศาจ",
          "vote_count": 0,
          "vote_average": 0,
          "first_air_date": "2015-10-07",
          "poster_path": "/moThN7iERydEHI2RbfrmhCp69R4.jpg",
          "genre_ids": [
            35
          ],
          "original_language": "th",
          "backdrop_path": "/iRYOwW6DRIRwDYVmRWA8nbfaV2c.jpg",
          "overview": "",
          "origin_country": [
            "TH"
          ]
        }
      ],
      "gender": 2
    },
    {
      "popularity": 39.35,
      "known_for_department": "Acting",
      "gender": 1,
      "id": 2487703,
      "profile_path": "/jRdDoFoHq36hg4kYxxiLa5DRYUW.jpg",
      "adult": false,
      "known_for": [
        {
          "poster_path": "/d9PhCnofBEeQGR3lwywTjWKBiXj.jpg",
          "id": 449924,
          "vote_count": 346,
          "video": false,
          "media_type": "movie",
          "adult": false,
          "backdrop_path": "/ekP6EVxL81lZ4ivcqPsoZ72rY0h.jpg",
          "genre_ids": [
            28,
            18,
            36
          ],
          "original_title": "葉問4",
          "original_language": "cn",
          "title": "Ip Man 4: The Finale",
          "vote_average": 6,
          "overview": "Following the death of his wife, Ip Man travels to San Francisco to ease tensions between the local kung fu masters and his star student, Bruce Lee, while searching for a better future for his son.",
          "release_date": "2019-12-20"
        }
      ],
      "name": "Vanda Lee"
    },
    {
      "popularity": 28.664,
      "known_for_department": "Acting",
      "gender": 1,
      "id": 556435,
      "profile_path": "/5MgWM8pkUiYkj9MEaEpO0Ir1FD9.jpg",
      "adult": false,
      "known_for": [
        {
          "release_date": "2019-05-30",
          "id": 496243,
          "vote_count": 5120,
          "video": false,
          "media_type": "movie",
          "vote_average": 8.6,
          "title": "Parasite",
          "genre_ids": [
            35,
            18,
            53
          ],
          "original_title": "기생충",
          "original_language": "ko",
          "adult": false,
          "backdrop_path": "/TU9NIjwzjoKPwQHoHshkFcQUCG.jpg",
          "overview": "All unemployed, Ki-taek's family takes peculiar interest in the wealthy and glamorous Parks for their livelihood until they get entangled in an unexpected incident.",
          "poster_path": "/7IiTTgloJzvGI1TAYymCfbfl3vT.jpg"
        },
....

1 Ответ

0 голосов
/ 11 марта 2020

Ошибка говорит:

Нет значения для ключа adult в первом элементе (Index 0) массива knownFor ([Production]) в первом элементе (Index 0) массива results (Actor).

Пожалуйста, убедитесь, что на скриншоте показаны только [...]


Относительно вашего редактирования:

Вы можете ' Чтобы декодировать вложенный словарь таким образом, попробуйте это

struct TMDBActorsResult: Decodable {
    let page: Int
    let results: [Actor]
    let totalResults: Int
    let totalPages: Int
}

struct Actor: Decodable {
    let popularity: Double
    let knownForDepartment: String
    let gender: Int
    let profilePath: String
    let name: String?
    let id: Int
    let adult: Bool
    let knownFor: [Production]
}

enum MediaType : String, Decodable {
    case tv, movie
}

// MARK: Used for two objects with media type = (Movie or TV)
enum Production : Decodable {
    case movie(Movie), tvShow(TVShow)

    private enum CodingKeys : String, CodingKey { case mediaType }

    init(from decoder : Decoder) throws {
        let dictionaryContainer = try decoder.container(keyedBy: CodingKeys.self)
        let mediaType = try dictionaryContainer.decode(MediaType.self, forKey: .mediaType)
        let container = try decoder.singleValueContainer()
        switch mediaType {
            case .tv: self = .tvShow(try container.decode(TVShow.self))
            case .movie: self = .movie(try container.decode(Movie.self))
        }
    }
}

struct Movie: Decodable {
    let posterPath: String
    let adult: Bool
    let voteCount: Int
    let video: Bool
    let backdropPath: String
    let genreIds: [Int]
    let originalTitle: String
    let originalLanguage: String
    let title: String
    let voteAverage: Double
    let overview: String
    let releaseDate: String
}

struct TVShow: Decodable {
    let originalName: String
    let genreIds: [Int]
    let name: String
    let originCountry: [String]
    let backdropPath: String
    let voteCount: Int
    let firstAirDate: String
    let originalLanguage: String
    let overview: String
    let posterPath: String
    let id: Int
}
...