No value associated with key CodingKeys error - json

I'm trying to retrieve json from the google books api
Here are my structs:
struct Book: Codable {
let volumeInfo: VolumeInfo
}
struct VolumeInfo: Codable {
let title: String
let authors: [String]
let publisher, publishedDate, description: String
}
and this is the code to decode my json:
var bookInfo = [Book]()
override func viewDidLoad() {
super.viewDidLoad()
if let url = URL(string: "https://www.googleapis.com/books/v1/volumes?q=harry+potter") {
URLSession.shared.dataTask(with: url) { data, response, error in
if let data = data {
do {
let parsedJSON = try JSONDecoder().decode(Book.self, from: data)
for books in self.bookInfo {
DispatchQueue.main.async {
print(books.volumeInfo.title)
}
}
} catch {
print(error)
}
}
}.resume()
}
}
and the error message I get is this:
keyNotFound(CodingKeys(stringValue: "volumeInfo", intValue: nil),
Swift.DecodingError.Context(codingPath: [], debugDescription: "No
value associated with key CodingKeys(stringValue: "volumeInfo",
intValue: nil) ("volumeInfo").", underlyingError: nil))

you are trying to decode a Book object, where you should be decoding the
API response, then extract the Books from that. This is why you are getting the error.
Try the following example code:
struct Book: Identifiable, Codable {
let id = UUID()
let volumeInfo: VolumeInfo
}
struct VolumeInfo: Codable {
let title, publishedDate: String
let authors: [String]?
let publisher, description: String?
}
struct ApiResponse: Codable {
let kind: String
let totalItems: Int
let items: [Book]
}
var bookInfo = [Book]()
override func viewDidLoad() {
super.viewDidLoad()
if let url = URL(string: "https://www.googleapis.com/books/v1/volumes?q=harry+potter") {
URLSession.shared.dataTask(with: url) { data, response, error in
if let data = data {
do {
let response = try JSONDecoder().decode(ApiResponse.self, from: data)
bookInfo = response.items
} catch {
print(error)
}
}
}.resume()
}
}

Related

"Expected to decode Array<Any> but found a dictionary instead." How can i solve this

i'm trying to do with API Call. I got error every time trying to do API Call.
typeMismatch(Swift.Array, Swift.DecodingError.Context(codingPath: [], debugDescription: "Expected to decode Array but found a dictionary instead.", underlyingError: nil))
this is what i see when simulate my code in console.
Here is json format i try to call in my app. Click
My Model
struct Article: Codable {
let author: String
let title, articleDescription: String
let url: String
let urlToImage: String
let publishedAt: Date
let content: String?
enum CodingKeys: String, CodingKey {
case author, title
case articleDescription = "description"
case url, urlToImage, publishedAt, content
}
}
and This is my API Call function.
import UIKit
class ViewController: UIViewController {
var article = [Article]()
override func viewDidLoad() {
super.viewDidLoad()
jsonParse {
print("success")
}
view.backgroundColor = .red
}
func jsonParse(completed: #escaping () -> ()) {
let url = URL(string: "https://newsapi.org/v2/top-headlines?country=tr&apiKey=1ea9c2d2fbe74278883a8dc0c9eb912f")
let task = URLSession.shared.dataTask(with: url!) { data, response, error in
if error != nil {
print(error?.localizedDescription as Any)
}else {
do {
let result = try JSONDecoder().decode([Article].self, from: data!)
DispatchQueue.main.async {
print(data as Any)
print("success")
self.jsonParse {
print("success")
}
}
}catch {
print(error.localizedDescription)
}
}
}
task.resume()
}
}
Can you help me about my problem, thank you.
This is a very common mistake: You ignore the root object, a dictionary. With Decodable it's mandatory to decode the JSON from the top.
Add this struct
struct Response: Decodable {
let status: String
let totalResults: Int
let articles: [Article]
}
and decode
let result = try JSONDecoder().decode(Response.self, from: data!)
And never print(error.localizedDescription) in a Codable catch block. Always write this
} catch {
print(error)
}

Swift JSON with dynamic Keys

I am trying to parse JSON using Swift, which has dynamic keys. Tried several ways but still did not find the solution. Could you please help me ?
I am trying to parse NativeName, which is dynamic based on which language country name is present.
API: https://restcountries.com/v3.1/all
struct Objects: Codable {
let name: Name
let cca2 : String
let flag: String
}
struct Name: Codable {
let common, official: String
let nativeName: NativeName
}
struct NativeName: Codable {
var deu : Deu
}
struct Deu: Codable {
let official, common: String?
}
and here is JSON Model:
class ParsingService {
static let shared = ParsingService()
func fetchData() {
guard let url = URL(string: "https://restcountries.com/v3.1/all") else {
print("DEBUG: URL is nill")
return}
let session = URLSession.shared
let task = session.dataTask(with: url) { data, _, error in
guard let retrievedData = data, error == nil else {
print("DEBUG: Data is not available")
return}
print("DEBUG: Data is available \(retrievedData)")
guard let decodedData = self.JSONParsing(inputData: retrievedData) else {
print("DEBUG: Missing data")
return}
print("DEBUG: Data is there")
print("DEBUG: \(decodedData[0].cca2)")
print("DEBUG: \(decodedData[0].flag)")
print("DEBUG: \(decodedData[0].name.nativeName.deu.official)")
DispatchQueue.main.async {
print(decodedData.currencies)
}
}
task.resume()
}
func JSONParsing(inputData: Data)-> [Objects]? {
let decoder = JSONDecoder()
do {
let data = try? decoder.decode([Objects].self, from: inputData)
return data
} catch {
print("DEBUG: Cannot get data")
return nil
}
}
}
you could try this approach:
struct ContentView: View {
var body: some View {
Text("testing")
.onAppear {
fetchData() { results in
print("---> results: \(results.count) \n")
for i in 0..<3 {
print("---> results[\(i)]: \(results[i].name.nativeName)")
}
}
}
}
// todo deal with errors
func fetchData(completion: #escaping ([Objects]) -> Void) {
let url = URL(string: "https://restcountries.com/v3.1/all")
guard let url = url else { completion([]); return }
URLSession.shared.dataTask(with: url) { (data, response, error) in
guard let data = data else { completion([]); return }
do {
let results = try JSONDecoder().decode([Objects].self, from: data)
completion(results)
}
catch {
print("Error: \(error)")
completion([])
}
}.resume()
}
}
struct Objects: Codable {
let name: Name
let cca2 : String
let flag: String
}
struct Deu: Codable {
let official, common: String?
}
struct Name: Codable {
let common, official: String
let nativeName: NativeName? // <-- here
}
// -- here
struct NativeName: Codable {
var lang: [String: Deu]
init(from decoder: Decoder) throws {
let container = try decoder.singleValueContainer()
lang = try container.decode([String: Deu].self)
}
func encode(to encoder: Encoder) throws {
// todo
}
}
Note, you could also use a Tuple, such as var lang: (key: String, value: Deu)

how to parse this complex nested Json

The Json is valid but I m getting nil with below code and struct for the returned json.
problem encountered:
at JSonDecoder.decode() : it returned this error msg:
keyNotFound(CodingKeys(stringValue: "items", intValue: nil), Swift.DecodingError.Context(codingPath: [_JSONKey(stringValue: "items", intValue: nil), _JSONKey(stringValue: "Index 0", intValue: 0)], debugDescription: "No value associated with key CodingKeys(stringValue: "items", intValue: nil) ("items").", underlyingError: nil))
How to get these a) image b) location from the Json
Thanks
here the code
func getJsonMapData(){
guard let mapUrl = URL(string: "https://xxxxxx/traffic-images") else { return }
URLSession.shared.dataTask(with: mapUrl) { (data, response, error) in
guard error == nil else { return}
guard let data = data else { return}
//- problem:
do {
let LocationArrDict = try JSONDecoder().decode([String:[Location]].self, from: data)else{
print(LocationArrDict)
} catch {
print(error)
}
}.resume()
}
//------------- return Json String:
{
"items":[
{
"timestamp":"2020-12-05T08:45:43+08:00",
"cameras":[
{
"timestamp":"2020-11-05T08:42:43+08:00",
"image":"https://xxxxxxxxx/traffic-images/2020/12/2ab06cd8-4dcf-434c-b758-804e690e57db.jpg",
"location":{
"latitude":1.29531332,
"longitude":103.871146
},
"camera_id":"1001",
"image_metadata":{
"height":240,
"width":320,
"md5":"c9686a013f3a2ed4af61260811661fc4"
}
},
{
"timestamp":"2020-11-05T08:42:43+08:00",
"image":"https://xxxxxxxxxx/traffic-images/2020/12/9f6d307e-8b05-414d-b27d-bf1414aa2cc7.jpg",
"location":{
"latitude":1.319541067,
"longitude":103.8785627
},
"camera_id":"1002",
"image_metadata":{
"height":240,
"width":320,
"md5":"78060d8fbdd241adf43a2f1ae5d252b1"
}
},
........
{
"timestamp":"2020-12-05T08:42:43+08:00",
"image":"https://xxxxxx/traffic-images/2020/12/98f64fe6-5985-4a8a-852f-0be24b0a6271.jpg",
"location":{
"latitude":1.41270056,
"longitude":103.80642712
},
"camera_id":"9706",
"image_metadata":{
"height":360,
"width":640,
"md5":"f63d54176620fa1d9896fa438b3cc753"
}
}
]
}
],
"api_info":{
"status":"healthy"
}
}
//------------ struct for the return Json result:
// MARK: - Location
struct Location: Codable {
let items: [Item]
let apiInfo: APIInfo
enum CodingKeys: String, CodingKey {
case items
case apiInfo = "api_info"
}
}
// MARK: - APIInfo
struct APIInfo: Codable {
let status: String
}
// MARK: - Item
struct Item: Codable {
let timestamp: Date
let cameras: [Camera]
}
// MARK: - Camera
struct Camera: Codable {
let timestamp: Date
let image: String
let location: LocationClass
let cameraID: String
let imageMetadata: ImageMetadata
enum CodingKeys: String, CodingKey {
case timestamp, image, location
case cameraID = "camera_id"
case imageMetadata = "image_metadata"
}
}
// MARK: - ImageMetadata
struct ImageMetadata: Codable {
let height, width: Int
let md5: String
}
// MARK: - LocationClass
struct LocationClass: Codable {
let latitude, longitude: Double
}
``
Error #1: The type to be decoded is wrong it must be Location.self.
Error #2: To decode the ISO date as Date you have to add the .iso8601 date decoding strategy.
do {
let decoder = JSONDecoder()
decoder.dateDecodingStrategy = .iso8601
let locationArrDict = try decoder.decode(Location.self, from: data)
print(locationArrDict)
} catch {
print(error)
}
And you could decode the strings representing an URL directly to URL
You have to create your data models and describe them as the response you expect to receive. I will just give you a brief example based on your json how you would decode it.
First of all in order to decode a JSON using JSONDecoder your models have to conform to Decodable protocol. Then you have to create those models.
struct Item: Decodable {
let timestamp: Date
// let cameras: [...]
}
struct ApiInfo: Decodable {
enum Status: String, Decodable {
case healthy
}
let status: Status
}
struct Response: Decodable {
let items: [Item]
let apiInfo: ApiInfo
}
Then you have to configure your JSONDecoder and decode that JSON. As we can see clearly, your JSON uses snake_case naming convention and that is not the default one for JSONDecoder so you have to set it. Same also applies for the dates - it is used iso8601 standard of representation.
let jsonDecoder = JSONDecoder()
jsonDecoder.dateDecodingStrategy = .iso8601
jsonDecoder.keyDecodingStrategy = .convertFromSnakeCase
Finally, you have you decode it.
do {
let response = try jsonDecoder.decode(Response.self, from: jsonData)
print(response)
} catch {
print(error)
}

how to fetch data from a dictionary using swift 4 and struct?

struct family: Decodable {
let userId: [String:Int]
}
class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
let url = "http://supinfo.steve-colinet.fr/supfamily?action=login&username=admin&password=admin"
let urlobj = URL(string: url)
URLSession.shared.dataTask(with: urlobj!){(data, response, error) in
do{
let member = try JSONDecoder().decode(family.self, from: data!)
print(member)
}catch{
print(error)
}
}.resume()
}
}
Error:
keyNotFound(CodingKeys(stringValue: "userId", intValue: nil), Swift.DecodingError.Context(codingPath: [], debugDescription: "No value associated with key CodingKeys(stringValue: \"userId\", intValue: nil) (\"userId\").", underlyingError: nil))
The issue is that the userId key is nested in the JSON response. You need to decode the response from its root.
struct Family: Decodable {
let id: Int
let name: String
}
struct User: Codable {
let userId: Int
let lasName: String
let firstName: String
}
struct RootResponse: Codable {
let family: Family
let user: User
}
class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
let url = "http://supinfo.steve-colinet.fr/supfamily?action=login&username=admin&password=admin"
let urlobj = URL(string: url)
URLSession.shared.dataTask(with: urlobj!){(data, response, error) in
do{
let rootResponse = try JSONDecoder().decode(RootResponse.self, from: data!)
print(rootResponse)
}catch{
print(error)
}
}.resume()
}
}

