SwiftUI: получить ошибку при декодировании данных JSON, которые включают имя ключа как «iso_3166_1» (строка + число) - PullRequest
0 голосов
/ 23 апреля 2020

Я работаю с TMDB API уже некоторое время. Все идет хорошо, кроме как при попытке добавить локализацию.

Короче говоря, я получаю ошибку ниже, когда декодирую ключ с именем "iso_3166_1", строка объединяется с числом. Мне нужно отфильтровать значение этого ключа как iso_3166_1 = "CN", тогда я смогу получить элемент перевода на китайском языке, чтобы упростить. Я пытаюсь использовать CodingKey таким образом case iso31661 = "iso_3166_1". Но это не работает, «iso31661» возвращается с nil, однако оно имеет значение в JSON данных.

Так как я могу установить ключ в модели, когда он находится в этом формате?

Ошибка:

ERROR: keyNotFound(CodingKeys(stringValue: "iso_3166_1", intValue: nil), Swift.DecodingError.Context(codingPath: [CodingKeys(stringValue: "translations", intValue: nil), CodingKeys(stringValue: "translations", intValue: nil), _JSONKey(stringValue: "Index 0", intValue: 0)], debugDescription: "No value associated with key CodingKeys(stringValue: \"iso_3166_1\", intValue: nil) (\"iso_3166_1\").", underlyingError: nil))

JSON Данные

{
"adult": false,
"backdrop_path": "/xPMbNZwn1nQzc5fsjnhuu3fDndA.jpg",
"belongs_to_collection": null,
"budget": 1200000,
"genres": [],
"homepage": "",
"id": 500,
"imdb_id": "tt0105236",
"original_language": "en",
"original_title": "Reservoir Dogs",
"overview": "A botched robbery indicates a police informant, and the pressure mounts in the aftermath at a warehouse. Crime begets violence as the survivors -- veteran Mr. White, newcomer Mr. Orange, psychopathic parolee Mr. Blonde, bickering weasel Mr. Pink and Nice Guy Eddie -- unravel.",
"popularity": 20.717,
"poster_path": "/g7spS2Y4SZoQoC6Hn7zoqEqdYqR.jpg",
"production_companies": [],
"production_countries": [],
"release_date": "1992-09-02",
"revenue": 2859750,
"runtime": 99,
"spoken_languages": [
{
"iso_639_1": "en",
"name": "English"
}
],
"status": "Released",
"tagline": "Every dog has his day.",
"title": "Reservoir Dogs",
"video": false,
"vote_average": 8.2,
"vote_count": 8946,
"translations": {
"translations": [
{
"iso_3166_1": "BG",
"iso_639_1": "bg",
"name": "български език",
"english_name": "Bulgarian",
"data": {
"homepage": "",
"overview": "Банда престъпници подготвят най-големия обир на скъпоценности. Всичко се развива по плана, но изненадващо нищо неподозиращите бандити са обградени от полицията и става ясно, че един от тях е предател. Сюжетът се разгръща от изследване на мъжката психика до дисекция на престъпното съзнание, докато всички се опитват да разберат какво е провалило перфектно замисления удар.",
"runtime": 105,
"tagline": "",
"title": "Глутница кучета"
}
},
{
"iso_3166_1": "ES",
"iso_639_1": "ca",
"name": "Català",
"english_name": "Catalan",
"data": {
"homepage": "",
"overview": "Uns delinqüents professionals que no es coneixen entre ells i que es mantenen a l'anonimat darrere de noms de colors (senyor Rosa, senyor Blanc, senyor Taronja...) han preparat minuciosament el robatori a una joieria. En el moment de l'atracament apareix inesperadament la policia i es produeix una massacre. Tot fa sospitar que hi ha un traïdor infiltrat. Reunits a porta tancada dins d'un vell magatzem abandonat, els supervivents s'enfrontaran entre ells decidits a descobrir qui els ha conduït a aquesta situació límit.",
"runtime": 0,
"tagline": "",
"title": ""
}
},
...
...

Модель данных

import SwiftUI

struct Movie: Codable {
    let id: Int
    let overview: String?
    let title: String?
    let translations: Translation

    static var `default`: Movie {
        Movie(id: 0, overview: "", title: "", translations: Translation.default)
    }
}

struct Translation: Codable {
    let translations: [TranslationItem]

    static var `default`: Translation {
        Translation(translations: [])
    }
}

struct TranslationItem: Codable {
    let iso31661: String
    let name: String
    let data: DataDetail

    enum CodingKeys: String, CodingKey {
        case iso31661 = "iso_3166_1"
        case name
        case data
    }
}

struct DataDetail: Codable {
    let overview: String
    let title: String
}

WebService

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
        return URLSession(configuration: config, delegate: nil, delegateQueue: nil)
    }()

    private func createPublisher<T: Codable>(for url: URL) -> AnyPublisher<T, Error> {
        print("Publisher 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
                }

                return output.data
            }
            .decode(type: T.self, decoder: decoder)
            .eraseToAnyPublisher()
    }

    func getMovieDetail(movieId: Int) -> AnyPublisher<Movie, Error> {
        createPublisher(for: TMDBClient.Endpoints.movieDetail(movieId).url)
    }

}

ViewModel

import SwiftUI
import Combine


class MovieListViewModel: ObservableObject {
    private var webService = WebService()
    private var cancellableSet: Set<AnyCancellable> = []

    @Published var movie = Movie.default

    func getMovieDetail(movieId: Int) {
        webService.getMovieDetail(movieId: movieId)
            .receive(on: DispatchQueue.main)
            .sink(receiveCompletion: { status in
                switch status {
                case .finished:
                    break
                case .failure(let error):
                    print("ERROR: \(error)")
                    break
                }
            }) { movie in
                self.movie = movie
        }.store(in: &self.cancellableSet)
    }
}

Main ContentView

import SwiftUI
import Combine

struct ContentView: View {

    @ObservedObject var model = MovieListViewModel()

    var body: some View {
        NavigationView {
            Text(String(self.model.movie.translations.translations.filter{$0.iso31661 == "CN"}.first?.data.title ?? ""))
        }.onAppear() {
            self.model.getMovieDetail(movieId: 500)
        }
    }
}

struct ContentView_Previews: PreviewProvider {
    static var previews: some View {
        ContentView()
    }
}

1 Ответ

1 голос
/ 23 апреля 2020

О, я понял.

Я забыл, что использовал .convertFromSnakeCase в JSON декодере, который преобразует iso_3166_1 в iso31661 до применения строки CodingKey. Таким образом, решение состоит в том, чтобы удалить перечисление CodingKeys и удалить подчеркивание в iso_3166_1, пусть .convertFromSnakeCase выполнит свою работу.

Решение

struct TranslationItem: Codable {
    let iso31661: String
    let name: String
    let data: DataDetail
}

...