Fetching Data from API in Swift - json

So I am trying to fetch data from the Pokemon API, and I am getting stuck at the point where I am trying to decode the JSON into a struct. Here is my code:
{
"count":1118,
"next":"https://pokeapi.co/api/v2/pokemon/?offset=20&limit=20",
"previous":null,
"results":
[
{"name":"bulbasaur","url":"https://pokeapi.co/api/v2/pokemon/1/"},
{"name":"ivysaur","url":"https://pokeapi.co/api/v2/pokemon/2/"},
{"name":"venusaur","url":"https://pokeapi.co/api/v2/pokemon/3/"},
{"name":"charmander","url":"https://pokeapi.co/api/v2/pokemon/4/"},
{"name":"charmeleon","url":"https://pokeapi.co/api/v2/pokemon/5/"},
{"name":"charizard","url":"https://pokeapi.co/api/v2/pokemon/6/"},
{"name":"squirtle","url":"https://pokeapi.co/api/v2/pokemon/7/"},
{"name":"wartortle","url":"https://pokeapi.co/api/v2/pokemon/8/"},
{"name":"blastoise","url":"https://pokeapi.co/api/v2/pokemon/9/"},
{"name":"caterpie","url":"https://pokeapi.co/api/v2/pokemon/10/"},
{"name":"metapod","url":"https://pokeapi.co/api/v2/pokemon/11/"},
{"name":"butterfree","url":"https://pokeapi.co/api/v2/pokemon/12/"},
{"name":"weedle","url":"https://pokeapi.co/api/v2/pokemon/13/"},
{"name":"kakuna","url":"https://pokeapi.co/api/v2/pokemon/14/"},
{"name":"beedrill","url":"https://pokeapi.co/api/v2/pokemon/15/"},
{"name":"pidgey","url":"https://pokeapi.co/api/v2/pokemon/16/"},
{"name":"pidgeotto","url":"https://pokeapi.co/api/v2/pokemon/17/"},
{"name":"pidgeot","url":"https://pokeapi.co/api/v2/pokemon/18/"},
{"name":"rattata","url":"https://pokeapi.co/api/v2/pokemon/19/"},
{"name":"raticate","url":"https://pokeapi.co/api/v2/pokemon/20/"}
]
}
func fetchPokemon() {
let defaultSession = URLSession(configuration: .default)
if let url = URL(string: "https://pokeapi.co/api/v2/pokemon/") {
let request = URLRequest(url:url)
let dataTask = defaultSession.dataTask(with: request, completionHandler: { (data, response, error) -> Void in
guard error == nil else {
print ("error: ", error!)
return
}
guard data != nil else {
print("No data object")
return
}
guard let httpResponse = response as? HTTPURLResponse, (200...299).contains(httpResponse.statusCode) else {
print("response is: ", response!)
return
}
guard let mime = response?.mimeType, mime == "application/json" else {
print("Wrong MIME type!")
return
}
DispatchQueue.main.async {
guard let result = try? JSONDecoder().decode(PokemonList.self, from: data!) else {
print("Error Parsing JSON")
return
}
let pokemon = result.pokemon
self.Pokemon = pokemon
print(self.Pokemon)
}
})
dataTask.resume()
}
}
and here is the pokemon struct:
struct Pokemon {
// Various properties of a post that we either need or want to display
let name: String
let url: String
}
extension Pokemon: Decodable {
// properties within a Post returned from the Product Hunt API that we want to extract the info from.
enum PokemonKeys: String, CodingKey {
// first three match our variable names for our Post struct
case name = "name"
case url = "url"
}
init(from decoder: Decoder) throws {
let postsContainer = try decoder.container(keyedBy: PokemonKeys.self)
name = try postsContainer.decode(String.self, forKey: .name)
url = try postsContainer.decode(String.self, forKey: .url)
}
}
struct PokemonList: Decodable {
var pokemon: [Pokemon]
}
It keeps reaching the point when decoding which says "Error Parsing JSON". I'm assuming that there may be an error in how I setup the pokemon struct?
Any ideas?

you are getting a parse error because the data model is not the same. your struct should be:
struct PokemonList: Decodable {
var results: [Pokemon]
var count: Int
var next: String
}
you don't need the extension.

