Swift and APIs: Decoding JSON tree - ios

In my app, I have a song-searching feature where I call the Happi API (https://happi.dev/docs/music) and it will return songs with song info that matches a search keyword passed in.
Here is my code for accessing the data:
func getSongs(completion: #escaping(Result<[SongInfo], SongError>) -> Void) {
let dataTask = URLSession.shared.dataTask(with: resourceURL) { data, _, _ in
guard let jsonData = data else {
completion(.failure(.noDataAvailable))
return
}
do {
let decoder = JSONDecoder()
let songResponse = try decoder.decode(SongResponse.self, from: jsonData)
//print("decoded")
let songInfos = songResponse.response.results
completion(.success(songInfos))
}catch{
completion(.failure(.canNotProcessData))
}
}
dataTask.resume()
}
the let dataTask portion of the code is successful, and does not return the noDataAvailable error. However, I do receive a canNotProcessData error when I go decode the JSON data, and I assuming it's because of an error in my structure, but not entirely sure. Here's my structure:
struct SongResponse:Decodable {
var response:Results
}
struct Results:Decodable {
var results: [SongInfo]
}
struct SongInfo:Decodable {
var songName:String
var artist:String
}
Here are photos of my JSON tree from the API:image 1
image 2
Essentially, all I want is to retrieve each array in the results:[] dictionary, then for each array, I want to get the values for track and artist. For example in the image there are 5 results, and for each result i want to create a song class with "track" and "artist" attributes. These song classes would be stored in an array. Please let me know how to change my code, or of an easier way to do this!
EDIT: error message:
keyNotFound(CodingKeys(stringValue: "response", intValue: nil), Swift.DecodingError.Context(codingPath: [], debugDescription: "No value associated with key CodingKeys(stringValue: "response", intValue: nil) ("response").", underlyingError: nil))

Following is the correct structure
do {
let decoder = JSONDecoder()
let songResponse = try decoder.decode(SongResponse.self, from: jsonData)
let songInfos = songResponse.result ?? []
completion(.success(songInfos))
}catch{
completion(.failure(.canNotProcessData))
}
// Models
struct SongResponse: Decodable {
var success: Bool?
var result: [SongInfo]?
}
struct SongInfo: Decodable {
var track: String?
var artist: String?
}

To mirror the json structure, you have to change the key in SongResponse.
struct SongResponse: Decodable {
var result: [SongInfo]
}

Related

Swift Dictionary API "No value associated with key CodingKeys(stringValue: \"type\", intValue: nil) (\"type\").", underlyingError: nil))

I am running into a error showing that there is no value for "type" from the API I'm trying to grab from. Ive tried looking through other posts revolving around this but can't find anything that works for me without causing a different issue to come up.
The full error I'm getting is "No value associated with key CodingKeys(stringValue: "type", intValue: nil) ("type").", underlyingError: nil))
The API I'm trying to grab from is: https://github.com/ToontownRewritten/api-doc/blob/master/invasions.md
import UIKit
struct InvasionList: Decodable {
let type: String
let asOf: Int?
let progress: String?
}
class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
let jsonUrlString = "https://www.toontownrewritten.com/api/invasions"
guard let url = URL(string: jsonUrlString) else { return }
URLSession.shared.dataTask(with: url) { (data, response, err) in
guard let data = data else {return }
do {
let invasions = try JSONDecoder().decode(InvasionList.self, from: data)
print(invasions.type)
} catch let err {
print(err)
}
}.resume()
}
}
Your InvasionList doesn't actually match the structure of the JSON that you're trying to decode. It matches one small part of it, but you need to decode the entire thing. This includes the outermost wrapper (the part with lastUpdated and the dynamic keys for each "invasion").
Here's a working example:
let jsonData = """
{"lastUpdated":1624230138,"invasions":{"Gulp Gulch":{"asOf":1624230127,"type":"Robber Baron","progress":"3797/7340"},"Splashport":{"asOf":1624230129,"type":"Ambulance Chaser","progress":"2551/8000"},"Kaboom Cliffs":{"asOf":1624230131,"type":"Money Bags","progress":"5504/6000"},"Boingbury":{"asOf":1624230132,"type":"Tightwad","progress":"4741/8580"}},"error":null}
""".data(using: .utf8)!
struct InvasionWrapper: Decodable {
let lastUpdated : Int
let invasions : [String:Invasion]
}
struct Invasion: Decodable {
let type: String
let asOf: Int?
let progress: String?
}
do {
let list = try JSONDecoder().decode(InvasionWrapper.self, from: jsonData)
print(list)
} catch {
print(error)
}

