Parsing JSON response using Codable gives error in swift - ios

I'm trying to parse JSON response using Codable but it gives me error.
I tried to refer from the below stack overflow link but did not work.
how do I parse Any in dictionary using swift
Below is my code, not sure where I'm wrong in this.
> enum JSONError: String,Error {
> case NoData = "ERROR: no data"
> case ConversionFailed = "ERROR: conversion from JSON failed"
> }
struct Owner : Decodable {
let full_name : String
let html_url:String
let follower:follower
}
struct follower : Decodable {
let followers_url : String
}
func jsonParser() {
let urlPath = "https://api.github.com/search/repositories?q=language:ruby&sort=stars&order=desc"
guard let endpoint = NSURL(string: urlPath) else {
print("Error creating endpoint")
return
}
let request = NSMutableURLRequest(url:endpoint as URL)
URLSession.shared.dataTask(with: request as URLRequest) { (data, response, error) in
do {
guard let data = data else {
throw JSONError.NoData
}
let jsonResponse = try JSONSerialization.jsonObject(with:
data)
let entries = try! JSONDecoder().decode([Owner].self, from: jsonResponse as! Data)
print(jsonResponse)
} catch let error as JSONError {
print(error.rawValue)
} catch let error as NSError {
print(error.debugDescription)
}
}.resume()
}
I need to get 3 information from this response - full name, html url and followers.
Link for web api
https://api.github.com/search/repositories?q=language:ruby
Please latest have a look at the code.
Below is the error message :
'__NSDictionaryI' (0x102965a98) to 'NSData' (0x102964580). 2019-02-09
16:17:42.062971+0530 PhotoViewwer[13342:259997] Could not cast value
of type '__NSDictionaryI' (0x102965a98) to 'NSData' (0x102964580).
Thanks

Please learn to read JSON. It's pretty easy. There are only two collection types, array ([]) and dictionary ({})
Your structs are wrong.
In the root dictionary of the JSON there is an array of dictionaries for key items.
In each dictionary there are keys full_name and owner (here is the location of the Owner struct).
A dictionary for key follower does not exist.
These structs represent the JSON correctly
struct Response : Decodable {
let items : [Item]
}
struct Item : Decodable {
let fullName : String
let owner : Owner
}
struct Owner : Decodable {
let htmlUrl : URL // URL strings can be decoded directly into URL
let followersUrl : URL
}
Add a completion handler to your function and use an enum as result type. The failure case returns all real errors. An URLRequest is redundant. Just pass the URL. The JSONSerialization line is pointless.
The convertFromSnakeCase strategy converts snake_cased keys to camelCased struct members
enum Result {
case success(Response), failure(Error)
}
func jsonParser(completion: #escaping (Result) -> Void) {
let endpoint = URL(string:"https://api.github.com/search/repositories?q=language:ruby&sort=stars&order=desc")!
URLSession.shared.dataTask(with: endpoint) { (data, response, error) in
if let error = error { completion(.failure(error)); return }
do {
let decoder = JSONDecoder()
decoder.keyDecodingStrategy = .convertFromSnakeCase
let entries = try decoder.decode(Response.self, from: data!)
completion(.success(entries))
} catch {
completion(.failure(error))
}
}.resume()
}
And call it
jsonParser { result in
switch result {
case .success(let entries) : print(entries)
case .failure(let error) : print(error)
}
}
Basically never use NS... classes if there are native equivalents, here URL for NSURL and URLRequest for NS(Mutable)URLRequest
Edit:
In Swift 5 the syntax becomes more convenient using the native Result type. It is able to convert the throwing expression
enum Result {
case success(Response), failure(Error)
}
func jsonParser(completion: #escaping (Result<Response,Error>) -> Void) {
let endpoint = URL(string:"https://api.github.com/search/repositories?q=language:ruby&sort=stars&order=desc")!
URLSession.shared.dataTask(with: endpoint) { (data, response, error) in
if let error = error { completion(.failure(error)); return }
let decoder = JSONDecoder()
decoder.keyDecodingStrategy = .convertFromSnakeCase
completion(Result{ try decoder.decode(Response.self, from: data!) })
}.resume()
}

You need
func jsonParser() {
let urlPath = "https://api.github.com/search/repositories?q=language:ruby&sort=stars&order=desc"
guard let endpoint = NSURL(string: urlPath) else {
print("Error creating endpoint")
return
}
let request = NSMutableURLRequest(url:endpoint as URL)
URLSession.shared.dataTask(with: request as URLRequest) { (data, response, error) in
do {
let dec = JSONDecoder()
dec.keyDecodingStrategy = .convertFromSnakeCase
let entries = try dec.decode(Root.self, from:data!)
print(entries)
} catch {
print(error)
}
}.resume()
}
struct Root : Decodable {
let items:[Item]
}
struct Owner: Codable {
let login: String
let id: Int
let nodeId: String
let avatarUrl: String
let gravatarId: String
let url, htmlUrl, followersUrl: String
let followingUrl, gistsUrl, starredUrl: String
let subscriptionsUrl, organizationsUrl, reposUrl: String
let eventsUrl: String
}
struct Item: Codable {
let fullName : String
let htmlUrl:String
let owner: Owner
}
You shouldn't cast the response to data here
from: jsonResponse as! Data)
as it will crash

