Parse JSON with Decodable return empty Model - ios

I'm trying to load local JSON file and parse using model which conforms to Decodable protocol.
JSON file:
[
{
"body": {},
"header": {
"returnCode": "200",
"returnMessage": "Successfully Received",
}
}
]
Response Message model:
struct ResponseMessage: Decodable {
struct header: Decodable {
let returnCode: String
let returnMessage: String
}
}
Mock API implementation:
let url = Bundle.main.url(forResource: "MockJSONData", withExtension: "json")!
do {
let data = try Data(contentsOf: url)
let teams = try JSONDecoder().decode(ResponseMessage.self, from: data)
print(teams)
} catch {
print(error)
}
But Response Message returns empty data for that.
Appreciate your help and suggestions!
Thanks

Update ResponseMessage and Header types as below,
struct ResponseMessage: Decodable {
var header: Header
}
struct Header: Decodable {
let returnCode: String
let returnMessage: String
}
and decode like this,
do {
let data = try Data(contentsOf: url)
let teams = try JSONDecoder().decode([ResponseMessage].self, from: data)
print(teams.first!.header.returnMessage)
} catch {
print(error)
}

Related

Fetching and parsing JSON

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

Decoding json data in swift with two different possible json reponse from server to two different structs

When I make an api request through urlSession to the server I might get a json like
{
"movie": "Avengers",
"director": "Joss Whedon",
}
or like this
{
"apiKey": "invalid"
}
I have two structs to save like this
struct Movie: Codable {
let request: String
let director: String
init(movie:String, director:Sring) {
self.movie = movie
self.director = director
}
}
struct Valid: Codable {
let apiKey: String
init(apiKey:String) {
self.apiKey = apiKey
}
}
Based on the response i want to either decode to first struct or the second struct. How to do that.
if let url = URL(string: "myurl.com") {
URLSession.shared.dataTask(with: url) { data, response, error in
if let data = data {
guard let json = data as? [String:AnyObject] else {
return
}
do {
if let movie = json["movie"]{
//moview is there. so you can decode to your movie struct
let res = try JSONDecoder().decode(Movie.self, from: data)
}else{
//move is not there.it means error
let res = try JSONDecoder().decode(Valid.self, from: data)
}
} catch let error {
print(error)
}
}
}.resume()
}
Parse json with a single Struct like this
struct RootClass : Codable {
let apiKey : String?
let director : String?
let movie : String?
enum CodingKeys: String, CodingKey {
case apiKey = "apiKey"
case director = "director"
case movie = "movie"
}
init(from decoder: Decoder) throws {
let values = try decoder.container(keyedBy: CodingKeys.self)
apiKey = try values.decodeIfPresent(String.self, forKey: .apiKey)
director = try values.decodeIfPresent(String.self, forKey: .director)
movie = try values.decodeIfPresent(String.self, forKey: .movie)
}
}
And check the value of key apiKey if it's nil then use movies and director. or use the value of apiKey
First of all, the Codable types to parse the above 2 JSON responses should look like,
struct Movie: Decodable {
let movie: String
let director: String
}
struct Valid: Decodable {
let apiKey: String
}
There is no need to explicitly create init(). Codable will handle that automatically.
Next, you need to create another Codable type that can handle both the responses, i.e.
enum Response: Decodable {
case movie(Movie)
case valid(Valid)
init(from decoder: Decoder) throws {
let container = try decoder.singleValueContainer()
do {
let data = try container.decode(Movie.self)
self = .movie(data)
} catch {
let data = try container.decode(Valid.self)
self = .valid(data)
}
}
}
Now, you can parse the JSON data like so,
do {
let response = try JSONDecoder().decode(Response.self, from: data)
print(response)
} catch {
print(error)
}

How do I map API?

