JSON parsing in swift 4.2 - ios

I've been trying to get image links from the json data in this Google API link in Xcode using swift:
https://www.googleapis.com/books/v1/volumes?q=flowers
These are the keys:
import UIKit
struct Kind: Decodable {
let kind: String
let items: [Item]
}
struct Item: Decodable {
let id: String
let etag: String
let volumeInfo: VolumeInfo
}
struct VolumeInfo: Decodable {
let title: String
let publisher: String
let imageLinks: ImageLinks
}
struct ImageLinks: Decodable{
let smallThumbnail: String
let thumbnail: String
}
And this is the json parsing:
func fetchPictures(){
let url = URL(string: "https://www.googleapis.com/books/v1/volumes?q=flowers")
URLSession.shared.dataTask(with: url!) { (data, _, _) in
guard let data = data else {return}
do {
let book = try JSONDecoder().decode(Kind.self, from: data)
print(book.items)
} catch {}
}.resume()
}
If i comment out the volumeInfo guy in the Item struct, i get the ids and etags in the console no problem. But if i leave volumeInfo and try to get to the image links through this nested data, it doesn't show anything in the console. I've been trying to do this all day and still can't figure out whats wrong.

Related

How to run query on Firebase Firestore in Swift using REST API?

I'm trying to run a query on Firestore DB using Swift REST API. I'd like to retrieve values if they are equal to a certain referenceValue. Without queries I can read, write and update documents with zero issues on Firestore.
The code I've written is this:
guard let url = URL(string: "https://firestore.googleapis.com/v1beta1/projects/my-project/databases/(default)/documents/Occhiale/") else {
print("Could not get glasses")
return
}
let requestBody = Query(structuredQuery: StructuredQuery(from: [From(collectionID: "Occhiale")], structuredQueryWhere: Where(fieldFilter: FieldFilter(field: Field(fieldPath: "glasses_inclination_coeff"), op: "EQUAL", value: ValueQuery(referenceValue: lineaFire.lineaRefString!)))))
let jsonData = try! JSONEncoder().encode(requestBody)
var request = URLRequest(url: url)
request.httpMethod = "POST"
request.httpBody = jsonData
let task = URLSession.shared.dataTask(with: request) {[self] data, response, error in
guard let httpResponse = response as? HTTPURLResponse, (200...299).contains(httpResponse.statusCode) else {
print("Invalid Response received from the server \(response!)") // <-- I get this
return
}
print("Response Session: \(response!)")
}
task.resume()
The Query struct looks like this:
import Foundation
// MARK: - Query
struct Query: Codable {
let structuredQuery: StructuredQuery
}
// MARK: - StructuredQuery
struct StructuredQuery: Codable {
let from: [From]
let structuredQueryWhere: Where
enum CodingKeys: String, CodingKey {
case from
case structuredQueryWhere = "where"
}
}
// MARK: - From
struct From: Codable {
let collectionID: String
enum CodingKeys: String, CodingKey {
case collectionID = "collectionId"
}
}
// MARK: - Where
struct Where: Codable {
let fieldFilter: FieldFilter
}
// MARK: - FieldFilter
struct FieldFilter: Codable {
let field: Field
let op: String
let value: ValueQuery
}
// MARK: - Field
struct Field: Codable {
let fieldPath: String
}
// MARK: - Value
struct ValueQuery: Codable {
let referenceValue: String
}
The collection I'm trying to get is called "Occhiale". If I run the request without the httpBody i get a 200 response, while with it I get error 400.
The field I'd like to use for comparison in the query is "lineaRef", which is a Document Reference.
Now, if I use Firebase's tool to simulate queries at: https://firebase.google.com/docs/firestore/reference/rest/v1/projects.databases.documents/runQuery?apix_params I get 200 but without any field in the JSON response:
What would you suggest? What can I try to run queries using REST APIs?
Thanks in advance.

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

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

Array of Codable structs possibly decoded from JSON data object

I have this piece of code:
struct NoteRecord: Codable {
let id: String
let title: String
let detail: String?
let dueDate: String?
private enum CodingKeys: String, CodingKey {
case id, title, detail, dueDate
}}
and parsing part:
do {
let decoder = JSONDecoder()
let note = try decoder.decode(NoteRecord.self, from: data)
} catch let err {
print("Error occured:", err)
}
Is there any way to use this when REST API returns an array of objects to decode the data correctly as array of structs?
Yes, just use this:
do {
let decoder = JSONDecoder()
let notes = try decoder.decode([NoteRecord].self, from: data)
} catch let err {
print("Error occured:", err)
}
If you use [YourCodableStruct].self you are parsing the array. If you use YourCodableStruct.self you are parsing the struct.
You can implement another struct to hold the array.
struct NoteRecords: Codable {
var list: [NoteRecord] // You should change the var name and coding keys
}
And parse it like
let note = try decoder.decode(NoteRecords.self, from: data)
I hope this helps.

Resources