Related

Swift - How do I show Data in Swift View from Data.swift file?

In a separate data file called data.swift I have this code
struct Response: Decodable {
var data: Data
}
struct Data: Decodable {
var search: search
}
struct search: Decodable {
var __Typename: String
var query: String
var searchResults: searchResults
}
...and so on and so forth. I then decode the data from a Rapid-Api like so
let session = URLSession.shared
let dataTask = session.dataTask(with: request as URLRequest, completionHandler: { (data, response, error) -> Void in
if (error != nil) {
print(error!)
} else {
let products = try? JSONSerialization.jsonObject(with: data!, options: .allowFragments)
if let water = products {
print("JSON: \n" + String(describing: water) + "\n")
}
}
})
How do I display the data elements in ProductList.swift it's a (view) file. The API works as expected and displays the JSON in the terminal. I am using XCODE 12.4 as I am not permitted to upgrade any further.
So, actually you want to receive the data in Model and show in view, either a label or image.
In your productSwift.List:
var response: Response?
Right now, you have to decode the data in the Model:
static func postApiCall<T: Decodable>(completion: #escaping (Result<T,Error>) -> Void) {
let url = URL(string: "Enter Your URL here")
let request = URLRequest(url: url!)
let dataTask = URLSession.shared.dataTask(with: request) { data, response , error in
guard let data = data else {
if error == nil {
completion(.failure(error as! Error))
}
return
}
do {
let decoder = JSONDecoder()
let json = try decoder.decode(T.self, from: data)
completion(.success(json))
} catch let error {
print(error.localizedDescription)
}
}
dataTask.resume()
}
}
Now in your ProductList.swift:
ServiceManage.postApiCall { (result : Result<Response,Error>) in
switch result {
case .success(let result):
print("result is \(result)")
self.response = response.data
self.yourLabel.text = response.data.search.query
case .failure(let failure):
print(failure)
}
}
and as Larme said, change your Struct "Data" name to something else.

Getting objects from JSON

My problem:
I use the site API - https://www.themealdb.com/api.php .
I want to get a list of all products. For this purpose, the link is https://www.themealdb.com/api/json/v1/1/categories.php
In my code, I created a structure:
struct Category: Decodable {
var idCategory: Int?
var strCategory: String?
var strCategoryDescription: String?
var strCategoryThumb: String?
}
Then I try to get to the address and get the data. I can convert the incoming data to JSON. It works.
Next, I want to convert the data and write it into an array of structures.
func load(url: String, completion: #escaping (_ objects: [Category])->()) {
guard let url = URL(string: url) else { return }
let session = URLSession.shared
session.dataTask(with: url) { (data, response, error) in
guard let data = data else { return }
do {
//let json = try? JSONSerialization.jsonObject(with: data, options: [])
//print("JSONSerialization" + "\(json)")
let object = try JSONDecoder().decode([Category].self, from: data)
print("JSONDecoder" + "\(object)")
completion(object)
} catch {
print(error.localizedDescription)
}
}.resume()
}
But in this line I get an error in the console:
The data couldn’t be read because it isn’t in the correct format.
Probably a mistake in my structure. I can not deal with this problem.
There are two mistakes.
The actual error
Type 'Array' mismatch: Expected to decode Array but found a dictionary instead.
indicates that you are ignoring the root object, the dictionary with key categories
The value for key id is String not Int, note the double quotes in the JSON
Declare all struct members as non-optional constants as the JSON provides all keys the in dictionaries. And please map the horrible dictionary keys to more meaningful member names.
And print all errors and never .localizedDescription in a Decodable catch block.
struct Response: Decodable {
let categories: [Category]
}
struct Category: Decodable {
let id: String
let name: String
let description: String
let thumbnailURL: URL
private enum CodingKeys: String, CodingKey {
case id = "idCategory"
case name = "strCategory"
case description = "strCategoryDescription"
case thumbnailURL = "strCategoryThumb"
}
}
func load(url: String, completion: #escaping ([Category]) -> Void) {
guard let url = URL(string: url) else { return }
let session = URLSession.shared
session.dataTask(with: url) { (data, _, error) in
if let error = error { print(error); return }
do {
let response = try JSONDecoder().decode(Response.self, from: data!)
print("JSONDecoder", response)
completion(response.categories)
} catch {
print(error)
completion([])
}
}.resume()
}
You need two codables
struct MyData: Codable {
var categories: [Category]?
}
And
let object = try JSONDecoder().decode(MyData.self, from: data)
With a wrapper class you can fetch your categories. The following code works fine in Playground:
let json = """
{
"categories": [
{"idCategory": "1"},
{"idCategory": "2"}
]
}
"""
struct CategoryHolder: Codable {
var categories: [Category]
}
struct Category: Codable {
let idCategory: String?
let strCategory: String?
let strCategoryDescription: String?
let strCategoryThumb: String?
}
let jsonData = Data(json.utf8)
let categories = try JSONDecoder().decode(CategoryHolder.self, from: jsonData).categories

