Parsing Json Show Error keyNotFound Swift - json

i want to Parsing a json but Xcode Show this message : keyNotFound(CodingKeys(stringValue: "Id", intValue: nil), Swift.DecodingError.Context(codingPath: [], debugDescription: "No value associated with key CodingKeys(stringValue: \"Id\", intValue: nil) (\"Id\").", underlyingError: nil))
JSON :
{
"result": [
{
"Id": 5,
"Title": "Test1",
"EnTitle": "Story and Novel"
},
{
"Id": 38,
"Title": "Test2",
"EnTitle": " Motivational"
}
],
"status": {
"message": "Confirm",
"success": true,
"systemDateTime": "2019-11-01T12:07:05+03:30",
"httpStatusCode": 200
}
}
Model :
struct Result : Decodable {
let Id : Int
let Title : String
let EnTitle : String
}
class ResultInitialiser {
let Id : Int
let Title : String
let EnTitle : String
init(Id:Int, Title:String, EnTitle: String) {
self.Id = Id
self.Title = Title
self.EnTitle = EnTitle
}
}
View Controller :
var genresFetch = [Result]()
var structGenresFetch = [ResultInitialiser]()
let headers : HTTPHeaders = ["Token" : "6f8652e3-d9d9-4b34-9455-0fa32e82ec58"]
AF.request(BASE_URL!, method: .get, headers: headers).response { (response) in
do {
self.genresFetch = [try JSONDecoder().decode(Result.self, from: response.data!)]
for eachProduct in self.genresFetch {
let recived_Data = ResultInitialiser(Id: eachProduct.Id, Title: eachProduct.Title, EnTitle: eachProduct.EnTitle)
self.structGenresFetch.append(recived_Data)
print(eachProduct.Title)
DispatchQueue.main.async {
self.tvMainApi.reloadData()
}
}
}catch {
print(error)
}
}

JSONDecoder will try to decode the JSON from the root. At the root level, there are only two keys, results and status. The Decodable type that you have passed to it, Result, has neither of those keys.
You need to create another struct:
struct Response {
let results: [Result]
}
And decode this struct instead:
do {
// notice the change to this line
self.genresFetch = try JSONDecoder().decode(Response.self, from: response.data!).results
...
}catch {
print(error)
}
IMO, you don't need the extra class called ResultInitialiser. What's wrong with just using the struct as your model?

"No value associated with key CodingKeys(stringValue: \"Id\", intValue: nil)
The root of the json contains result key only and doesn't have id or the other keys you submit with your Result struct , so You need
self.genresFetch = try JSONDecoder().decode(Root.self, from: response.data!)
print(self.genresFetch.result)
struct Root : Decodable {
let result : [Result]
}
struct Result: Decodable {
let id: Int
let title, enTitle: String
enum CodingKeys: String, CodingKey {
case id = "Id"
case title = "Title"
case enTitle = "EnTitle"
}
}

Related

Parsing issue : APIError: keyNotFound key CodingKeys(stringValue: "data", intValue: nil)