Parsing JSON with Swift 4 Decodable

I have been getting the following error every time I try to parse JSON in my program. I can't seem to figure it out.
"Expected to decode String but found an array instead.", underlyingError: nil
Here is the code I have been struggling with:
struct Book: Decodable {
let id: Int
let title: String
let chapters: Int
var pages: [Page]?
}
struct Page: Decodable {
let id: Int
let text: [String]
}
struct Chapter: Decodable {
var chapterNumber: Int
}
func fetchJSON() {
let urlString = "https://api.myjson.com/bins/kzqh3"
guard let url = URL(string: urlString) else { return }
URLSession.shared.dataTask(with: url) { (data, _, err) in
if let err = err {
print("Failed to fetch data from", err)
return
}
guard let data = data else { return }
do {
let decoder = JSONDecoder()
let books = try decoder.decode([Book].self, from: data)
books.forEach({print($0.title)})
} catch let jsonErr {
print("Failed to parse json:", jsonErr)
}
}.resume()
}
Are you sure that's the real error message?
Actually the error is supposed to be
"Expected to decode String but found a dictionary instead."
The value for key text is not an array of strings, it's an array of dictionaries
struct Page: Decodable {
let id: Int
let text: [[String:String]]
}
The struct Chapter is not needed.
Alternatively write a custom initializer and decode the dictionaries containing the chapter number as key and the text as value into an array of Chapter
struct Book: Decodable {
let id: Int
let title: String
let chapters: Int
let pages: [Page]
}
struct Page: Decodable {
let id: Int
var chapters = [Chapter]()
private enum CodingKeys : String, CodingKey { case id, chapters = "text" }
init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
id = try container.decode(Int.self, forKey: .id)
var arrayContainer = try container.nestedUnkeyedContainer(forKey: .chapters)
while !arrayContainer.isAtEnd {
let chapterDict = try arrayContainer.decode([String:String].self)
for (key, value) in chapterDict {
chapters.append(Chapter(number: Int(key)!, text: value))
}
}
}
}
struct Chapter: Decodable {
let number : Int
let text : String
}
This is working:
import UIKit
class ViewController: UIViewController {
private var books = [Book]()
struct Book: Decodable {
let id: Int
let title: String
let chapters: Int
var pages: [Page]
}
struct Page: Decodable {
let id: Int
let text: [[String:String]]
}
override func viewDidLoad() {
super.viewDidLoad()
fetchJSON()
}
func fetchJSON() {
let urlString = "https://api.myjson.com/bins/kzqh3"
guard let url = URL(string: urlString) else { return }
URLSession.shared.dataTask(with: url) { data, response, error in
if error != nil {
print(error!.localizedDescription)
return
}
guard let data = data else { return }
do {
let decoder = JSONDecoder()
self.books = try decoder.decode([Book].self, from: data)
DispatchQueue.main.async {
for info in self.books {
print(info.title)
print(info.chapters)
print(info.pages[0].id)
print(info.pages[0].text)
print("-------------------")
}
}
} catch let jsonErr {
print("something wrong after downloaded: \(jsonErr) ")
}
}.resume()
}
}
// print
//Genesis
//50
//1
//[["1": "In the beg...]]
//-------------------
//Exodus
//40
//2
//[["1": "In the beginning God created...]]
//
If you need to print the value of each chapter in the book, I can use something like this:
import UIKit
class ViewController: UIViewController {
private var books = [Book]()
struct Book: Decodable {
let id: Int
let title: String
let chapters: Int
var pages: [Page]
}
struct Page: Decodable {
let id: Int
let text: [[String:String]]
}
override func viewDidLoad() {
super.viewDidLoad()
fetchJSON()
}
func fetchJSON() {
let urlString = "https://api.myjson.com/bins/kzqh3"
guard let url = URL(string: urlString) else { return }
URLSession.shared.dataTask(with: url) { data, response, error in
if error != nil {
print(error!.localizedDescription)
return
}
guard let data = data else { return }
do {
let decoder = JSONDecoder()
self.books = try decoder.decode([Book].self, from: data)
DispatchQueue.main.async {
for info in self.books {
print(info.title)
print(info.chapters)
print(info.pages[0].id)
//print(info.pages[0].text)
for cc in info.pages[0].text {
for (key, value) in cc {
print("\(key) : \(value)")
}
}
print("-------------------")
}
}
} catch let jsonErr {
print("something wrong after downloaded: \(jsonErr) ")
}
}.resume()
}
}
//Genesis
//50
//1
//1 : In the beginning God ...
//2 : But the earth became waste...
//.
//.
//.
//31 : And God saw everything...
//-------------------
//Exodus
//40
//2
//1 : In the beginning God...
//2 : But the earth became...
//.
//.
//.
//31 : And God saw everything