Related

Swift Parse Json Data [duplicate]

This question already has answers here:
Get all file names from a Github repo through the Github API
(5 answers)
Closed 4 months ago.
I am making an api call to the github api to get the names of folders in a repository. I do not know how to extract the data from the api call and where to go from here. Any help would be appriciated!
Code:
func extractData() {
let url = URL(string: "https://api.github.com/repos/myrepository/myrepository/contents/folder")!
let task = URLSession.shared.dataTask(with: url) { (data, response, error) in
if let error = error {
print("Error with fetching repos: \(error)")
return
}
guard let httpResponse = response as? HTTPURLResponse,
(200...299).contains(httpResponse.statusCode) else {
print("Error with the response, unexpected status code: \(String(describing: response))")
return
}
if let mimeType = httpResponse.mimeType, mimeType == "application/json",
let data = data,
let dataString = String(data: data, encoding: .utf8) {
print("Got data: \(dataString)")
do {
if let json = try JSONSerialization.jsonObject(with: data, options: []) as? [String: Any] {
print(json["name"])
}
} catch let error as NSError {
print("Failed to load: \(error.localizedDescription)")
}
}
}
task.resume()
}
To get the names of the folders in a repository, try this example code.
It shows how to call github and return the list of folders for one of my repo.
It then displays the folders in a List.
struct ContentView: View {
#State var folders: [RepoContent] = []
var body: some View {
List(folders) { folder in
Text(folder.name) + Text(" \(folder.type)").foregroundColor(folder.type == "dir" ? .blue : .red)
}
.onAppear {
getRepoFolders(owner: "workingDog", repo: "OWOneCall")
}
}
func getRepoFolders(owner: String, repo: String) {
guard let url = URL(string: "https://api.github.com/repos/\(owner)/\(repo)/contents") else {
return
}
let task = URLSession.shared.dataTask(with: url) { (data, response, error) in
if let error = error {
print("Error with fetching repos: \(error)")
return
}
guard let httpResponse = response as? HTTPURLResponse,
(200...299).contains(httpResponse.statusCode) else {
print("Error with the response, unexpected status code: \(String(describing: response))")
return
}
if let data = data {
do {
let response = try JSONDecoder().decode([RepoContent].self, from: data)
self.folders = response
} catch {
print("\n error: \(error)\n")
}
}
}
task.resume()
}
}
// MARK: - RepoContent
struct RepoContent: Identifiable, Codable {
let id = UUID()
let name, path, sha: String
let size: Int
let url, htmlURL: String
let gitURL: String
let downloadURL: String?
let type: String
let links: Links
enum CodingKeys: String, CodingKey {
case name, path, sha, size, url, type
case htmlURL = "html_url"
case gitURL = "git_url"
case downloadURL = "download_url"
case links = "_links"
}
}
// MARK: - Links
struct Links: Codable {
let linksSelf: String
let git: String
let html: String
enum CodingKeys: String, CodingKey {
case linksSelf = "self"
case git, html
}
}

Decoding the incoming json string in swift after api call

here is the string(my whole response is string) which i get after api call .
"{'result': {'ip':'49.36.183.40','id':'T1199','Date':'2022-7-24','Time':'20:58:36','Temp':38.94,'PM25':117.00,'lux':7.00,'VOC':586.00,'CO':0.97,'CO2':828.00,'O3':118.00,'RH':48.88,'Pres':989.00}}",,,
I tried this since I was receiving the string back :
#Published var singleData : [iotData] = []
func loadSingleData() {
if let url = URL(string: urlString) {
URLSession.shared.dataTask(with: url) { [weak self ] data , response, error in
DispatchQueue.main.async {
let decoder = JSONDecoder()
if let data = data {
if let users = try? decoder.decode([String : iotData].self, from: data) {
print(users)
} else {
print("failed to decode the data")
}
} else {
print("Failed to load the data")
}
}
}.resume()
} else {
print("wrong url")
}
}
but my output is failed to decode the data
also there are commas outside json string how do i take care of those .
i am beginner in ios development so i don't have any clue how to decode this json string and use it.how can i get past this
here is my model for this
struct iotData : Codable {
var result : Result
}
struct Result: Codable {
let ip, id, Date, Time: String
let Temp: Double
let PM25, lux, VOC: Int
let CO: Double
let CO2, O3: Int
let RH: Double
let Pres: Int
}
Use:
decoder.decode(iotData.self, from: data) { }
Instead of
decoder.decode(iotData, from: data) { }
Also make sure 'data' is actually 'Data' object

