Fetching and parsing JSON - ios

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

Related

How to map json data using codable and populate to table view?

I made a network request using urlsession and got json data , but don't know how to map the data and populate into table view . I am a beginner please help me out.
Here is the json data :
{
page = 1;
results = (
{
adult = 0;
"backdrop_path" = "/w2PMyoyLU22YvrGK3smVM9fW1jj.jpg";
"genre_ids" = (
28,
12,
878
);
id = 299537;
"original_language" = en;
"original_title" = "Captain Marvel";
}) }
Here is my code so far:
var movies = [Movies]()
do {
let json = try JSONSerialization.jsonObject(with: data!, options: [])
print(json)
self.parse(json: json as! Data)
} catch {
print("JSON error: \(error.localizedDescription)")
}
func parse(json: Data) {
let decoder = JSONDecoder()
if let jsonUsers = try? decoder.decode([Movies].self, from: json) {
self.movies = jsonUsers
print(movies[0].request)
}
}
Welcome to Swift. If you search here on Stack Overflow (and elsewhere around the web) you will find a great many examples on how to decode JSON Swift.
Apple's documentation on the subject can be found here:
Encoding and Decoding Custom Types
With a code sample here:
Using JSON with Custom Types
Generally speaking, you will need to create a struct to represent the decoded data. That struct will derive from decodable. You should give it a "CodingKeys" value that will define how the fields in your struct map to the keys in the JSON. You then use the decoder to create and fill out that struct.
JSONDecoder is the more recent and type-safe mechanism for decoding JSON. JSONSerialization will be more challenging for a beginner because of typecasting so I recommend you stick with JSONDecoder
Based on the json data I assume that you're using TMDB's API.
Firstly, you should create a decodable struct for the results:
struct SearchResult: Decodable {
let page: Int
let results: [Movie]
let totalPages: Int
let totalResults: Int
}
Secondly, you should fetch the results from the API. I suggest that you do it with the following code:
func searchURL(withQuery query: String) -> URL? {
var components = URLComponents()
components.scheme = "https"
components.host = "api.themoviedb.org"
components.path = "/3/search/movie"
components.queryItems = [
URLQueryItem(name: "query", value: query),
URLQueryItem(name: "api_key", value: "YOUR-API-KEY")
]
return components.url
}
This function will return an optional URL to the search results, if you'll call it with "Pulp Fiction" as a query, it'll return:
https://api.themoviedb.org/3/search/movie?query=Pulp%20Fiction&api_key=YOUR-API-KEY
You can use it to actually fetch the searched phrase from the API:
func search(_ text: String, completion: #escaping (Result<[Movie], Error>) -> Void) {
if let url = getSearchURL(withQuery: text) {
let request = URLRequest(url: url)
URLSession.shared.dataTask(with: request) { data, _, _ in
if let data = data {
do {
let decoder = JSONDecoder()
decoder.keyDecodingStrategy = .convertFromSnakeCase
let decodedSearchResult = try decoder.decode(SearchResult.self, from: data)
completion(.success(decodedSearchResult.results))
} catch {
completion(.failure(error))
}
}
}.resume()
}
}
The following code:
search("Pulp Fiction") { result in
switch result {
case .success(let items):
for item in items {
print(item.title)
}
case .failure(let error):
print(error.localizedDescription)
}
}
will print:
Pulp Fiction
Pulp Fiction: The Facts
Pulp Fiction Art
Stealing Pulp Fiction
Pulp Fiction: the Golden Age of Storytelling
UFOTV Presents: Pulp Fiction: The Golden Age of Storytelling
Pulpe Fiction
If you want to populate a tableView with the search results, you should replace the .success case with:
search("Pulp Fiction") { [weak self] result in
switch result {
case .success(let items):
self?.searchResults = items
DispatchQueue.main.async {
self?.tableView.reloadData()
}

How to decode this particular JSON model?

EDIT 2:
After changing the Model struct and calling the JSON decoder without a completion handler, I have managed to get this to work. Thank you all for your help.
Fixed code:
Model
import Foundation
struct RatesResponse: Decodable, Hashable{
let rates: [String: Double]
}
Decoder Class
import Foundation
class RatesModelData{
public var rateCurrency = [String]()
public var rateValue = [Double]()
public func getRates(currency: String){
guard let url = URL(string: "https://api.exchangerate.host/latest?base=\(currency)")
else {
print("URL is invalid")
return
}
var request = URLRequest(url: url)
let dataTask = URLSession.shared.dataTask(with: request){data, response, error in
if let data = data {
do{
let ratesResponse = try JSONDecoder().decode(RatesResponse.self, from: data)
for rate in ratesResponse.rates{
self.rateCurrency.append(rate.key)
self.rateValue.append(rate.value)
}
print(self.rateCurrency)
} catch {
print(error)
}
}
}
dataTask.resume()
}
}
EDIT:
I have changed let rate = Rates to let rates = Rates, modified the decoder class to the following and added a do/catch statement however I am now getting the error "The data given is invalid JSON". I have updated the code snippets as well.
I have this json model:
https://api.exchangerate.host/latest?base=usd
and I cannot for the life of me figure out how to decode it properly. I know its a dictionary and has to be decoded as such but i'm struggling to wrap my head around what i'm doing wrong.
Model:
struct RatesResponse: Decodable, Hashable{
let rates : Rates
}
struct Rates: Decodable, Hashable {
let rates: [String: Double]
}
Decoder Class:
class RatesModelData{
public func getRates(currency: String, _ completionHandler: #escaping(Rates) -> Void){
guard let url = URL(string: "https://api.exchangerate.host/latest?base=\(currency)")
else {
print("URL is invalid")
return
}
var request = URLRequest(url: url)
request.httpMethod = "GET"
request.setValue("application/json", forHTTPHeaderField: "ACCEPT")
let dataTask = URLSession.shared.dataTask(with: request){data, response, error in
do{
if let data = data {
if let ratesResponse = try JSONDecoder().decode(RatesResponse?.self, from: data){
completionHandler(ratesResponse.rates)
}
return
}
guard let httpResponse = response as? HTTPURLResponse, (200...299).contains(httpResponse.statusCode) else{
print("Error with response: \(response)")
return
}
if let error = error {
print("Error Thrown : \(error)")
return
}
} catch {
print(error)
}
}
dataTask.resume()
}
}
I ideally want to get this to be displayed in a list view (SwiftUI), but for now just asking for some advice as to where i've gone wrong with my decoding.
Your object model suggests that the value associated with rates key is another object with another rates key. But that doesn’t match the JSON you have provided. The value associated with top-level rates key is just a dictionary.
So you could do the following and be done with it:
struct ResponseObject: Decodable {
let rates: [String: Double]
}
Or, if you want to capture everything in this JSON, you could add the additional properties:
struct Motd: Decodable {
let msg: String
let url: URL
}
struct ResponseObject: Decodable {
let motd: Motd
let success: Bool
let base: String
let date: Date
let rates: [String: Double]
}
And then:
do {
let formatter = DateFormatter()
formatter.locale = Locale(identifier: "en_US_POSIX")
formatter.dateFormat = "yyyy-MM-dd"
let decoder = JSONDecoder()
decoder.dateDecodingStrategy = .formatted(formatter)
let responseObject = try decoder.decode(ResponseObject.self, from: data)
print(responseObject)
} catch {
print(error)
}
FWIW, the base, date, and rates objects likely could/should be optionals, e.g.:
struct ResponseObject: Decodable {
let motd: Motd
let success: Bool
let base: String?
let date: Date?
let rates: [String: Double]?
}
To confirm, we would need to see what a well-formed non-success response looks like. Glancing at the documentation it was not immediately obvious, but something like the above is likely what you want, where JSONDecoder would be able to successfully decode both success responses and failure responses.
I went through the struggles of parsing JSON into structs last year so I feel comfortable helping you out. It's definitely a challenging concept to wrap your head around:
First, let me explain what's wrong with your structs:
struct RatesResponse: Decodable, Hashable{
let rates : Rates
// "rates" is a root-level key in your JSON.
// You're giving it the value of your structure Rates,
// which also has a key titled "rates".
}
struct Rates: Decodable, Hashable {
let rates: [String: Double]
}
Here's what your JSON would look like if these structs could be used to parse it:
{
"rates": {
"rates": {
"AED": 3.671827,
"AFN": 80.098152
}
}
In your actual JSON, your "rates" key has the value of an object (or dictionary, which is nothing more than an array with keys and values) I'm sure you don't want to make a struct with a variable for every single currency, so just use an array in your struct, with an element type of String: Double
struct RatesResponse: Decodable, Hashable{
let rates : [String: Double]
}
JSON key "rates" has a value of a dictionary where the keys are strings and the values are doubles.
After decoding in your URLSession, you can do:
for rate in ratesResponse.rates {
let currency = rate.key // AED, AFN, etc.
let exchangeRate = rate.value // 3.671827, 80.098152, etc.
// With these variables, you can append each of them to a new, separate array for use in your SwiftUI list.
}

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

Parsing JSON response using Codable gives error in swift

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

Error in decoding: The data couldn't be read. Alamofire

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.

Resources