Passing JSON result into a struct model

I am receiving a result from an API, I can iterate through the result. My understanding is I can pass the value into a model immediately.
Apple Developer article on struct models
My issue is I am not doing it properly and am receiving a nil value. Perhaps someone can see where I need to change. I am using Swift 4.2
Here is my struct model.
import Foundation
struct ProfileModel {
//MARK: Properties
var name: String
var email: String
var profileURL: String
//MARK: Initialization
}
extension ProfileModel{
init?(json: [String:AnyObject]) {
guard
let name = json["name"] as? String,
let email = json["email"] as? String,
let profileURL = json["profileURL"] as? String
else { return nil }
self.name = name
self.email = email
self.profileURL = profileURL
}
}
Here is my result code from my urlConnection. Let me know if we want to see the entire swift file
//create dataTask using the session object to send data to the server
let task = session.dataTask(with: request as URLRequest, completionHandler: { data, response, error in
guard error == nil else {
return
}
guard let data = data else {
return
}
do {
//create json object from data
if let json = try JSONSerialization.jsonObject(with: data, options: .mutableContainers) as? [String:AnyObject] {
self.onSuccess(data: json)
}
} catch let error {
print(error.localizedDescription)
}
})
task.resume()
}
func onSuccess(data: [String:AnyObject]){
print("onSuccess")
let myProfile = ProfileModel(json: data)
//myProfile is nil while unwrapping
let title: String = myProfile!.name
print(title)
}
I could just iterate through the strings since I am able to print 'data'. I just figured it would be cleaner to put everything into a ProfileModel and manage that object as a whole.
This json is my more simple one which is why I used it for this question. I also can't remember but I had to use "[String:AnyObject]" to get the json properly. This was pulled directly from my terminal, this was the data being passed in my JsonResponse. The output json from Xcode has [] on the outside instead.
{
'detail': 'VALID',
‘name’: ‘Carson,
'email': ‘carson.skjerdal#somethingelselabs.com',
'pic_url': None
}
EDIT:
So my problem is solved, and ultimately moving to Codable was the key. Here is my fixed code for anyone who might need a working solution.
URLSession.shared.dataTask(with: request as URLRequest) { (data, response
, error) in
guard let data = data else { return }
do {
let decoder = JSONDecoder()
let gitData = try decoder.decode(ProfileModel.self, from: data)
print(gitData.name)
self.onSuccess(data: gitData)
} catch let err {
print("Err", err)
}
}.resume()
}
func onSuccess(data: ProfileModel){
print("onSuccess")
print(data.email)
}
My Codable Struct - slightly simplified
import Foundation
struct ProfileModel: Codable {
let detail, name, email: String
private enum CodingKeys: String, CodingKey {
case detail, email
case name = "firstname"
//case picUrl = "pic_url"
}
}
After "Codable" has been introduced I always uses that.
You can take your JSON ans pars it in to QuickType.io, and you will get a Struct that confirms to the codadable
// To parse the JSON, add this file to your project and do:
//
// let aPIResponse = try? newJSONDecoder().decode(APIResponse.self, from: jsonData)
import Foundation
struct APIResponse: Codable {
let detail, name, email, picUrl: String
enum CodingKeys: String, CodingKey {
case detail, name, email
case picUrl = "pic_url"
}
}

problem in Parsing json in SWIFT for iOS developing