Unable to parse using codable structs for following json
{
"data": {
"listDeviceStateTable": {
"items": [
{
"Data": "{'state': -1, 'remainSec': 0}",
"PK": "DEVICE#144b584b-xxxx-xxxx-xxxx-1e584bdb1e8c",
"SK": "Station1"
},
{
"Data": "{'state': -1, 'remainSec': 0}",
"PK": "DEVICE#144b584b-xxxx-xxxx-xxxx-1e584bdb1e8c",
"SK": "Station2"
}
]
}
}
}
Error :
APIError: keyNotFound key CodingKeys(stringValue: "data", intValue:
nil) Caused by: keyNotFound(CodingKeys(stringValue: "data", intValue:
nil), Swift.DecodingError.Context(codingPath: [], debugDescription:
"No value associated with key CodingKeys(stringValue: "data",
intValue: nil) ("data").", underlyingError: nil)))
Model:
//MARK: DeviceState
struct DeviceState:Codable {
let data: DataClass
}
// MARK: - DataClass
struct DataClass: Codable {
let listDeviceStateTable: ListDeviceStateTable
}
// MARK: - ListDeviceStateTable
struct ListDeviceStateTable: Codable {
let items: [Item]
}
// MARK: - Item
struct Item: Codable {
let data, pk, sk: String
enum CodingKeys: String, CodingKey {
case data = "Data"
case pk = "PK"
case sk = "SK"
}
}
works well for me. This is the test code I used to show how to decode the json data into your structs. Of course Item->data is here decoded as a String, as per the json you show.
struct ContentView: View {
#State var devState: DeviceState?
var body: some View {
Group {
if let devFirst = devState?.data.listDeviceStateTable.items.first {
Text("pk: \(devFirst.pk)")
}
}
.onAppear {
let json = """
{
"data": {
"listDeviceStateTable": {
"items": [
{
"Data": "{'state': -1, 'remainSec': 0}",
"PK": "DEVICE#144b584b-xxxx-xxxx-xxxx-1e584bdb1e8c",
"SK": "Station1"
},
{
"Data": "{'state': -1, 'remainSec': 0}",
"PK": "DEVICE#144b584b-xxxx-xxxx-xxxx-1e584bdb1e8c",
"SK": "Station2"
}
]
}
}
}
"""
let data = json.data(using: .utf8)!
do {
devState = try JSONDecoder().decode(DeviceState.self, from: data)
print(devState)
} catch {
print("\(error)")
}
}
}
}
struct DeviceState:Codable {
let data: DataClass
}
// MARK: - DataClass
struct DataClass: Codable {
let listDeviceStateTable: ListDeviceStateTable
}
// MARK: - ListDeviceStateTable
struct ListDeviceStateTable: Codable {
let items: [Item]
}
// MARK: - Item
struct Item: Codable {
let data, pk, sk: String
enum CodingKeys: String, CodingKey {
case data = "Data"
case pk = "PK"
case sk = "SK"
}
}
to decode Data into something else than a String, eg a ItemData, you need to remove the double quotes,
and replace the single quote with double quotes (in the value not the key).
eg. "Data": "{'state': -1, 'remainSec': 0}", to "Data": {"state": -1, "remainSec": 0},
and use the following Item struct and decoding code:
// MARK: - Item
struct Item: Codable {
let data: ItemData
let pk, sk: String
enum CodingKeys: String, CodingKey {
case data = "Data"
case pk = "PK"
case sk = "SK"
}
init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
pk = try container.decode(String.self, forKey: .pk)
sk = try container.decode(String.self, forKey: .sk)
do {
let theString = try container.decode(String.self, forKey: .data)
let json = theString.replacingOccurrences(of: "\"", with: "").replacingOccurrences(of: "'", with: "\"")
data = try JSONDecoder().decode(ItemData.self, from: json.data(using: .utf8)!)
} catch DecodingError.typeMismatch {
data = ItemData(state: 0, remainSec: 0) // <-- todo
}
}
}
struct ItemData: Codable {
let state, remainSec: Int
}
You need one more struct to decode data inside Item like this
struct Item: Codable {
let data: ItemData
let pk, sk: String
enum CodingKeys: String, CodingKey {
case data = "Data"
case pk = "PK"
case sk = "SK"
}
}
struct ItemData: Codable {
let state, remainSec: Int
}

Parsing nested JSON using Decodable in Swift

