I think this will be simple I assume I'm just missing something about JSON structure here. I have some code that pulls down some data from an API handle to get a list of country names:
https://restcountries.eu/rest/v2/all?fields=name
Here is a sample of the API data though please feel free to view it using the link above:
[{"name":"Afghanistan"},{"name":"Åland Islands"},{"name":"Albania"},{"name":"Algeria"},{"name":"American Samoa"},{"name":"Andorra"},{"name":"Angola"},{"name":"Anguilla"},{"name":"Antarctica"},{"name":"Antigua and Barbuda"},{"name":"Argentina"}
I created this struct to hold the data
struct CountryList: Codable {
public let country: [Country]
}
struct Country: Codable {
public let name: String
}
I have these two functions that create the URLRequest and then grab the data and return it via a completion handler:
private func setupApiUrlRequest(apiURL: String) throws -> URLRequest {
let urlString = apiURL
guard let url = URL(string: urlString) else {
print("Error setting up URL")
throw CountriesError.invalidURLString
}
var request = URLRequest(url: url)
request.httpMethod = "GET"
return request
}
func getCountries(completion: #escaping (Country?, URLResponse?, Error?) -> Void) {
if let request = try? setupApiUrlRequest(apiURL: "https://restcountries.eu/rest/v2/all?fields=name") {
URLSession.shared.dataTask(with: request) { data,response,error in
guard let data = data else {
completion(nil, response, error)
return
}
do {
let decoder = JSONDecoder()
let downloadedCountries = try decoder.decode(Country.self, from: data)
completion(downloadedCountries, response, nil)
} catch {
print(error.localizedDescription)
completion(nil, response, error)
}
}.resume()
}
}
This gives me an error:
The data couldn’t be read because it isn’t in the correct format.
So it seems like my Struct is not correct somehow but I am just not sure how. Can anyone offer any guidance? I have a few other functions using almost identical code that grab API JSON Data and decode it into structs... just missing something here.
The JSON you've provided is not in the correct format.
Valid JSON:
[{"name":"Afghanistan"},{"name":"Åland Islands"},{"name":"Albania"},{"name":"Algeria"},{"name":"American Samoa"},{"name":"Andorra"},{"name":"Angola"},{"name":"Anguilla"},{"name":"Antarctica"},{"name":"Antigua and Barbuda"},{"name":"Argentina"}]
You need to use the [Country].self instead of just Country.self while parsing, i.e.
do {
let downloadedCountries = try JSONDecoder().decode([Country].self, from: data)
print(downloadedCountries)
} catch {
print(error)
}
Also, there is not requirement of struct CountryList. You can remove that.
Related
I am trying to parse a nested Json in SwiftUI for the last couple of days and I have no idea how to move forward.
At this point, I suspect that the trouble is a parameter received within the Json named "data" which might cause a confusion between the param value in struct "VTResponse" and the data param that URLSession.shared.dataTask is getting.
Here's the code at this point:
import UIKit
struct VTResponse: Decodable {
let data: [VT]
}
struct VT: Decodable {
var id: String
}
let token = "<TOKEN>"
let XDOMAIN = "<XDOMAIN>"
guard let url = URL(string: "https://www.lalalla.com/subdomains") else {
fatalError("Invalid URL")
}
var request = URLRequest(url: url)
request.httpMethod = "GET"
request.setValue("x-apikey: \(token)", forHTTPHeaderField: "Authorization")
URLSession.shared.dataTask(with: request) { data, response, error in
guard let data = data, error == nil else { return }
let result = try? JSONDecoder().decode(VTResponse.self, from: data)
if let result = result {
result.data.forEach {
print($0.id)
}
}
else {
print("Error")
}
}.resume()
Assuming that I define a token and domain for the query, for example, lookup all of the subdomains of "giphy.com", the Json response:
Json Response - Pastebin
As you can see in the Json response, the subdomains parameter ("id") is under a dictionary, under an array("data"). My guess is the code is trying to assign data to the variable:
guard let data = data, error == nil else { return }
But this is just a guess. And even if so, how could I solve this?
Anyways, I'm getting the following output:
Error
I'm trying to get the following output:
pingback.giphy.com
media3.giphy.com
api.giphy.com
developers.giphy.com
media.giphy.com
x-qa.giphy.com
media1.giphy.com
x.giphy.com
media4.giphy.com
media0.giphy.com
Any ideas?
try this using an x-apikey header for your token:
func fetchData(token: String, XDOMAIN: String, completion: #escaping (VTResponse) -> Void) {
guard let url = URL(string: "https://www.virustotal.com/api/v3/domains/\(XDOMAIN)/subdomains") else {
fatalError("Invalid URL")
}
var request = URLRequest(url: url)
request.httpMethod = "GET"
request.setValue("\(token)", forHTTPHeaderField: "x-apikey") // <-- here
URLSession.shared.dataTask(with: request) { data, response, error in
guard let data = data else { return } // todo return some error msg
do {
let results = try JSONDecoder().decode(VTResponse.self, from: data)
return completion(results)
} catch {
print(error) // <-- here important
}
}.resume()
}
And use it like this:
fetchData(token: "xxx", XDOMAIN: "www") { results in
results.data.forEach {
print("---> id: \($0.id)")
}
}
EDIT-1: to get an array of [String], that is, of id, use this function:
func ListFromSubDomains(token: String, XDOMAIN: String, completion: #escaping ([String]) -> Void) {
fetchData(token: token, XDOMAIN: XDOMAIN) { results in
completion(results.data.map{ $0.id })
}
}
and use it like this:
var IPList: [String] = []
ListFromSubDomains(token: "xxx", XDOMAIN: "www") { ids in
IPList = ids
print("---> ids: \(ids)")
}
I am facing an issue when trying to retrieve and decode JSON data from an API. I am able to get the data from the API and decode it successfully, but I can't access it from outside the function scope. I am using the following function to get and decode data. The API returns an array of JSON objects.
func getJSON(completed: #escaping () -> ()) {
var jsonData = [API_data] () // A struct for retrieved data
let url = "URL Here" // I have the original URL here, which I can't share
let task = URLSession.shared.dataTask(with: URL(string: url)!, completionHandler: { [self]data,response,error in
guard let data = data, error == nil else{
print("error")
return
}
do{
jsonData = try JSONDecoder().decode([API_data].self,from:data)
} catch{
print(error)
}
print(jsonData[0].id) // This prints my data
})
print(jsonData[0].id) // This won't print my data
task.resume()
}
struct API_data: Codable {
let id : String
}
As a result of this, I cannot use this data anywhere in the application. Any help will be appreciated. I have also tried making jsonData a global variable, updating it in the function and returning it and then using it, still doesn't work.
Thanks for your help.
As you already have a completion handler use it and pass the received data
func getJSON(completed: #escaping ([API_data]) -> Void) {
let url = "URL Here" // I have the original URL here, which I can't shar
let task = URLSession.shared.dataTask(with: URL(string: url)!) { data, _ , error in
if let error = error { print(error); return }
do {
completed(try JSONDecoder().decode([API_data].self,from:data!))
} catch{
print(error)
}
}
task.resume()
}
and use it
getJSON { apiData in
print(apiData[0].id) // This prints my data
}
Or more comfortable with the Result type
func getJSON(completed: #escaping (Result<[API_data],Error>) -> Void) {
let url = "URL Here" // I have the original URL here, which I can't shar
let task = URLSession.shared.dataTask(with: URL(string: url)!) { data, _ , error in
if let error = error { completed(.failure(error)); return }
Result { try JSONDecoder().decode([API_data].self,from: data!) }
}
task.resume()
}
getJSON { result in
switch result {
case .success(let apiData): print(apiData[0].id) // This prints my data
case .failure(let error): print(error)
}
}
It is asynchronous, you might want to access it a bit later when it’s ready, e.g. by using the completion handler.
Try creating a completion handler from your API function and send the data with the completion. And try using it there.
I have a JSON response from an api call. The problem is I get different JSON responses depending on whether the user has entered the correct credentials or not. My question is how do I read and decode these responses to a useable struct and what is the best way to go about decoding these different responses. one thing I noticed is both response have a common "isSuccess" that may be useful. I have little to no experience with swift or reading JSON so this is all a learning experience for me.
This is the response for successful login
{"result":{"login":{"isAuthorized":true,"isEmpty":false,"userName":{"isEmpty":false,"name":{"firstName":"Jason","lastName":"Test","displayName":"Test, Jason","isEmpty":false,"fullName":"Jason Test"},"canDelete":false,"id":5793,"canModify":false},"username":"test#testable.com"},"parameters":{"isEmpty":false,"keep_logged_in_indicator":false,"username":"test#testable.com"}},"isAuthorized":true,"version":{"major":"2021","minor":"004","fix":"04","display":"2021.004.04","isEmpty":false},"isSystemDown":false,"timestamp":"2021-07-28T02:47:33Z","isSuccess":true}
This is the response for failure
{"isAuthorized":true,"version":{"major":"2021","minor":"004","fix":"04","display":"2021.004.04","isEmpty":false},"isSystemDown":false,"errors":[{"password":"Unable to login as 'test#testable.com'"}],"timestamp":"2021-07-28T02:47:05Z","isSuccess":false}
This is the code I have written for my api calls
func request<T: Decodable>(endPoint: EndPoint, method: Method, parameters: [String: Any]? = nil, completion: #escaping(Result<T, Error>) -> Void) {
// Creates a urlRequest
guard let request = createRequest(endPoint: endPoint, method: method, parameters: parameters) else {
completion(.failure(AppError.invalidUrl))
return
}
let session = URLSession.shared
session.dataTask(with: request) { data, response, error in
var results: Result<Data, Error>?
guard let httpResponse = response as? HTTPURLResponse, httpResponse.statusCode == 200 else {
completion(.failure(AppError.badStatusCode))
return
}
if let response = response {
// Gets the JSESSIONID
let cookieName = "JSESSIONID"
if let cookie = HTTPCookieStorage.shared.cookies?.first(where: { $0.name == cookieName }) {
debugPrint("\(cookieName): \(cookie.value)")
}
print(response)
}
if let data = data {
results = .success(data)
// Converts data to readable String
let responseString = String(data: data, encoding: .utf8) ?? "unable to convert to readable String"
print("Server Response: \(responseString.description)")
} else if let error = error {
results = .failure(error)
print("Server Error: \(error.localizedDescription)")
}
DispatchQueue.main.async {
self.handleResponse(result: results, completion: completion)
}
}.resume()
}
private func handleResponse<T: Decodable>(result: Result<Data, Error>?, completion: (Result<T, Error>) -> Void) {
guard let result = result else {
completion(.failure(AppError.unknownError))
return
}
switch result {
case .success(let data):
do {
let json = try JSONSerialization.jsonObject(with: data, options: [])
print("Server JsonObject response: \(json)")
} catch {
completion(.failure(AppError.errorDecoding))
}
let decoder = JSONDecoder()
// Decodes that json data
do {
} catch {
}
case .failure(let error):
completion(.failure(error))
}
}
Im mostly interesting in being able to display the json error that occurs when credentials are incorrect. The deadline for my project Is slowing approaching and any help or suggestions would be much appreciated.
You can use Swift's Result type to differentiate a successful result from a failed result.
The Result type is not decodable by default so you will need to write a custom decoder like this:
struct Response: Decodable {
let result: Swift.Result<Result, Errors>
enum CodingKeys: String, CodingKey {
case isSuccess
case errors
case result
}
struct Result: Codable {
let login: Login
struct Login: Codable {
let isAuthorized: Bool
}
}
struct Errors: Error {
let contents: [[String: String]]
}
init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
if try container.decode(Bool.self, forKey: .isSuccess) {
result = .success(try container.decode(Result.self, forKey: .result))
} else {
result = .failure(
Errors(contents: try container.decode([[String: String]].self, forKey: .errors))
)
}
}
}
I have looked at the other answers for similar questions to this but can not find one that explains why this is coming up when requesting data from API. I am mostly confused because the error occurs when I copy the path from the JSON. After reading the error code and the documentation, I think it is something to do with an expected dictionary but found an array instead. I don't understand how to fix this when the data is coming from an API request.
Here is the JSON data that I am aiming to pull from:
Here is a picture of the error:
Here the code I have so far:
import Foundation
struct VaccineManager {
let vaccineURL = "https://coronavirus.data.gov.uk/api/v1/data?filters=areaType=overview&structure=%7B%22areaType%22:%22areaType%22,%22areaName%22:%22areaName%22,%22areaCode%22:%22areaCode%22,%22date%22:%22date%22,%22newPeopleVaccinatedFirstDoseByPublishDate%22:%22newPeopleVaccinatedFirstDoseByPublishDate%22,%22newPeopleVaccinatedSecondDoseByPublishDate%22:%22newPeopleVaccinatedSecondDoseByPublishDate%22,%22cumPeopleVaccinatedFirstDoseByPublishDate%22:%22cumPeopleVaccinatedFirstDoseByPublishDate%22,%22cumPeopleVaccinatedSecondDoseByPublishDate%22:%22cumPeopleVaccinatedSecondDoseByPublishDate%22%7D&format=json"
func performRequest(vaccineURL: String){
if let url = URL(string: vaccineURL) {
let session = URLSession(configuration: .default)
let task = session.dataTask(with: url) { (data, response, error) in
if error != nil {
print(error!)
return
}
if let safeData = data {
self.parseJASON(vaccineData: safeData)
}
}
task.resume()
}
}
func parseJASON(vaccineData: Data) {
let decoder = JSONDecoder()
do{
let decodedData = try decoder.decode(VaccineData.self, from: vaccineData)
print(decodedData.data[0].cumPeopleVaccinatedFirstDoseByPublishDate)
} catch {
print(error)
}
}
And this is the code from the other file I have to for the data:
import Foundation
struct VaccineData: Decodable {
let data: VaccineDataObject
}
struct VaccineDataObject: Decodable {
let cumPeopleVaccinatedFirstDoseByPublishDate: Int
let date: String
}
I hope that all makes sense. Also, please tell me if I am being too detailed.
Your struct definition is incorrect. It says there's a single value in data, but you have an array:
struct VaccineData: Decodable {
let data: VaccineDataObject
}
Should be:
struct VaccineData: Decodable {
let data: [VaccineDataObject]
}
You may find QuickType helpful here. It will take JSON and write decoding structs for you automatically. (Though it looks like you are pulling out such a small part, that it may be easier to hand-write as you have.)
I have a URL which my app fetches. it prints a dictionary with two keys but inside one of the keys is a lot of information I would like to get for my app.
The URL gets lots of information but not as a conventional dictionary.
this is a VERY simplified version:
["person":
name: John
height: 187, "fruit": colour: red
]
etc...
so I would just want to get the name of the person inside the key person but I am having trouble finding this.
Is there any way to do this? I have been trying JSON Parsing, for loops and I am stuck.
Edit:
it isn't a dictionary inside a dictionary. If you would like to see what I am working with. Just copy and paste this link. It is an example of what I am using. http://itunes.apple.com/lookup?bundleId=com.burbn.instagram
I would need just the seller name or just the currency etc.
Code to read the link and print it:
override func viewDidLoad() {
super.viewDidLoad()
fetchData { (dict, error) in
print(dict!)
}
}
func fetchData(completion: #escaping ([String:Any]?, Error?) -> Void) {
let url = URL(string: link)!
let task = URLSession.shared.dataTask(with: url) { (data, response, error) in
guard let data = data else { return }
do {
if let array = try JSONSerialization.jsonObject(with: data, options: .allowFragments) as? [String:Any]{
completion(array, nil)
}
} catch {
print(error)
completion(nil, error)
}
}
task.resume()
}
The data you are fetching is JSON. In order to use it, you will have to decode it. The recommended way is using JSONDecoder in Swift.
First you will have to define your model, which correspond to the data model, and make it conform to Codable protocol:
struct App: Codable {
var sellerName: String
// Alternatively, if you don't want to use an enum, you can use a String.
var currency: Currency
enum Currency: String, Codable {
case australianDollar = "AUD",
case britishPound = "GBP",
case euro = "EUR",
case hongKongDollar = "HKD",
case usDollar = "USD"
// Complete this with all the currency…
}
}
struct JSONResult: Codable {
var resultCount: Int
var results: [App]
}
Once this is done, you only have to edit your fetchData method so it returns an array App populated with the data you fetched.
Swift 4 version:
func fetchData(completion: #escaping (JSONResult?, Error?) -> Void) {
guard let url = URL(string: link) else { return }
let task = URLSession.shared.dataTask(with: url) { (data, response, error) in
if let error = error {
completion(nil, error)
return
} else if let data = data {
do {
let decoder = JSONDecoder()
let result = try decoder.decode(JSONResult.self, from: data)
completion(result, nil)
} catch {
print(error)
completion(nil, error)
}
}
}
task.resume()
}
Swift 5 version using Result type:
func fetchData(completion: #escaping (Result<JSONResult, Error>) -> Void) {
guard let url = URL(string: link) else { return }
let task = URLSession.shared.dataTask(with: url) { (data, response, error) in
if let error = error {
completion(.failure(error))
return
} else if let data = data {
do {
let decoder = JSONDecoder()
let result = try decoder.decode(JSONResult.self, from: data)
completion(.success(result))
} catch {
print(error)
completion(.failure(error))
}
}
}
task.resume()
}
More information about JSONDecoder
Dictionary data is:
let dict = ["person": ["name": "John", "height": "187"], "fruit": ["colour": "red"]]
Suppose you need name of the person. So you can do it by the following way.
if let person = dict["person"], let name = person["name"] as? String {
print (name)
}