Error: keyNotFound(CodingKeys(stringValue: "origin", intValue: nil),

**This question shows research effort; it is useful and clear
I am building a small swift dog fetch app using the DogsApi and I am running into some issues trying to parse the JSON. I have used the following function to parse the get and parse the JSON.
Below is my viewController:**
import UIKit
class ViewController: UIViewController {
#IBOutlet weak var tableView: UITableView!
var dogs = [DogStats]()
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
downloadJson {
print("Successful")
}
}
func downloadJson(completed: #escaping () -> ()) {
if let url = URL(string: "https://raw.githubusercontent.com/DevTides/DogsApi/master/dogs.json") {
URLSession.shared.dataTask(with: url) { data, response, error in
if let data = data {
do {
let dogs = try JSONDecoder().decode([DogStats].self, from: data)
print(dogs)
DispatchQueue.main.async {
self.dogs = dogs
self.tableView.reloadData()
}
} catch let error {
print(error)
}
}
}.resume()
}
}
}
below is my struct
import Foundation
struct DogStats: Codable {
let name: String
let origin: String
let breed_group: String!
let life_span: String
let temperament: String
}
Error in debugger
keyNotFound(CodingKeys(stringValue: "origin", intValue: nil), Swift.DecodingError.Context(codingPath: [_JSONKey(stringValue: "Index 5", intValue: 5)], debugDescription: "No value associated with key CodingKeys(stringValue: "origin", intValue: nil) ("origin").", underlyingError: nil))
origin, breed_group, temperament are not contained in all of your JSON objects in your API data. So there parse error occurred. You can make those optional(i.e. let origin: String?). You can also make all of your fields optional.
struct DogStats: Codable {
let name: String
let origin: String?
let breed_group: String?
let life_span: String
let temperament: String?
}

Can't decode JSON because Content-Type missed

My JSONDecoder().decode can't decode data to json format, because server response has Content-Type like this "* \ *;charset=utf8".
What I have to do in this situation? Any ideas? API link
My code:
private static let livePhotoUrlString = "https://m1.kappboom.com/livewallpapers/info?o=0&v=575"
static func getLivePhotos(completionHandler: #escaping (([LivePhoto]) -> Void)) {
guard let livePhotoUrl = URL(string: livePhotoUrlString) else { return }
let semaphore = DispatchSemaphore(value: 0)
URLSession.shared.dataTask(with: livePhotoUrl) { (data, response, error) in
do {
guard let data = data else { return }
let livePhotos = try JSONDecoder().decode([LivePhoto].self, from: data)
completionHandler(livePhotos)
} catch {
completionHandler([])
}
semaphore.signal()
}.resume()
semaphore.wait()
}
My entity (LivePhoto):
class LivePhoto: Decodable {
init(smallUrl: String, largeUrl: String, movieUrl: String, id: Int, isLocked: Bool, promotionalUnlock: Bool) {
self.smallUrl = smallUrl
self.largeUrl = largeUrl
self.movieUrl = movieUrl
self.id = id
self.isLocked = isLocked
self.promotionalUnlock = promotionalUnlock
}
var smallUrl: String
var largeUrl: String
var movieUrl: String
var id: Int
var isLocked: Bool
var promotionalUnlock: Bool
}
Response headers:
Correct response (another API):
The error is not related to the content type.
Rather than ignoring the error in the catch block print it, decoding errors are very descriptive.
} catch {
print(error)
completionHandler([])
}
It states
keyNotFound(CodingKeys(stringValue: "smallUrl", intValue: nil), Swift.DecodingError.Context(codingPath: [_JSONKey(stringValue: "Index 0", intValue: 0)], debugDescription: "No value associated with key CodingKeys(stringValue: \"smallUrl\", intValue: nil) (\"smallUrl\").", underlyingError: nil))
You can see immediately that the key is small_url and your struct member is smallUrl.
The easiest solution is to add the convertFromSnakeCase key decoding strategy
let decoder = JSONDecoder()
decoder.keyDecodingStrategy = .convertFromSnakeCase
let livePhotos = try decoder.decode([LivePhoto].self, from: data)
And you don't need the init method in the class. Declare it as struct with constant members
struct LivePhoto: Decodable {
let smallUrl, largeUrl, movieUrl: String
let id: Int
let isLocked: Bool
let promotionalUnlock: Bool
}
And please delete this horrible semaphore. As you are using a completion handler anyway it's pointless.
Are you sure that is the problem, to me it looks like you need to define coding keys for your struct
enum CodingKeys: String, CodingKey {
case smallUrl = "small_url"
case largeUrl = "large_url"
case movieUrl = "movie_url"
case isLocked = "is_locked"
case promotionalUnlock = "promotional_unlock"
case id
}
You need to use key names as they are in json , or write an enum with the converted names , but better to use convertFromSnakeCase
func getLivePhotos(completionHandler: #escaping (([LivePhoto]) -> Void)) {
guard let livePhotoUrl = URL(string: livePhotoUrlString) else { return }
URLSession.shared.dataTask(with: livePhotoUrl) { (data, response, error) in
print(data)
do {
guard let data = data else { return }
let dec = JSONDecoder()
dec.keyDecodingStrategy = .convertFromSnakeCase
let livePhotos = try dec.decode([LivePhoto].self, from: data)
completionHandler(livePhotos)
} catch {
print(error)
completionHandler([])
}
}.resume()
}
}
struct LivePhoto: Codable {
let id: Int
let smallUrl, largeUrl: String
let movieUrl: String
let isLocked, promotionalUnlock: Bool
}
Also it's a best practice to always print(error) inside the catch block , so you can know the error and fix , here there is no place for semaphores , it's only the job of the completion , also you may show an activity indicator until the request finishes as a better UX

