When I change a letter in the searchText, I receive this error.
decoding error keyNotFound(CodingKeys(stringValue: "items", intValue: nil), Swift.DecodingError.Context(codingPath: [], debugDescription: "No value associated with key CodingKeys(stringValue: "items", intValue: nil) ("items").", underlyingError: nil))
I'm at a loss because “items” are what I'm actually searching so making it optional doesn't seem right. (like I've seen as the solution for others) It feels like there's a bigger problem that I am missing.
Here is the json if you search harry potter - https://www.googleapis.com/books/v1/volumes?q=harry+potter
{
"kind": "books#volumes",
"totalItems": 1588,
"items": [
{
"kind": "books#volume",
"id": "L18VBQAAQBAJ",
"etag": "vz78Ah8lJGE",
"selfLink": "https://www.googleapis.com/books/v1/volumes/L18VBQAAQBAJ",
"volumeInfo": {
"title": "The Psychology of Harry Potter",
"subtitle": "An Unauthorized Examination Of The Boy Who Lived",
"authors": [
"Neil Mulholland"
],
"publisher": "BenBella Books",
"publishedDate": "2007-04-10",
"description": "Harry Potter has provided a portal to the wizarding world for millions of readers, but an examination of Harry, his friends and his enemies will take us on yet another journey: through the psyche of the Muggle (and wizard!) mind. The twists and turns of the series, as well as the psychological depth and complexity of J. K. Rowling’s characters, have kept fans enthralled with and puzzling over the many mysteries that permeate Hogwarts and beyond: • Do the Harry Potter books encourage disobedience? • Why is everyone so fascinated by Professor Lupin? • What exactly will Harry and his friends do when they finally pass those N.E.W.T.s? • Do even wizards live by the ticking of the clock? • Is Harry destined to end up alone? And why did it take Ron and Hermione so long to get together? Now, in The Psychology of Harry Potter, leading psychologists delve into the ultimate Chamber of Secrets, analyzing human mind and motivation by examining the themes and characters that make the Harry Potter books the bestselling fantasy series of all time. Grab a spot on the nearest couch, and settle in for some fresh revelations about our favorite young wizard!",
"industryIdentifiers": [
{
"type": "ISBN_13",
"identifier": "9781932100884"
},
{
"type": "ISBN_10",
"identifier": "1932100881"
}
],
"readingModes": {
"text": false,
"image": false
},
"pageCount": 338,
"printType": "BOOK",
"categories": [
"Literary Criticism"
],
"averageRating": 3.5,
"ratingsCount": 5,
"maturityRating": "NOT_MATURE",
"allowAnonLogging": false,
"contentVersion": "0.1.2.0.preview.0",
"panelizationSummary": {
"containsEpubBubbles": false,
"containsImageBubbles": false
},
"imageLinks": {
"smallThumbnail": "http://books.google.com/books/content?id=L18VBQAAQBAJ&printsec=frontcover&img=1&zoom=5&edge=curl&source=gbs_api",
"thumbnail": "http://books.google.com/books/content?id=L18VBQAAQBAJ&printsec=frontcover&img=1&zoom=1&edge=curl&source=gbs_api"
},
"language": "en",
"previewLink": "http://books.google.com/books?id=L18VBQAAQBAJ&printsec=frontcover&dq=harry+potter&hl=&cd=1&source=gbs_api",
"infoLink": "http://books.google.com/books?id=L18VBQAAQBAJ&dq=harry+potter&hl=&source=gbs_api",
"canonicalVolumeLink": "https://books.google.com/books/about/The_Psychology_of_Harry_Potter.html?hl=&id=L18VBQAAQBAJ"
},
"saleInfo": {
"country": "US",
"saleability": "NOT_FOR_SALE",
"isEbook": false
},
"accessInfo": {
"country": "US",
"viewability": "PARTIAL",
"embeddable": true,
"publicDomain": false,
"textToSpeechPermission": "ALLOWED",
"epub": {
"isAvailable": false
},
"pdf": {
"isAvailable": false
},
"webReaderLink": "http://play.google.com/books/reader?id=L18VBQAAQBAJ&hl=&source=gbs_api",
"accessViewStatus": "SAMPLE",
"quoteSharingAllowed": false
},
"searchInfo": {
"textSnippet": "Now, in The Psychology of Harry Potter, leading psychologists delve into the ultimate Chamber of Secrets, analyzing human mind and motivation by examining the themes and characters that make the Harry Potter books the bestselling fantasy ..."
}
}
}
Book Model
import Foundation
struct ApiResponse: Codable {
let items: [Book]
}
struct Book: Codable, Identifiable {
let id: String?
let volumeInfo: VolumeInfo
}
struct VolumeInfo: Codable {
let title: String
let authors: [String]?
let categories: [String]?
let description: String?
let industryIdentifier: [IndustryIdentifier]?
let imageLinks: ImageLinks
}
struct ImageLinks: Codable {
let thumbnail: URL?
}
struct IndustryIdentifier: Codable {
let identifier: String
}
SearchBookViewModel
import Foundation
import Combine
class SearchBookViewModel: ObservableObject {
@Published var searchText = ""
@Published var books = [Book]()
let limit: Int = 20
var subscriptions = Set<AnyCancellable>()
init() {
$searchText
.debounce(for: .seconds(0.5), scheduler: RunLoop.main)
.sink { [weak self] term in
self?.searchBooks(for: term)
}.store(in: &subscriptions)
}
func searchBooks(for searchText: String) {
if let url = URL(string: "https://www.googleapis.com/books/v1/volumes?q=\(searchText)") {
URLSession.shared.dataTask(with: url) { data, response, error in
if let data = data {
do {
let response = try JSONDecoder().decode(ApiResponse.self, from: data)
DispatchQueue.main.async {
self.books = response.items
}
} catch {
print("decoding error \(error)")
}
}
}.resume()
}
}
}
You need to make sure the struct models you have match the json data exactly.
To do that, you need to read the docs of the server responses, to determine
which fields are optionals, for example, in VolumeInfo
you should have let imageLinks: ImageLinks?
.
Also you need to cater for when the server returns an error message
, for example when the
searchText
is empty, as it is at the begining, leading to decoding of it into your ApiResponse
.
Try this simple example code, and build on that for your own purpose. Note, there many other approaches, this is just an example to avoid your decoding error.
import Foundation
import SwiftUI
import Combine
struct ContentView: View {
@StateObject var viewModel = SearchBookViewModel()
var body: some View {
VStack {
TextField("search for", text: $viewModel.searchText).border(.red)
List(viewModel.books) { book in
Text(book.volumeInfo.title)
}
}
}
}
class SearchBookViewModel: ObservableObject {
@Published var searchText = ""
@Published var books = [Book]()
let limit: Int = 20
var subscriptions = Set<AnyCancellable>()
init() {
$searchText
.debounce(for: .seconds(0.5), scheduler: RunLoop.main)
.sink { [weak self] term in
self?.searchBooks(for: term)
}.store(in: &subscriptions)
}
func searchBooks(for searchText: String) {
if let url = URL(string: "https://www.googleapis.com/books/v1/volumes?q=\(searchText)") {
URLSession.shared.dataTask(with: url) { data, response, error in
if let data = data {
// to show what you get from the server
// print("---> data: \(String(data: data, encoding: .utf8) as AnyObject)")
do {
let response = try JSONDecoder().decode(ApiResponse.self, from: data)
DispatchQueue.main.async {
if let books = response.items { // <-- here
self.books = books
} else {
self.books = []
// todo, deal with the error
print("---> error \(response.error)")
}
}
} catch {
// todo, deal with decoding errors
print("decoding error \(error)")
}
}
}.resume()
}
}
}
struct ErrorMesage: Codable { // <-- here
let domain: String
let message: String
let reason: String
let location: String
let locationType: String
}
struct ApiError: Codable { // <-- here
let code: Int
let message: String
let errors: [ErrorMesage]
}
struct ApiResponse: Codable {
let items: [Book]? // <-- here
let error: ApiError? // <-- here
}
struct Book: Codable, Identifiable {
let id: String?
let volumeInfo: VolumeInfo
}
struct VolumeInfo: Codable {
let title: String
let authors: [String]?
let categories: [String]?
let description: String?
let industryIdentifier: [IndustryIdentifier]?
let imageLinks: ImageLinks? // <-- here
}
struct ImageLinks: Codable {
let thumbnail: URL?
}
struct IndustryIdentifier: Codable {
let identifier: String
}