How to fix the "no value associated" error with CodingKeys? - json

I am trying to decode the API from the API below, but I am still getting the error below:
keyNotFound(CodingKeys(stringValue: "resources", intValue: nil), Swift.DecodingError.Context(codingPath: [_JSONKey(stringValue: "resources", intValue: nil)], debugDescription: "No value associated with key CodingKeys(stringValue: \"resources\", intValue: nil) (\"resources\").", underlyingError: nil))
API Address: https://age-of-empires-2-api.herokuapp.com/docs/
I have already tried reviewing my structs numerous times, and trying the call code within different levels of the API, but still cannot get the data. I have also tried calling the objects from different levels with the print(resources.XXX) format.
This is my first data call from the API:
{
"resources": {
"civilizations": "https://age-of-empires-2-api.herokuapp.com/api/v1/civilizations",
"units": "https://age-of-empires-2-api.herokuapp.com/api/v1/units",
"structures": "https://age-of-empires-2-api.herokuapp.com/api/v1/structures",
"technologies": "https://age-of-empires-2-api.herokuapp.com/api/v1/technologies"
}
}
These are the first two levels of the structs:
// MARK: - Resources
struct Resources: Codable {
let resources: [String : ResourcesList]
enum CodingKeys: String, CodingKey {
case resources
}
}
// MARK: - ResourcesList
struct ResourcesList: Codable {
let civilizations: CivilizationList
let units: UnitList
let structures: StructureList
let technologies: TechnologyList
enum CodingKeys: String, CodingKey {
case civilizations, units, technologies, structures
}
}
Below these structs, I have implemented the models as indicated in the API website, e.g., CivilizationList, Civilization etc.
This is my call code:
let jsonUrl = "https://age-of-empires-2-api.herokuapp.com/api/v1"
guard let url = URL(string: jsonUrl) else { return }
URLSession.shared.dataTask(with: url) { (data, response, error) in
guard let data = data else { return }
let dataAsString = String(data: data, encoding: .utf8)
do {
let decoder = JSONDecoder()
let resources = try decoder.decode([String : Resources].self, from: data)
print(resources)
} catch {
print(error)
}
print(dataAsString!)
}.resume()
I have reviewed all the other threads here about the same error code, tried stuff, but there is probably something very basic that I am missing, unfortunately I am too much of a beginner to notice it. Any help is appreciated.

Model should be
// MARK: - Empty
struct Resources: Codable {
let resources: ResourcesList
}
// MARK: - Resources
struct ResourcesList: Codable {
let civilizations, units, structures, technologies: String
}
Decode
let resources = try decoder.decode(Resources.self, from: data)
as civilizations, units, structures, technologies are strings not models
OR
let resources = try decoder.decode([String : ResourcesList].self, from: data)

Related

Server Response Not matching model (no records) in Swift

sorry if the question is vague but I am trying to be as expressive as possible.
I have the following model:
struct Posts: Codable, Identifiable {
let id: String
let title: String
let content: String
enum CodingKeys: String, CodingKey {
case id = "_id"
case title
case content
}
}
the server response if a post is found would be the same model no issues because the JSON matches the model .
but if the server returns an error post not found, this would be the response JSON:
{
"error": "No records found"
}
I receive the following when this happens:
keyNotFound(CodingKeys(stringValue: "id", intValue: nil), Swift.DecodingError.Context(codingPath: [], debugDescription: "No value associated with key CodingKeys(stringValue: "id", intValue: nil) ("id").", underlyingError: nil))
what would be the best approach to handle this issue?
UPDATE:
Thank you jnpdx!
So, I did a ErrorResponse Struct, and it did catch the error response like so:
struct ErrorResponse: Codable {
let error: String
enum CodingKeys: String, CodingKey {
case error
}
}
So in my APIServices file, how do I handle this?
// this is what gets the Post data
let decodedData = try JSONDecoder().decode(Post?.self, from: data)
//Do I need another JSONDecoder to also catch the error below the above line like this?
let decodedDataError = try JSONDecoder().decode(ErrorResponse?.self, from: data)
In the comments, we discussed creating a struct to model the error, which it looks like you've done. To address your followup, no, you don't need a separate JSONDecoder. You also probably should not be decoding optionals.
There's not just a single way to do this correctly, but your function might look something like this:
let decoder = JSONDecoder()
do {
let post = try decoder.decode(Post.self, from: data)
//handle the post
} catch {
//try to decode an error
if let error = try? decoder.decode(ErrorResponse.self, from: data) {
//handle an API error
}
//handle an unknown error
}

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)
}