unable to decode json with text in get request

I need to create GET request like this:
https://public-api.nazk.gov.ua/v1/declaration/?q=Чер
https://public-api.nazk.gov.ua/v1/declaration/?q=Володимирович
Last characters after = are Cyrillic symbols
I make get request something like this:
var hostURL = "https://public-api.nazk.gov.ua/v1/declaration/?q="
hostURL = hostURL + searchConditions
let escapedSearchConditions = hostURL.addingPercentEncoding(withAllowedCharacters:NSCharacterSet.urlQueryAllowed)
let url = URL(string: escapedSearchConditions!)!
Request is:
https://public-api.nazk.gov.ua/v1/declaration/?q=%D0%9F%D1%80%D0%BE
that return necessary data from server but returned data can't be decoded.
it works fine with integers in search condition but not with Cyrillic text(
import Foundation
struct Declarant: Codable {
var id: String
var firstname: String
var lastname: String
var placeOfWork: String
var position: String
var linkPDF: String
}
struct DeclarationInfo: Codable {
let items: [Declarant]
}
import Foundation
struct DeclarationInfoController {
func fetchDeclarationInfo (with searchConditions: String, completion: #escaping(DeclarationInfo?) -> Void) {
var hostURL = "https://public-api.nazk.gov.ua/v1/declaration/?q="
hostURL = hostURL + searchConditions
let escapedSearchConditions = hostURL.addingPercentEncoding(withAllowedCharacters:NSCharacterSet.urlQueryAllowed)
let url = URL(string: escapedSearchConditions!)!
print(url)
let dataTask = URLSession.shared.dataTask(with: url) {
(data, response, error) in
let jsonDecoder = JSONDecoder()
print("Trying to decode data...")
if let data = data,
let declarationInfo = try? jsonDecoder.decode(DeclarationInfo.self, from: data) {
completion(declarationInfo)
print(declarationInfo)
} else {
print("Either no data was returned, or data was not properly decoded.")
completion(nil)
}
}
dataTask.resume()
}
}
import UIKit
class DeclarationViewController: UIViewController {
let declarationInfoController = DeclarationInfoController()
#IBOutlet weak var searchBar: UISearchBar!
#IBOutlet weak var resultLabel: UILabel!
#IBAction func beginSearchButton(_ sender: UIButton) {
declarationInfoController.fetchDeclarationInfo(with: searchBar.text!) { (declarationInfo) in
if let declarationInfo = declarationInfo {
DispatchQueue.main.async {
self.resultLabel.text = declarationInfo.items[0].lastname
}
}
}
}
}
Never ever use try? ignoring the error while decoding JSON. Codable errors are incredibly descriptive and tell you exactly what's wrong.
Use always a do catch block like
do {
let declarationInfo = try jsonDecoder.decode(DeclarationInfo.self, from: data)
} catch { print error }
and print the error rather than useless literal strings.
The error has nothing to do with Cyrillic text.
The suggested JSON struct in the comments of one of your previous questions
struct Item: Codable {
let id, firstname, lastname, placeOfWork: String
let position, linkPDF: String
}
reveals the error (the most significant parts are emphasized)
keyNotFound(CodingKeys(stringValue: "position", intValue: nil), Swift.DecodingError.Context(codingPath: [CodingKeys(stringValue: "items", intValue: nil), _JSONKey(stringValue: "Index 11", intValue: 11)], debugDescription: "No value associated with key CodingKeys(stringValue: \"position\", intValue: nil) (\"position\").", underlyingError: nil))
It clearly describes that in the struct Item there is no value for key position in the item at index 11 of the array.
The solution is to declare this particular struct member as optional
struct Item: Codable {
let id, firstname, lastname, placeOfWork: String
let position : String?
let linkPDF: String
}
Once again: Don't ignore errors, they help you to fix the issues instantly.
Update
if let data = data,
let declarationInfo = try? jsonDecoder.decode(DeclarationInfo.self, from: data) {
completion(declarationInfo)
print(declarationInfo)
} else {
print("Either no data was returned, or data was not properly decoded.")
completion(nil)
}
by
do {
if let data = data {
let declarationInfo = try jsonDecoder.decode(DeclarationInfo.self, from: data)
completion(declarationInfo)
print(declarationInfo)
return
} catch {
print(error)
}
completion(nil)
You will have the error printed, ans do you will know why decoding fails

How to parse jsondata using API in swift 4

"collection_listings" = (
{
"body_html" = "";
"collection_id" = 57229082710;
"default_product_image" = "<null>";
handle = men;
image = {
"created_at" = "2018-05-02T01:34:16-04:00";
src = "https://cdn.shopify.com/s/files/1/2331/3377/collections/men.jpg?v=1525239256";
};
"published_at" = "2018-05-02T01:34:16-04:00";
"sort_order" = manual;
title = Men;
"updated_at" = "2018-05-02T08:01:58-04:00";
}
How to print this data in the simulator using swift 4?
While I'm trying to print this data, I get this error:
typeMismatch(Swift.Array, Swift.DecodingError.Context(codingPath:
[], debugDescription: "Expected to decode Array but found a
dictionary instead.", underlyingError: nil))
Here is my updated code:
import UIKit
struct product: Decodable
{
let product_id : String
let title : String
let image : String
}
class ViewController: UIViewController,UICollectionViewDataSource
{
var products = product
#IBOutlet weak var productCell: UICollectionView!
override func viewDidLoad()
{
super.viewDidLoad()
productCell.dataSource = self
guard let url = URL(string: "https://psofttech-test.myshopify.com/admin/collection_listings.json") else { return }
var request = URLRequest(url: url)
request.httpMethod = "GET"
let session = URLSession.shared
session.dataTask(with: url) { (data, response, error) in
if error == nil
{
do
{
let json = try JSONSerialization.jsonObject(with: data!) as? [String: Any]
self.products = try JSONDecoder().decode([product].self, from: data!)
print(self.products, "dddd")
for info in self.products
{
self.productCell.reloadData()
}
}
catch
{
print(error)
}
}
}.resume()
print(products.self,"0000")
}
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int
{
return products.count
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell
{
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "productCollection", for: indexPath) as! productCollectionViewCell
cell.proLBL.text = products[indexPath.row].title
return cell
}
}
See if this code helps you..
if let data = data as? [String: Any] {
if let data = data["collection_listings"] as? NSArray {
for data in data {
if let data = data as [String: Any] {
}
}
}
}
Now you can use codable to parse json data in Swift 4.0.
Using Codable, we can model JSONObject or PropertyList file into
equivalent Struct or Classes by writing very few lines of code. We
don’t have to write the constructor for the properties in the
objects. It’s all handed by Codable. We just need to extend our model
to conform to the Codable, Decodable or Encodable protocol.
Mismatch between the strong data types of Swift and lose data types
of JSON has been internally handled by Swift compiler. We can now
handle Swift Data types like Date, URL, Float etc
Complex JSON can be modelled easily using Nesting Structs for
readability.
Parsing actual JSON become one-liner using JSONDecoder
You can refer to this app for using the codable : Demo App for Codable Swift
You're using the wrong object to decode. Basically it's telling you that the object type you sent to the decoder does not match the JSON. In your case (without seeing your code it's hard to tell exactly) it seems the object you provided is of type of array, and the JSON is a dictionary. This is the method I typically use:
Specify a struct of the type you want to decode:
struct Response {
var collection_listings: Listing
}
struct Listing {
var collection_id: String
}
In your decoder specific something like:
let decoder = JSONDecoder()
let apiResponse = try decoder.decode(Response.self, from: data)

Resources