I'm trying to parse this JSON response
{
"payload": {
"bgl_category": [{
"number": "X",
"name": "",
"parent_number": null,
"id": 48488,
"description": "Baustellenunterk\u00fcnfte, Container",
"children_count": 6
}, {
"number": "Y",
"name": "",
"parent_number": null,
"id": 49586,
"description": "Ger\u00e4te f\u00fcr Vermessung, Labor, B\u00fcro, Kommunikation, \u00dcberwachung, K\u00fcche",
"children_count": 7
}]
},
"meta": {
"total": 21
}
}
What I'm interested to view in my TableViewCell are only the number and description
here is what I tried to far:
//MARK: - BGLCats
struct BGLCats: Decodable {
let meta : Meta!
let payload : Payload!
}
//MARK: - Payload
struct Payload: Decodable {
let bglCategory : [BglCategory]!
}
//MARK: - BglCategory
struct BglCategory: Decodable {
let descriptionField : String
let id : Int
let name : String
let number : String
let parentNumber : Int
}
//MARK: - Meta
struct Meta: Decodable {
let total : Int
}
API request:
fileprivate func getBgls() {
guard let authToken = getAuthToken() else {
return
}
let headers = [
"content-type" : "application/json",
"cache-control": "no-cache",
"Accept" : "application/json",
"Authorization": "\(authToken)"
]
let request = NSMutableURLRequest(url: NSURL(string: "https://api-dev.com")! as URL, cachePolicy: .useProtocolCachePolicy, timeoutInterval: 10.0)
request.allHTTPHeaderFields = headers
let endpoint = "https://api-dev.com"
guard let url = URL(string: endpoint) else { return }
URLSession.shared.dataTask(with: request as URLRequest) {(data, response, error) in
guard let data = data else { return }
do {
let BGLList = try JSONDecoder().decode(BglCategory.self, from: data)
print(BGLList)
DispatchQueue.main.sync { [ weak self] in
self?.number = BGLList.number
self?.desc = BGLList.descriptionField
// self?.id = BGLList.id
print("Number: \(self?.number ?? "Unknown" )")
print("desc: \(self?.desc ?? "Unknown" )")
// print("id: \(self?.id ?? 0 )")
}
} catch let jsonError {
print("Error Serializing JSON:", jsonError)
}
}.resume()
}
But I'm getting error:
Error Serializing JSON: keyNotFound(CodingKeys(stringValue: "childrenCount", intValue: nil), Swift.DecodingError.Context(codingPath: [], debugDescription: "No value associated with key CodingKeys(stringValue: \"childrenCount\", intValue: nil) (\"childrenCount\").", underlyingError: nil))
There are a few issues here.
You created the model (mostly) correctly, but there're just two mismatches:
struct BglCategory: Decodable {
let description : String // renamed, to match "description" in JSON
let parentNum: Int? // optional, because some values are null
// ...
}
Second issue is that your model properties are camelCased whereas JSON is snake_cased. JSONDecoder has a .convertFromSnakeCase startegy to automatically handle that. You need to set it on the decoder prior to decoding.
Third issue is that you need to decode the root object BGLCats, instead of BglCategory.
let decoder = JSONDecoder()
decoder.keyDecodingStrategy = .convertFromSnakeCase // set the decoding strategy
let bglCats = try decoder.decode(BGLCats.self, from: data) // decode BGLCats
let blgCategories = bglCats.payload.bglCategory
The problem is that JSONDecoder doesn't know that for example bglCategory is represented in JSON payload as bgl_category. If the JSON name isn't the same as the variable name you need to implement CodingKeys to your Decodable
In your case:
struct BglCategory: Decodable {
let descriptionField : String
let id : Int
let name : String
let number : String
let parentNumber : Int?
enum CodingKeys: String, CodingKey {
case id, name, number
case descriptionField = "description"
case parentNumber = "parent_number"
}
}
struct Payload: Decodable {
let bglCategory : [BglCategory]!
enum CodingKeys: String, CodingKey {
case bglCategory = "bgl_category"
}
}

Swift Json KeyNotFound