I'm new to swift language and trying to work on the API below..
https://api.foursquare.com/v2/venues/search?ll=40.7484,-73.9857&oauth_token=NPKYZ3WZ1VYMNAZ2FLX1WLECAWSMUVOQZOIDBN53F3LVZBPQ&v=20180616
I'm trying to parse the data and then serialise, however I'm not able to map the data.
struct Venue: Codable {
let id: String
let name: String
let contact: Location
}
struct Location: Codable {
let address: String
let postalCode: String
}
class DataService {
private init() {}
static let shared = DataService()
func getdata() {
guard let url = URL(string: "https://api.foursquare.com/v2/venues/search?ll=40.7484,-73.9857&oauth_token=NPKYZ3WZ1VYMNAZ2FLX1WLECAWSMUVOQZOIDBN53F3LVZBPQ&v=20180616") else { return }
let task = URLSession.shared.dataTask(with: url) { (data, response, error) in
if let data = data {
do {
guard let venues = try? JSONDecoder().decode([Venue].self, from: data) else { return }
print(venues[0].id)
} catch let jsonError {
print(jsonError)
}
}
}
task.resume()
}
}
I need to work on venues array ( mainly "id", "name", "location" ( "address", "postalCode" ))
I'm trying to use the codable and decodable, how do I get the the results, please help.
This is a very common mistake.
You ignore the root object (the dictionary containing the meta and response keys). And the venues are in the dictionary for key response, a sub-dictionary of the root object
struct Root : Decodable {
let response : Response
}
struct Response : Decodable {
let venues : [Venue]
}
struct Venue: Decodable {
let id: String
let name: String
let location : Location
}
struct Location: Decodable { // both struct members must be optional
let address: String?
let postalCode: String?
}
And – as Joakim already said in the comments – never ignore Decoding errors. Decoding errors are very descriptive. They contain the specific error message as well as the CodingPath of the error.
let task = URLSession.shared.dataTask(with: url) { (data, response, error) in
if let data = data {
do {
let result = try JSONDecoder().decode(Root.self, from: data)
result.response.venues.forEach{print($0.id)}
} catch {
print(error)
}
}
}
task.resume()

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

How do I decode JSON(JSON Web Token) in Swift 4?

How do I decode this json in Swift 4, with decoder?
I want to be able to get the "token" by itself, so I can store it in the Keychain.
{
"success":true,
"token":"***"
,
"user": {
"id": "59f0ec6d5479390345980cc8",
"username": "john",
"email": "john#gmail.com"
}
}
I have tried this, but it doesn't print anything.
let session = URLSession.shared
let task = session.dataTask(with: request) { (data, _, _) in
guard let data = data else { return }
do {
let jsonwt = try JSONDecoder().decode(JWT.self, from: data)
print(jsonwt.token)
} catch {}
}
task.resume()
}
I can put this after the catch, but that gets the whole json, and I don't want that.
print(String(data: data, encoding: .utf8)!)
Here are the structures. I think this is where the problem lies.
struct User: Decodable {
let id: String
let username: String
let email: String
}
struct JWT: Decodable {
let success: String
let token: String
let user: User
}
Works like this:
struct User : Codable
{ var id : String
}
struct JWT : Codable
{ var success : Bool
var token : String
var user :User
}
let json = """
{ \"success\" : true,
\"token\" : \"***\",
\"user\":
{ \"id\": \"59f0ec6d5479390345980cc8\",
\"username\": \"john\",
\"email\": \"john#gmail.com\"
}
}
"""
let decoder = JSONDecoder()
let jwt = try decoder.decode(JWT.self, from: json.data(using: .utf8)!)
print ("token: \(jwt.token)")
Here's some playground code that demonstrates the code for parsing JSON in Swift:
//: Playground - noun: a place where people can play
import UIKit
import XCTest
import PlaygroundSupport
let json = """
{
"success":true,
"token":"***"
,
"user": {
"id": "59f0ec6d5479390345980cc8",
"username": "john",
"email": "john#gmail.com"
}
}
""".data(using: .utf8)!
do {
if let data = try JSONSerialization.jsonObject(with: json, options: .allowFragments) as? [String:Any], let token = data["token"] {
print("token is \(token)")
}
} catch _ {
print("Failed to decode JSON")
}

Resources