Swift - JSON decoding

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

How to parse JSON using swift 4

I am confusing to getting detail of fruit
{
"fruits": [
{
"id": "1",
"image": "https://cdn1.medicalnewstoday.com/content/images/headlines/271/271157/bananas.jpg",
"name": "Banana"
},
{
"id": "2",
"image": "http://soappotions.com/wp-content/uploads/2017/10/orange.jpg",
"title": "Orange"
}
]
}
Want to parse JSON using "Decodable"
struct Fruits: Decodable {
let Fruits: [fruit]
}
struct fruit: Decodable {
let id: Int?
let image: String?
let name: String?
}
let url = URL(string: "https://www.JSONData.com/fruits")
URLSession.shared.dataTask(with: url!) { (data, response, error) in
guard let data = data else { return }
do{
let fruits = try JSONDecoder().decode(Fruits.self, from: data)
print(Fruits)
}catch {
print("Parse Error")
}
also can you please suggest me cocoapod library for fastly download images
The issue you are facing is because your JSON is returning different data for your Fruits.
For the 1st ID it returns a String called name, but in the 2nd it returns a String called title.
In addition when parsing the JSON the ID appears to be a String and not an Int.
Thus you have two optional values from your data.
As such your Decodable Structure should look something like this:
struct Response: Decodable {
let fruits: [Fruits]
}
struct Fruits: Decodable {
let id: String
let image: String
let name: String?
let title: String?
}
Since your URL doesn't seem to be valid, I created the JSON file in my main bundle and was able to parse it correctly like so:
/// Parses The JSON
func parseJSON(){
if let path = Bundle.main.path(forResource: "fruits", ofType: "json") {
do {
let data = try Data(contentsOf: URL(fileURLWithPath: path), options: .mappedIfSafe)
let jsonResult = try JSONDecoder().decode(Response.self, from: data)
let fruitsArray = jsonResult.fruits
for fruit in fruitsArray{
print("""
ID = \(fruit.id)
Image = \(fruit.image)
""")
if let validName = fruit.name{
print("Name = \(validName)")
}
if let validTitle = fruit.title{
print("Title = \(validTitle)")
}
}
} catch {
print(error)
}
}
}
Hope it helps...
// Parse Json using decodable
// First in create Structure depends on json
//
//
//
struct Countory : Decodable {
let name: String
let capital: String
let region: String
}
let url = "https://restcountries.eu/rest/v2/all"
let urlObj = URL(string: url)!
URLSession.shared.dataTask(with: urlObj) {(data, responds, Error) in
do {
var countories = try JSONDecoder().decode([Countory].self, from: data!)
for country in countories {
print("Country",country.name)
print("###################")
print("Capital",country.capital)
}
} catch {
print(" not ")
}
}.resume()
Model sample:
public struct JsonData: Codable{
let data: [Data]?
let meta: MetaValue?
let linksData: LinksValue?
private enum CodingKeys: String, CodingKey{
case data
case meta
case linksData = "links"
}
}
enum BackendError: Error {
case urlError(reason: String)
case objectSerialization(reason: String)
}
struct APIServiceRequest {
static func serviceRequest<T>(reqURLString: String,
resultStruct: T.Type,
completionHandler:#escaping ((Any?, Error?) -> ())) where T : Decodable {
guard let url = URL(string: reqURLString) else {
print("Error: cannot create URL")
let error = BackendError.urlError(reason: "Could not construct URL")
completionHandler(nil, error)
return
}
let urlRequest = URLRequest(url: url)
let session = URLSession.shared
let task = session.dataTask(with: urlRequest) { (data, response, error) in
guard error == nil else {
completionHandler(nil, error)
return
}
guard let responseData = data else {
print("Error: did not receive data")
let error = BackendError.objectSerialization(reason: "No data in response")
completionHandler(nil, error)
return
}
let decoder = JSONDecoder()
do {
let books = try decoder.decode(resultStruct, from: responseData)
completionHandler(books, nil)
} catch {
print("error trying to convert data to JSON")
print(error)
completionHandler(nil, error)
}
}
task.resume()
}
}
To Access:
let apiService = APIServiceRequest()
var dataArray: [String: Any]? //global var
apiService.serviceRequest(reqURLString: endPoint, resultStruct: VariantsModel.self, completionHandler: {dataArray,Error in})
POST Method
func loginWS(endpoint: String, completionHandler: #escaping (Any?) -> Swift.Void) {
guard let sourceUrl = URL(string: endpoint) else { return }
let request = NSMutableURLRequest(url: sourceUrl)
let session = URLSession.shared
request.httpMethod = "POST"
request.addValue(vehiceHeader, forHTTPHeaderField: "X-Vehicle-Type")
request.addValue(contentHeader, forHTTPHeaderField: "Content-Type")
let task = session.dataTask(with: request as URLRequest) { data, response, error in
guard let data = data else { return }
do {
let responseData = try JSONDecoder().decode(JsonData.self, from: data)
print("response data:", responseData)
completionHandler(responseData)
} catch let err {
print("Err", err)
}
}.resume()
}

Parsing JSON array without for statement

I want to find a solution for parsing JSON array. there is my code for parsing JSON but I want a solution without for statement I can parse array.
code for parsing:
func parsigJsonData(resultArray:any?){
if let resultArray = resultDic["trucks"] as? BaseModelData {
print(resultArray)
}
}
class BaseModel {
public typealias BaseModelData = (id:String?,title:String?,select:Bool)
var id : String?
var title : String?
var select : Bool = false
init(json: [String:Any]) {
self.id = json["id"] as? String
self.title = json["title"] as? String
self.select = false
}
}
extension BaseModel {
var tableRepresentation: [BaseModelData] {
return [(id:id,title:title,select:select)]
}
}
result array contains list of baseModel object. I try this code for parsing but that's not working and casting to BaseModelData unsuccessful.
if there is a solution for parsing JSON array without for statement?
Thank you for the solutions.
there is my json response sample:
{"trucks":[{"id":"1","title":"\u062e\u0627\u0648\u0631 \u062a\u0627 5 \u062a\u0646 \u0627\u062a\u0627\u0642 \u0686\u0648\u0628\u06cc"},{"id":"2","title":"\u062e\u0627\u0648\u0631 \u062a\u0627 5 \u062a\u0646 \u06a9\u0645\u067e\u0631\u0633\u06cc"},{"id":"3","title":"\u062e\u0627\u0648\u0631 \u062a\u0627 8 \u062a\u0646 \u0627\u062a\u0627\u0642 \u0686\u0648\u0628\u06cc"},{"id":"4","title":"\u062e\u0627\u0648\u0631 \u062a\u0627 8 \u062a\u0646 \u06a9\u0645\u067e\u0631\u0633\u06cc"},{"id":"5","title":"\u062e\u0627\u0648\u0631 \u06cc\u062e\u0686\u0627\u0644 \u062f\u0627\u0631"}]
Swift 4 allows you to make it very simple. Just add Decodable protocol,
struct BaseModel: Codable {
var id : String?
var title : String?
var select : Bool = false
}
And use this to retrieve data:
func makeRequest(completionHandler: #escaping ([BaseModel]?, Error?) -> Void) {
var urlRequest = URLRequest(url: url)
request.httpMethod = "GET"
let task = URLSession.shared.dataTask(with: request as URLRequest){
(data, response, error) in
guard let responseData = data else {
print("Error: did not receive data")
completionHandler(nil, error)
return
}
guard error == nil else {
completionHandler(nil, error)
return
}
let decoder = JSONDecoder()
do {
let dataObject = try decoder.decode([BaseModel].self, from: responseData)
completionHandler(dataObject, nil)
} catch {
print("error trying to convert data to JSON")
print(error)
completionHandler(nil, error)
}
}
task.resume()
}