I have an Error in parsing JSON Format form an ASMX Web service,
My Code is
func getData() {
let url = URL(string: "http://192.168.11.188/getItems.asmx/theItems")
let theCategory = "ALL"
let theSubCategory = "ALL"
let postString = "theCategory=\(theCategory)&theSubCategory=\(theSubCategory)"
var request = URLRequest(url: url!)
request.httpMethod = "POST"
request.cachePolicy = NSURLRequest.CachePolicy.reloadIgnoringCacheData
request.httpBody = postString.data(using: String.Encoding.utf8)
URLSession.shared.dataTask(with: request) { (data, respons, error) in
if let error = error {
print("Error conning to server \(error)")
} else {
if let respons = respons as? HTTPURLResponse {
if respons.statusCode == 200 {
print(data!)
if let data = data {
do {
let json = try JSONDecoder().decode([ITEMS].self, from: data)
print(json)
} catch let parsingError {
print("Error parsing json \(parsingError)")
}
}
} else {
print("Error in responce code.... \(respons.statusCode)")
}
}
}
}.resume()
}
I am using the decoder struct in this code:
struct ITEMS: Codable {
let CODE:String
let CAT_ID:String
let SUB_ID:String
let PRODUCT_AR:String
let PRODUCT_EN:String
let OLD_PRICE:String
let NEW_PRICE:String
let UNIT:String
let BARCODE:String
let THE_DATE:String
let TIME:String
}
The JSON value is
{ ITEMS : [{"CODE":111,"CAT_ID":203,"SUB_ID":null,"PRODUCT_AR":"ITEM 1","PRODUCT_EN":"ITEM 1","OLD_PRICE":133.0035,"NEW_PRICE":109,"UNIT":null,"BARCODE":"328031002009","THE_DATE":"\/Date(1553673958397)\/","TIME":"11:05 AM"},
{"CODE":222,"CAT_ID":201,"SUB_ID":null,"PRODUCT_AR":"ITEM 2","PRODUCT_EN":"ITEM 2","OLD_PRICE":18.95,"NEW_PRICE":9.95,"UNIT":null,"BARCODE":"628103400012","THE_DATE":"\/Date(1553673958260)\/","TIME":"11:05 AM"}]}
but this code returns an Error
Error parsing JSON Error Domain=NSCocoaErrorDomain Code=3840 "JSON text did not start with array or object and option to allow fragments not set." UserInfo={NSDebugDescription=JSON text did not start with array or object and option to allow fragments not set.}
What am I doing wrong?
as you are writing this,
let json = try JSONDecoder().decode([ITEMS].self, from: data)
it must ask for array of ITEMS to be parsed, but your JSON is just an object not an array (It starts with {ITEMS: [array_here]}).
For solving this issue, you need to make your struct like below and parse data into that object.
struct ITEMS: Codable {
let CODE:String
let CAT_ID:String
let SUB_ID:String
let PRODUCT_AR:String
let PRODUCT_EN:String
let OLD_PRICE:String
let NEW_PRICE:String
let UNIT:String
let BARCODE:String
let THE_DATE:String
let TIME:String
}
struct MyAPIData: Codable {
let ITEMS: [ITEMS]
}
Now parse data using below line of code,
let json = try JSONDecoder().decode(MyAPIData.self, from: data)
There were wrong types used in your struct, here is the fixed struct
struct ITEM: Codable {
let CODE:Int // not String
let CAT_ID:Int // not String
let SUB_ID:Int? // its null in JSON, use either Int? or String?
let PRODUCT_AR: String
let PRODUCT_EN: String
let OLD_PRICE:Double // not String
let NEW_PRICE:Double // not String
let UNIT:String?
let BARCODE:String
let THE_DATE:String
let TIME:String
}
Since you JSON has a root element ITEMS, you need to decode using this struct
struct BaseItems: Codable {
let ITEMS: [ITEM] // Actual array of items are within this JSON element
}
Usage:
do {
let decoded = try JSONDecoder().decode(BaseItems.self, from: data)
print(decoded.ITEMS)
} catch {
print(error)
}

JSONDecoder Not Parsing Data

I am trying to get data from this URL
https://api.opendota.com/api/heroStats
I have made a struct
struct HeroStats : Decodable {
let localized_name: String
let primary_attr: String
let attack_type: String
let legs: Int
let image: String
}
Top of my View Controller
var heros = [HeroStats]()
func downloadJSON(completed: #escaping () -> ()) {
let url = URL(string: "https://api.opendota.com/api/heroStats")
URLSession.shared.dataTask(with: url!) { (data, response, error) in
if error != nil {
print(error.debugDescription)
}
do {
guard let data = data else { return}
self.heros = try JSONDecoder().decode([HeroStats].self, from: data)
DispatchQueue.main.async {
completed()
}
print(self.heros)
} catch {
print("JSON ERROR")
return
}
}.resume()
}
I always return JSON ERROR for some reason, although everything seems to be correct.
Try to read more on Codable/Encodable in Swift
Encoding and Decoding custom types
You may want to improve your code by making Swift names that differs from JSON names
struct HeroStats: Codable {
let name: String
let primaryAttribute: String
let attackType: String // Better to be an enum also
let legs: Int
let image: String?
enum CodingKeys: String, CodingKey {
case name = "localized_name"
case primaryAttribute = "primary_attr"
case attackType = "attack_type"
case legs
case image = "img"
}
}

Resources