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
Related
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)
}
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]
}
**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?
}
I'm trying to decode data from this massive JSON Array https://coronavirus-19-api.herokuapp.com/countries I've had luck decoding by country or using the total stat worldwide https://coronavirus-19-api.herokuapp.com/all
by doing the following
//
// GlobalSwiftViewController.swift
// Universal
//
// Created by JOE on 3/20/20.
import UIKit
final class StatSwiftViewController: UIViewController {
// THESE LABELS WILL RETRIEVE THE FOLLOWING DATA FROM THE URL: THE CASE , DEATH AND RECOVERED DATA
#IBOutlet weak var CaseLable: UILabel!
#IBOutlet weak var DeathLable: UILabel!
#IBOutlet weak var RecoveredLabel: UILabel!
struct JSONTest: Decodable {
let cases: Double
let deaths: Float
let recovered: Int?
}
override func viewDidLoad() {
super.viewDidLoad()
let urlString = "https://coronavirus-19-api.herokuapp.com/all"
guard let url = URL(string: urlString) else { return }
URLSession.shared.dataTask(with: url) { (data, response, error) in
if error != nil {
print(error!.localizedDescription)
}
guard let data = data else { return }
do {
//Decode data
let urlString = try JSONDecoder().decode(JSONTest.self, from: data)
let numberFormatter = NumberFormatter()
numberFormatter.numberStyle = .decimal
//HERE WE ARE SHOWING TO THE USER THE DATA FROM THE URL ABOVE
DispatchQueue.main.async {
self.CaseLable.text = numberFormatter.string(for: urlString.cases)
self.DeathLable.text = numberFormatter.string(for: urlString.deaths)
self.RecoveredLabel.text = numberFormatter.string(for: urlString.recovered)
//self.timeLabel.text = JSONTest.deaths
}
} catch let jsonError {
print(jsonError)
}
}.resume()
}
}
Now I'm trying to decode all of the data in this URL https://coronavirus-19-api.herokuapp.com/countries to show in one view controller, I've had success by using the single URL https://coronavirus-19-api.herokuapp.com/countries/china for the country using the same code above by just adding more vars and labels However, I'm not able to add more counties by adding each URL for each country or using the main URL for all countries https://coronavirus-19-api.herokuapp.com/countries Therefore, How can I struct all Array List using the URL for all countries?
note: Im trying to edit/update my code above to get the results as possible without installing extra pods or files...
Try to adapt your model to be able to decode the countries data.
You can test this in a Playground:
import Foundation
struct JSONTestElement: Codable {
let country: String
let cases, todayCases, deaths, todayDeaths: Int
let recovered, active, critical, casesPerOneMillion: Int
}
typealias JSONTest = [JSONTestElement]
func decode() {
let urlString = "https://coronavirus-19-api.herokuapp.com/countries"
guard let url = URL(string: urlString) else { return }
URLSession.shared.dataTask(with: url) { (data, response, error) in
if error != nil {
print(error!.localizedDescription)
}
guard let data = data else { return }
do {
//Decode data
let countriesData = try JSONDecoder().decode(JSONTest.self, from: data)
let china = countriesData.filter({ $0.country.contains("China")})
print("China data: \(china)")
} catch let jsonError {
print(jsonError)
}
}.resume()
}
decode()
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