Decoding Error - keyNotFound when parsing JSON data

I am using NASA API in my iOS application for getting some images. My response from the server looks like:
{
"date": "2014-02-04T03:30:01",
"id": "LC8_L1T_TOA/LC81270592014035LGN00",
"resource": {
"dataset": "LC8_L1T_TOA",
"planet": "earth"
},
"service_version": "v1",
"url": "https://earthengine.googleapis.com/api/thumb?thumbid=bc77b079c8ecd07cd668c576c22b83a4&token=a16639b0d38dd68c586c24a6ee5299d9"
}
My request url is:
https://api.nasa.gov/planetary/earth/imagery/?lon=100.75&lat=1.5&date=2014-02-01&api_key=DEMO_KEY
My struct for decoding this response is:
import Foundation
// MARK: - EarthImages
struct EarthImages: Codable {
let date: String
let id: String
let resource: Resource
let serviceVersion: String
let url: String
private enum CodingKeys: String, CodingKey {
case date = "date"
case id = "id"
case resource = "resource"
case serviceVersion = "service_version"
case url = "url"
}
}
The problem is - when I am trying to decode my response using the following code
URLSession.shared.dataTask(with: url) { data, response, error in
if let data = data {
do {
let earthImages = try JSONDecoder().decode(EarthImages.self, from: data)
print(earthImages.url)
}
catch let error{
print(error)
}}
}.resume()
I get in console.
keyNotFound(CodingKeys(stringValue: "date", intValue: nil), Swift.DecodingError.Context(codingPath: [], debugDescription: "No value associated with key CodingKeys(stringValue: \"date\", intValue: nil) (\"date\").", underlyingError: nil))
I used PAW to check if I get correct response, and it works, so the problem more likely is in my code. How can I resolve this issue?
It... looks fine? (Assuming Resource is also Decodable, but that would be a separate issue). Perhaps the error is actually telling you the truth, you may be attempting to decode a JSON blob that does not have a date value.
You can explicitly see what we're attempting to decode with an added print just before we attempt to decode:
if let data = data {
print(String(data: data, encoding: .utf8)!)
do {
...
Then separately, if the date field is not guaranteed to exist in every response, you should make it optional:
struct EarthImages: Codable {
let date: String?
let id: String
let resource: Resource
let serviceVersion: String
let url: String
}
Small note, string enums dont need to be redeclared if its exactly the same as the enum case:
enum CodingKeys: String, CodingKey {
case date // "date" is implied
case id
case resource
case serviceVersion = "service_version"
case url
}
Another fun fact: JSONDecoder can also convert from snake case automatically without having to define CodingKeys if every key is consistent.
So you can also do:
struct EarthImages: Codable {
let date: String
let id: String
let resource: Resource
let serviceVersion: String
let url: String
}
...
do {
let decoder = JSONDecoder()
decoder.keyDecodingStrategy = .convertFromSnakeCase
let earthImages = try decoder.decode(EarthImages.self, from: data)
}

Swift 4 Decodable - No value associated with key CodingKeys [duplicate]

This question already has answers here:
How do I use custom keys with Swift 4's Decodable protocol?
(4 answers)
Closed 3 years ago.
I'm decoding a JSON response in my Swift App, and the code used to work till it decided to stop working.
this is my json reposnse
{
"foods": [
{
"food_name": "Milk Chocolate",
"brand_name": "Snickers",
"serving_weight_grams": 41.7,
"nf_calories": 212.3,
"nf_total_fat": 11.6,
"nf_saturated_fat": 4,
"nf_total_carbohydrate": 22.7,
"nf_protein": 3.9
}
]
}
And this is the code to decode my json
let task = URLSession.shared.dataTask(with: request) { (data, response, error) in
guard let data = data else { return }
print(String(data: data, encoding: .utf8)!)
do {
//Decode dataResponse received from a network request
let decoder = JSONDecoder()
let foods = try decoder.decode(JSONFoods.self, from: data) //Decode JSON Response Data
self.jsonfood = foods.JSONFood[0]
print(self.jsonfood!)
} catch let parsingError {
print("Error", parsingError)
}
}
task.resume()
And my Structs are
struct JSONFoods: Decodable {
var JSONFood: [JSONFood]
}
struct JSONFood: Decodable{
var food_name: String
var brand_name: String
var nf_calories: Int
var nf_protein: Int
var nf_total_fat: Int
var nf_total_carbohydrate: Int
var serving_weight_grams: Int
}
And the error message I get is this
keyNotFound(CodingKeys(stringValue: "JSONFood", intValue: nil), Swift.DecodingError.Context(codingPath: [], debugDescription: "No value associated with key CodingKeys(stringValue: "JSONFood", intValue: nil) ("JSONFood").", underlyingError: nil))
And if i get replace decode(JSONFoods.self, from: data) with decode(JSONFood.self, from: data)
I get this error message
keyNotFound(CodingKeys(stringValue: "food_name", intValue: nil), Swift.DecodingError.Context(codingPath: [], debugDescription: "No value associated with key CodingKeys(stringValue: "food_name", intValue: nil) ("food_name").", underlyingError: nil))
I searched everywhere with no luck, any help is very appreciated
You need
struct Root: Codable {
let foods: [Food]
}
struct Food: Codable {
let foodName: String?
let brandName: String
let servingWeightGrams, nfCalories, nfTotalFat: Double
let nfSaturatedFat: Int
let nfTotalCarbohydrate, nfProtein: Double
enum CodingKeys: String, CodingKey {
case foodName = "food_name"
case brandName = "brand_name"
case servingWeightGrams = "serving_weight_grams"
case nfCalories = "nf_calories"
case nfTotalFat = "nf_total_fat"
case nfSaturatedFat = "nf_saturated_fat"
case nfTotalCarbohydrate = "nf_total_carbohydrate"
case nfProtein = "nf_protein"
}
}
First : you make JSONFood while it should be foods
Second :food_name doesn't exist in current json root so this will fail
let foods = try decoder.decode(JSONFoods.self, from: data) //Decode JSON Response Data
In case to take advantage of convertFromSnakeCase
let str = """
{"foods":[{"food_name":"Milk Chocolate","brand_name":"Snickers","serving_weight_grams":41.7,"nf_calories":212.3,"nf_total_fat":11.6,"nf_saturated_fat":4,"nf_total_carbohydrate":22.7,"nf_protein":3.9}]}
"""
do {
let res = JSONDecoder()
res.keyDecodingStrategy = .convertFromSnakeCase
let ss = try res.decode(Root.self, from:Data(str.utf8))
print(ss)
}
catch {
print(error)
}
struct Root: Codable {
let foods: [Food]
}
struct Food: Codable {
let foodName: String?
let brandName: String
let servingWeightGrams, nfCalories, nfTotalFat: Double
let nfSaturatedFat: Int
let nfTotalCarbohydrate, nfProtein: Double
}

URLSession Type Mismatch Error

I'm getting an Type Mismatch error when trying to parse json with Swift jSon Decoder.
I just can't find a way to parse it normally.
The Error:
typeMismatch(Swift.String, Swift.DecodingError.Context(codingPath:
[CodingKeys(stringValue: "data", intValue: nil),
CodingKeys(stringValue: "itemArr", intValue: nil),
CodingKeys(stringValue: "price", intValue: nil)], debugDescription:
"Expected to decode String but found a number instead.",
underlyingError:
nil))
The Decoder Code:
func getDealDetails(id : String ,completion : #escaping ()->())
{
let jsonUrl = "https://androidtest.inmanage.com/api/1.0/android/getDeal_\(id).txt"
guard let url = URL(string: jsonUrl) else { return }
URLSession.shared.dataTask(with: url) { (data, response, error) in
guard let data = data else { return }
do
{
let deal = try JSONDecoder().decode(ResultDetails.self, from: data)
AppManager.shared.dealToShow = deal.data.itemArr
}catch
{
print("There's an error: \(error)")
}
completion()
}.resume()
}
}
And The Classes:
First:
class ResultDetails : Decodable
{
let data : DataDetails
init(data : DataDetails) {
self.data = data
}
}
Second:
class DataDetails : Decodable
{
let itemArr: ItemArr
init(itemArr: ItemArr) {
self.itemArr = itemArr
}
}
And Third:
class ItemArr : Decodable
{
let id, title, price, description: String
let image: String
let optionsToShow: Int
let gps: Gps
let website, phone: String
init(id: String, title: String, price: String, description: String, image: String, optionsToShow: Int, gps: Gps, website: String, phone: String) {
self.id = id
self.title = title
self.price = price
self.description = description
self.image = image
self.optionsToShow = optionsToShow
self.gps = gps
self.website = website
self.phone = phone
}
I think I tried everything for the last 6 hours to fix it, please help!
EDIT:
I put a wrong third class. now it's the right one
The error message is wrong. According to the JSON link and your classes it's supposed to be
CodingKeys(stringValue: "price", intValue: nil)], debugDescription:
Expected to decode Int but found a string/data instead.,
However it's going to tell you exactly what's wrong: The value for key price is a String rather than an Int.
You have to read the JSON carefully. The format is very simple. For example everything in double quotes is String, there is no exception.
Your data structure is too complicated. Use structs and drop the initializers. By the way there is a typo ItemsArr vs. ItemArr and there is no key orderNum.
The key image can be decoded as URL. This is sufficient
struct ResultDetails : Decodable {
let data : DataDetails
}
struct DataDetails : Decodable {
let itemArr: ItemArr
}
struct ItemArr : Decodable {
let id, title: String
let price: String
let image: URL
// let orderNum: Int
}
Specify the CodingKeys only if you want to map the keys. In your case you can even omit the CodingKeys, use theconvertFromSnakeCase strategy instead.
decoder.keyDecodingStrategy = .convertFromSnakeCase
Yes you have an error
let price: Int should be let price: String because it string in Json "price": "896" -- > String
Example for Int "optionsToShow":1 --> this is Int
Here is complete code you can use
import Foundation
struct ResultDetails: Codable {
let data: DataDetails
}
struct DataDetails: Codable {
let itemArr: ItemArr
}
struct ItemArr: Codable {
let id, title, price, description: String
let image: String
let optionsToShow: Int
let gps: Gps
let website, phone: String
}
struct Gps: Codable {
let lat, lon: String
}
// MARK: Convenience initializers
extension ResultDetails {
init(data: Data) throws {
self = try JSONDecoder().decode(ResultDetails.self, from: data)
}
init(_ json: String, using encoding: String.Encoding = .utf8) throws {
guard let data = json.data(using: encoding) else {
throw NSError(domain: "JSONDecoding", code: 0, userInfo: nil)
}
try self.init(data: data)
}
func jsonData() throws -> Data {
return try JSONEncoder().encode(self)
}
func jsonString(encoding: String.Encoding = .utf8) throws -> String? {
return String(data: try self.jsonData(), encoding: encoding)
}
}