Please help make this work I've been trying to figure out JSON and Swift for a week and facing this problem for 5 hours so far today.
Error Received
ERROR WHEN DECODING JSON keyNotFound(CodingKeys(stringValue: "MP", intValue: nil), Swift.DecodingError.Context(codingPath: [], debugDescription: "No value associated with key CodingKeys(stringValue: "MP", intValue: nil) ("MP").", underlyingError: nil))
YEAR IS NIL
Code
struct UserDay: Codable {
let MP: UserMP
let WP: UserWP
}
struct UserMP: Codable {
let M: [UserM]
let S: [UserS]
}
struct UserM : Codable {
let title: String
let description: String
let time: String
}
struct UserS : Codable {
let title: String
let description: String
let time: String
}
struct UserWP: Codable {
let WP: [WPData]
}
struct WPData: Codable {
let title: String
let values: [Int]
}
class LogDataHandler {
public func grabJSONInfo(){
guard let jsonURL = Bundle(for: type(of: self)).path(forResource: "newLogData", ofType: "json") else { return }
guard let jsonString = try? String(contentsOf: URL(fileURLWithPath: jsonURL), encoding: String.Encoding.utf8) else { return }
// print(jsonString)
// Print Info for TESTING
var year: UserDay?
do {
year = try JSONDecoder().decode(UserDay.self, from: Data(jsonString.utf8))
} catch {
print("ERROR WHEN DECODING JSON \(error)")
}
guard let results = year else {
print("YEAR IS NIL")
return
}
print(results)
}
}
JSON
{
"01/01/2020": {
"MP" : {
"M" : [
{"title" : "m1", "description" : "1", "time" : "12:30pm"},
{"title" : "m2", "description" : "2", "time" : "1:30pm"},
{"title" : "m3", "description" : "3", "time" : "2:30pm"}
],
"S" : [
{"title" : "s1", "description" : "1", "time" : "1pm"}
]
},
"WP" : [
{ "title" : "abc", "values" : [12, 10, 6]},
{ "title" : "def", "values" : [8]}
]
},
"01/29/2020": {
"MP" : {
"M" : [{"title" : "m1", "description" : "1", "time" : "12:30pm"}],
"S" : [{"title" : "s1", "description" : "1", "time" : "12:30pm"}]
},
"WP" :[{ "title" : "def", "values" : [8]}]
}
}
First, replace let WP: UserWP with an array:
struct UserDay: Codable {
let MP: UserMP
let WP: [WPData]
}
Then decode [String:UserDay] instead of UserDay:
try JSONDecoder().decode([String:UserDay].self, from: Data(jsonString.utf8))
Note that this will return a dict with key-value pairs "01/01/2020":UserDay object.
This means you can't assign a dictionary [String: UserDay] to a UserDay variable. Instead you can access the dict by a key which is some date (eg. "01/01/2020") and then assign it to your UserDay variable:
let result = try JSONDecoder().decode([String:UserDay].self, from: Data(jsonString.utf8))
let someYear = result["01/01/2020"]
Note that someYear will be optional so you may want to provide a default value or force-unwrap it.

Swift IOS Failed to access json array data using Codable

