I was trying to parse JSON through JSONDecoder and using Alamofire to fetch the data. However, when I run the app, it shows that the data couldn't be read because of the incorrect format. I have tried many things but still did not work. Any help would be appreciated. Sources are below:
VC:
class SecondTaskVC: UIViewController {
var weatherModel = [WeatherModelDecodable]()
override func viewDidLoad() {
let url = URL(string: "https://api.openweathermap.org/data/2.5/forecast?lat=42.874722&lon=74.612222&APPID=079587841f01c6b277a82c1c7788a6c3")
Alamofire.request(url!).responseJSON { (response) in
let result = response.data
do{
let decoder = JSONDecoder()
self.weatherModel = try decoder.decode([WeatherModelDecodable].self, from: result!) // it shows this line as a problem
for weather in self.weatherModel {
print(weather.city.name)
}
}catch let error{
print("error in decoding",error.localizedDescription)
}
}
}
}
Data Model:
struct WeatherModelDecodable: Decodable {
let city: CityDecodable
}
struct CityDecodable: Decodable {
let name: String
}
Actually the response structure is different from what you are trying to do at this line,
self.weatherModel = try decoder.decode([WeatherModelDecodable].self, from: result!)
The response is not an array as you can see it in a json viewer by hitting this Url in any browser. You are expecting an array of json objects but its not. So if you decode it as a single object, it will decode properly as below,
let weatherModel = try decoder.decode(WeatherModelDecodable.self, from: result!)
print(weatherModel.city.name)
So, SecondTaskVC will look like this,
class SecondTaskVC: UIViewController {
var weatherModel: WeatherModelDecodable?
override func viewDidLoad() {
let url = URL(string: "https://api.openweathermap.org/data/2.5/forecast?lat=42.874722&lon=74.612222&APPID=079587841f01c6b277a82c1c7788a6c3")
Alamofire.request(url!).responseJSON { (response) in
let result = response.data
do{
let decoder = JSONDecoder()
self.weatherModel = try decoder.decode(WeatherModelDecodable.self, from: result!)
print(self.weatherModel!.city.name)
}catch let error{
print("error in decoding",error.localizedDescription)
}
}
}
}
You should decode the respective objects with the same structure you are getting in the response.
Related
Hello I am creating an app with Xcode and I am having the following problem, I created this API with mockapi.io (if you enter the link you'll see the JSON data) https://62858a2ff0e8f0bb7c057f14.mockapi.io/categorias
If you dont want to enter the link here is how it looks the JSON: (By default the JSON has an array without name as the root and that can't be modified)
[
{
"categorie":"Fruits",
"id":"1"
},
{
"categorie":"Animals",
"id":"2"
},
{
"categorie":"Vegetables",
"id":"3"
},
{
"categorie":"Juices",
"id":"4"
},
{
"categorie":"Alcohol",
"id":"5"
},
{
"categorie":"Desserts",
"id":"6"
}
]
The problem I have is that when I try to decode the data from the API it cant't be readed because is in the wrong format, I am trying to recreate the same code of this youtube video, but with my API: https://www.youtube.com/watch?v=sqo844saoC4
This is how my code looks like:
import UIKit
class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
let url = "https://62858a2ff0e8f0bb7c057f14.mockapi.io/categorias"
getData(from: url)
}
private func getData(from url: String) {
let task = URLSession.shared.dataTask(with: URL(string: url)!, completionHandler: { data, response, error in
guard let data = data, error == nil else {
print("something went wrong")
return
}
var result: Response?
do {
result = try JSONDecoder().decode(Response.self, from: data)
}
catch {
print("failed to convert\(error.localizedDescription)")
}
guard let json = result else {
return
}
print(json.items.categorie) // 👈 HERE ES WHERE THE PRINT HAPPENS
})
task.resume()
}
}
// 👇 I THINK THE PROBLEM IS DEFINITELY HERE
struct Response: Codable {
let items: ResultItem
}
struct ResultItem: Codable {
let categorie: String
}
When I execute this the terminal print: "The data couldn't be read becouse it isn't in the correct format."
I am pretty sure the error comes of the way I am calling the data in the structs, so my question is...? How can I exactly call the data from my API's JSON in the code?
yes ,there is an issue in your model you don't need to use the (Response) only use the Model (ResultItem) the JSON isn't complex JSON like that it just array of (ResultItem)
private func getData(from url: String) {
let task = URLSession.shared.dataTask(with: URL(string: url)!, completionHandler: { data, response, error in
guard let data = data, error == nil else {
print("something went wrong")
return
}
do {
let result = try JSONDecoder().decode([ResultItem].self, from: data)
print(result)
}
catch {
print("failed to convert\(error.localizedDescription)")
}
})
task.resume()
}
struct ResultItem: Codable {
let categorie: String
}
The response you get is an array of ResultItems rather than a single object, so you need to decode it as an array:
result = try JSONDecoder().decode(Array<ResultItem>.self, from: data)
That said, you won't need the Response struct at all and the type of result will be [ResultItem].
I’m having a really hard time fetching and parsing the following JSON. I can't even fetch the data from given url, even less parse it using my data Model "Car". Any help is more than welcomed!
JSON
{
"cars":[
{
"date_stolen":1604616183,
"description":null,
"body_colors":[
"Black",
"Blue"
],
"id":"944846",
"is_stock_img":false,
"large_img":null,
"location_found":null,
"manufacturer_name":"Toyota",
"external_id":null,
"registry_name":null,
"registry_url":null,
"serial":"36-17-01012-xl09",
"status":null,
"stolen":true,
"stolen_location":"Calgary - CA",
"thumb":null,
"title":"2017 Toyota Corolla ",
"url":"https://cars.org/944846",
"year":2017
}
]
}
struct Car: Decodable {
let cars: String
}
var cars = [Car]()
fileprivate func fetchJSON() {
let urlString = "someUrl…"
guard let url = URL(string: urlString) else { return }
URLSession.shared.dataTask(with: url) { (data, _, err) in
DispatchQueue.main.async {
if let err = err {
print("Failed to get data from url:", err)
return
}
guard let data = data else { return }
do {
let decoder = JSONDecoder()
decoder.keyDecodingStrategy = .convertFromSnakeCase
print("DATA \n", data)
self.cars = try decoder.decode(Car.self, from: data)
print("---> ", data)
} catch let jsonErr {
print("Failed to decode:", jsonErr)
}
}
}.resume()
}
You Car model does not represent you json structure.
You're looking for something like this :
struct CarsResponse: Decodable {
let cars: [CarDTO]
}
struct CarDTO: Decodable {
let id: String
let isStockImg: Bool
// And so on
}
Then just write :
let decoder = JSONDecoder()
decoder.keyDecodingStrategy = .convertFromSnakeCase
let carResponse = try decoder.decode(CarsResponse.self, from: data)
You will access your cars by writing
let cars: [CarDTO] = carResponse.cars
If you're going to use the Codable Protocol and JSONDecoder() you will need to create a Struct that matches the format of your JSON. As others have pointed out in their comments, your Car struct is completely different than the format of the data you are receiving.
If you just want to deserialize the JSON you are receiving into dictionaries containing values, you could instead use JSONSerialization.
To do that you could replace the body of your do block with code like this:
let object = try JSONSerialization.jsonObject(with: data, options: [])
You'd then have to navigate the dictionary structure of object yourself. It isn't as clean as creating a custom struct and using Codable, but it works.
Note that your JSON has syntax errors in it. Here is a cleaned-up version:
{"cars":
[
{"date_stolen":1604616183,
"description":null,
"body_colors":["Black","Blue"],
"id":944846,
"is_stock_img":false,
"large_img":null,
"location_found":null,
"manufacturer_name":"Toyota",
"external_id":null,
"registry_name":null,
"registry_url":null,
"serial":"36-17-01012-xl09",
"status":null,
"stolen":true,
"stolen_location":"Calgary - CA",
"thumb":null,
"title":"2017 Toyota Corolla ",
"url":"https://cars.org/944846",
"year":2017
}
]
}
(I added whitespace for readability. It doesn't affect the parsing one way or the other.)
I am trying to use url(variable 'site') as information type, defining it String but my app crashes causing this thread. How can i resolve this?
Can you show where you fetch and parse the JSON in your code? Also perhaps the Codable struct you are using to handle the JSON.
Something like this:
func parseJSON() {
guard let url = URL(string: "https://www.hackingwithswift.com/samples/friendface.json") else { return }
let request = URLRequest(url: url)
URLSession.shared.dataTask(with: request) { data, response, error in
if let data = data {
if let decodedResponse = try? JSONDecoder().decode([CitizenData].self, from: data) {
// Back to main thread
DispatchQueue.main.async {
// update UI
self.results = decodedResponse
// Save to core data
self.saveToDevice(data: decodedResponse)
}
return
}
}
print("Fetch failed: \(error?.localizedDescription ?? "Unknown error")")
}.resume()
}
A snippet of your JSON data might help too.
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"
}
}
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)
}