How to access JSON using Codable. this is my sample json.
{
"status": "success",
"message": "Data received successfully",
"course": {
"id": 1,
"description": "something",
"name": "ielts",
"attachments": [
{
"id": 809,
"attachment": "https:--",
"file_name": "syllabus.pdf",
"description": "course",
},
{
"id": 809,
"attachment": "https:--",
"file_name": "syllabus.pdf",
"description": "course",
}]
"upcased_name": "IELTS"
}
}
This is my code.
struct ResponseObject: Codable {
let course: [Course]
}
struct Course: Codable {
let id: Int
let name: String
let description: String
let attachments: [Attachments]
}
struct Attachments: Codable {
let id: Int
let attachment: String
let file_name: String
let description: String
let about: String
}
var course: [Course] = []
This is my api call.
func fetchUserData() {
let headers: HTTPHeaders = [
"Authorization": "Token token="+UserDefaults.standard.string(forKey: "auth_token")!,
"Accept": "application/json"
]
let params = ["course_id" : "1"] as [String : AnyObject]
self.showSpinner("Loading...", "Please wait!!")
DispatchQueue.global(qos: .background).async {
AF.request(SMAConstants.courses_get_course_details , parameters: params, headers:headers ).responseDecodable(of: ResponseObject.self, decoder: self.decoder) { response in
DispatchQueue.main.async {
self.hideSpinner()
guard let value = response.value else {
print(response.error ?? "Unknown error")
return
}
self.course = value.course
}
}
}
}
}
I am getting following error.
responseSerializationFailed(reason:
Alamofire.AFError.ResponseSerializationFailureReason.decodingFailed(error:
Swift.DecodingError.typeMismatch(Swift.Array,
Swift.DecodingError.Context(codingPath: [CodingKeys(stringValue:
"course", intValue: nil)], debugDescription: "Expected to decode
Array but found a dictionary instead.", underlyingError: nil))))
Your model object does not match the JSON structure. Try this instead:
struct ResponseObject: Codable {
let status, message: String
let course: Course
}
struct Course: Codable {
let id: Int
let courseDescription, name: String
let attachments: [Attachment]
let upcasedName: String
enum CodingKeys: String, CodingKey {
case id
case courseDescription = "description"
case name, attachments
case upcasedName = "upcased_name"
}
}
struct Attachment: Codable {
let id: Int
let attachment, fileName, attachmentDescription: String
enum CodingKeys: String, CodingKey {
case id, attachment
case fileName = "file_name"
case attachmentDescription = "description"
}
}
and to download and parse this with plain Swift and Foundation, use code like this:
let url = URL(string: SMAConstants.courses_get_course_details)!
let task = URLSession.shared.dataTask(with: url) { data, response, error in
if let error = error {
print(error)
return
}
if let data = data {
do {
let response = try JSONDecoder().decode(ResponseObject.self, from: data)
// access your data here
} catch {
print(error)
}
}
}
task.resume()

typeMismatch on decodable Type when the type is correct

When I hit my configuration API with Postman I am given the following json response back. In this response the two apiVersion keys are numbers and not strings.
{
"data": {
"availability": {
"auth": true,
"ab": true,
"cd": true
},
"helloWorldConfiguration": {
"apiKey": "abcefg",
"rootUrl": "https://foo",
"apiVersion": 3
},
"fooBarConfiguration": {
"baseUrl": "https://foo",
"apiVersion": 1,
"privateApiPath": "",
"publicApiPath": "dev",
"tokenPath": ""
}
},
"errors": []
}
When I try to decode it it fails with a typeMismatch error. When I output the contents of the response, I see the following which looks fine to me.
data = {
availability = {
auth = 1;
ab = 1;
cd = 1;
};
helloWorldConfiguration = {
apiVersion = 1;
baseUrl = "https://foo";
privateApiPath = "";
publicApiPath = dev;
tokenPath = "";
};
fooBarConfiguration = {
apiKey = abcefg;
apiVersion = 3;
rootUrl = "https://foo";
};
};
errors = (
);
The error given to me indicates that data.helloWorldConfiguration.apiVersion is of type string instead of int. We can see from the original HTTP response I get from Postman that's not the case.
typeMismatch(Swift.Int, Swift.DecodingError.Context(codingPath: [CodingKeys(stringValue: "data", intValue: nil), CodingKeys(stringValue: "helloWorldConfiguration", intValue: nil), CodingKeys(stringValue: "apiVersion", intValue: nil)], debugDescription: "Expected to decode Int but found a string/data instead.", underlyingError: nil))
21:17:40 ERROR Unable to decode the response data into a model representation.
My model represents those properties as integers so it would appear that it receives the response and considers those numbers to be strings, which they're not.
public struct ServerConfiguration: Decodable {
let availability: AvailabilityConfiguration
let helloWorldConfiguration: HelloWorldConfiguration
let fooBarConfiguration: FooBarConfiguration
init(availability: AvailabilityConfiguration, helloWorldConfiguration: HelloWorldConfiguration, fooBarConfiguration: FloatSinkConfiguration) {
self.availability = availability
self.helloWorldConfiguration = helloWorldConfiguration
self.fooBarConfiguration = fooBarConfiguration
}
}
public struct FooBarConfiguration: Decodable {
let baseUrl: String
let apiVersion: Int
let privateApiPath: String
let publicApiPath: String
let tokenPath: String
init(baseUrl: String, apiVersion: Int, privateApiPath: String, publicApiPath: String, tokenPath: String) {
self.baseUrl = baseUrl
self.apiVersion = apiVersion
self.privateApiPath = privateApiPath
self.publicApiPath = publicApiPath
self.tokenPath = tokenPath
}
}
public struct AvailabilityConfiguration: Decodable {
let auth: Bool
let ab: Bool
let cd: Bool
init(auth: Bool, ab: Bool, cd: Bool) {
self.auth = auth
self.ab = ab
self.cd = cd
}
}
public struct HelloWorldConfiguration: Codable {
let apiKey: String
let rootUrl: String
let apiVersion: Int
init(apiKey: String, rootUrl: String, apiVersion: Int) {
self.apiKey = apiKey
self.rootUrl = rootUrl
self.apiVersion = apiVersion
}
}
As you can see my apiVersion members are both of type integer along with the json response. What am I doing wrong here? I assume what's happening is Swift is considering the numbers in the json string, regardless of how they're actually represented in the json. Is that the case?
Edit to show utf8 string of Alamofire response data
21:44:06 INFO GET: https:foo/configuration
{
"data" : {
"availability" : {
"auth" : true,
"ab" : true,
"cb" : true
},
"helloWorldConfiguration" : {
"apiKey" : "abcd",
"rootUrl" : "https://foo",
"apiVersion" : "3"
},
"fooBarConfiguration" : {
"baseUrl" : "https://foo",
"apiVersion" : "1",
"privateApiPath" : "",
"publicApiPath" : "dev",
"tokenPath" : "auth/token"
}
},
"errors" : []
}
It would seem that despite the API correctly returning apiVersion as a number, Swift is turning it into a string. Am I decoding it incorrectly?
func getRoute<TResponseData: Decodable>(route:String, completion: #escaping (TResponseData) -> Void) throws {
let headers = try! self.getHeaders(contentType: ContentType.json)
let completeUrl: String = self.getUrl(route: route, requestUrl: nil)
logger.info("GET: \(completeUrl)")
Alamofire.request(
completeUrl,
method: .get,
parameters: nil,
encoding: JSONEncoding.default,
headers: headers)
.validate()
.responseJSON { (response) -> Void in
self.logger.info("GET Response: \(String(describing:response.response?.statusCode))")
switch response.result {
case .success(_):
let decoder = JSONDecoder()
decoder.dateDecodingStrategy = .custom(Date.toFooBarDate)
do {
let result = try decoder.decode(TResponseData.self, from: response.data!)
completion(result)
} catch DecodingError.dataCorrupted(let error) {
self.logger.error(error.underlyingError!)
return
} catch {
print(response.result.value!)
print(error)
self.logger.error("Unable to decode the response data into a model representation.")
return
}
}
}
I checked in my playground and it seems that everything is working fine.To find the real issue i think you are required to provide the real url from where you are getting json and can be checked with alamofire
import Foundation
let json = """
{
"data": {
"availability": {
"auth": true,
"ab": true,
"cd": true
},
"helloWorldConfiguration": {
"apiKey": "abcefg",
"rootUrl": "https://foo",
"apiVersion": 3
},
"fooBarConfiguration": {
"baseUrl": "https://foo",
"apiVersion": 1,
"privateApiPath": "",
"publicApiPath": "dev",
"tokenPath": ""
}
},
"errors": []
}
"""
let data = json.data(using: .utf8)
struct Response : Codable {
let data : Data?
let errors : [String]?
}
struct Availability : Codable {
let auth : Bool?
let ab : Bool?
let cd : Bool?
}
struct Data : Codable {
let availability : Availability?
let helloWorldConfiguration : HelloWorldConfiguration?
let fooBarConfiguration : FooBarConfiguration?
}
struct FooBarConfiguration : Codable {
let baseUrl : String?
let apiVersion : Int?
let privateApiPath : String?
let publicApiPath : String?
let tokenPath : String?
}
struct HelloWorldConfiguration : Codable {
let apiKey : String?
let rootUrl : String?
let apiVersion : Int?
}
let decoder = JSONDecoder()
let response = try decoder.decode(Response.self, from: data!)
print(response)
And here is the response
Response(data: Optional(__lldb_expr_11.Data(availability: Optional(__lldb_expr_11.Availability(auth: Optional(true), ab: Optional(true), cd: Optional(true))), helloWorldConfiguration: Optional(__lldb_expr_11.HelloWorldConfiguration(apiKey: Optional("abcefg"), rootUrl: Optional("https://foo"), apiVersion: Optional(3))), fooBarConfiguration: Optional(__lldb_expr_11.FooBarConfiguration(baseUrl: Optional("https://foo"), apiVersion: Optional(1), privateApiPath: Optional(""), publicApiPath: Optional("dev"), tokenPath: Optional(""))))), errors: Optional([]))