Looping through JSON array | Swift 5 - swift

I need to perform an action on each instance of trialid in the following JSON array - How can I loop through each instance of trialid using forEach? The goal is to pass each instance of trialid to another function that only excepts one value of trialid at a time.
The following is the structure of the JSON array:
[
{
"name": "mobile",
"orderid": 1,
"trialid": 27
},
{
"name": "mobile",
"orderid": 1,
"trialid": 33
}
]
The following is the what I am currently trying - how can foreach be performed here to loop through each object:
var structure = [testStructure]()
func fetch() {
guard let url = URL(string: "test.com")
else { return }
var request = URLRequest(url: url)
request.httpMethod = "POST"
request.httpBody = "id=1".data(using: .utf8)
URLSession.shared.dataTask(with: request) { data, _, error in
guard let data = data else { return }
do {
let nav = try JSONDecoder().decode(structure, from: data)
structure.forEach ..
}
catch {
print(error)
}
}.resume()
}
struct testStructure: Decodable {
let name: String?
let orderid: Int?
let trialid: Int?
}

First of all please start names of classes, structures and other types with a capital letter.
If the API JSON response contains an array you need to decode it to the array type like this:
let nav = try JSONDecoder().decode([TestStructure].self, from: data)
then you can iterate through your decoded array of structures (full code):
import UIKit
struct TestStructure: Decodable {
let name: String?
let orderid: Int?
let trialid: Int?
}
func fetch() {
guard let url = URL(string: "test.com")
else { return }
var request = URLRequest(url: url)
request.httpMethod = "POST"
request.httpBody = "id=1".data(using: .utf8)
URLSession.shared.dataTask(with: request) { data, _, error in
guard let data = data else { return }
do {
let nav = try JSONDecoder().decode([TestStructure].self, from: data)
nav.forEach { testStructure in
// do whatever you want here
if let trialid = testStructure.trialid {
processingTrialid(trialid)
}
}
}
catch {
print(error)
}
}.resume()
}
func processingTrialid(_ trialid: Int) {
print("trialid: ", trialid)
}

Related

Variable Not Passing In POST parameter in Swift

I'm trying to get the cities by country using the POSTMAN Api. When I don't bring any variables into the parameter, the request works as expected. Though, when I try to use a global variable as a parameter, it returns empty. It works perfectly fine if it was coded as such: "country": "Nigeria" (everything else the same)
Code below:
let myCountry = selectedCountryString.lowercased().trimmingCharacters(in: .whitespacesAndNewlines)
guard let url = URL(string: "https://countriesnow.space/api/v0.1/countries/population/cities/filter") else {
return
}
var request = URLRequest(url: url)
request.httpMethod = "POST"
request.setValue("application/json", forHTTPHeaderField: "Content-Type")
let body: [String: Any] = [
"limit": 10,
"order": "dsc",
"orderBy": "value",
"country": "\(myCountry)"
]
request.httpBody = try? JSONSerialization.data(withJSONObject: body, options: .fragmentsAllowed)
let task = URLSession.shared.dataTask(with: request) {data, _, error in
guard let data = data, error == nil else {
return
}
do{
let response = try JSONDecoder().decode(CitiesPopModel.self, from: data)
onCompletion(response)
}
catch {
print("Error country -> \(myCountry)")
}
}
task.resume()
}
I switched my code to this and it is now working with the variable:
func callCitiesByPopAPI(completion: #escaping (CitiesPopModel?, Error?) -> ()) {
let url = "https://countriesnow.space/api/v0.1/countries/population/cities/filter"
let parameters: [String: Any] = [
"limit": 20,
"order": "dsc",
"orderBy": "value",
"country": selectedCountryString
]
AF.request(url, method: .post, parameters: parameters, encoding: JSONEncoding.default).responseDecodable(of: CitiesPopModel.self) { response in
if let error = response.error {
completion(nil, error)
return
}
if let result = response.value {
completion(result, nil)
print("City pop model result is \(result)")
return
}
}
}
Not worry about it you have to pass selectedCountryString value as like it then all goes perfectly
let selectedCountryString = "INDIA"
Replace value forHttpHeaderField for this:
request.setValue("application/json", forHTTPHeaderField: "Accept")
request.setValue("application/x-www-form-urlencoded", forHTTPHeaderField: "Content-Type")
And replace httpBody for this:
request.httpBody = body.percentEncoded()
I tested this with Algeria, it's worked.
extension Dictionary {
func percentEncoded() -> Data? {
map { key, value in
let escapedKey = "\(key)".addingPercentEncoding(withAllowedCharacters: .urlQueryValueAllowed) ?? ""
let escapedValue = "\(value)".addingPercentEncoding(withAllowedCharacters: .urlQueryValueAllowed) ?? ""
return escapedKey + "=" + escapedValue
}
.joined(separator: "&")
.data(using: .utf8)
}
}
extension CharacterSet {
static let urlQueryValueAllowed: CharacterSet = {
let generalDelimitersToEncode = ":#[]#" // does not include "?" or "/" due to RFC 3986 - Section 3.4
let subDelimitersToEncode = "!$&'()*+,;="
var allowed: CharacterSet = .urlQueryAllowed
allowed.remove(charactersIn: "\(generalDelimitersToEncode)\(subDelimitersToEncode)")
return allowed
}()
}
Here is my test code that shows how to fetch the data from the server,
using a variable as a parameter (country) to the POST request, and display it in a view. Works very well for me.
struct ContentView: View {
#State var cityPop = CitiesPopModel()
var body: some View {
List(cityPop.data) { data in
VStack {
Text(data.city).foregroundColor(.blue)
ScrollView(.horizontal) {
HStack {
ForEach(data.populationCounts) { pop in
VStack {
Text(pop.year)
Text(pop.sex)
Text(pop.value).foregroundColor(.blue)
}
}
}
}
}
}
.onAppear {
getCountry(country: "Australia") { result in
if let popData = result {
cityPop = popData
}
}
}
}
func getCountry(country: String, completion: #escaping(CitiesPopModel?) -> Void) {
guard let url = URL(string: "https://countriesnow.space/api/v0.1/countries/population/cities/filter") else {
return
}
var request = URLRequest(url: url)
request.httpMethod = "POST"
request.setValue("application/json", forHTTPHeaderField: "Content-Type")
let body: [String: Any] = [
"limit": 10,
"order": "dsc",
"orderBy": "value",
"country": country
]
request.httpBody = try? JSONSerialization.data(withJSONObject: body, options: .fragmentsAllowed)
URLSession.shared.dataTask(with: request) { data, _, error in
guard let data = data, error == nil else { return }
do {
let response = try JSONDecoder().decode(CitiesPopModel.self, from: data)
return completion(response)
}
catch {
print("Error country -> \(country)")
}
completion(nil)
}.resume()
}
}
// MARK: - CitiesPopModel
struct CitiesPopModel: Codable {
var error: Bool = false
var msg: String = ""
var data: [City] = []
}
// MARK: - City
struct City: Identifiable, Codable {
let id = UUID()
let city: String
let country: String
let populationCounts: [Population]
enum CodingKeys: String, CodingKey {
case city, country, populationCounts
}
}
// MARK: - Population
struct Population: Identifiable, Codable {
let id = UUID()
let year, value, sex, reliabilty: String
enum CodingKeys: String, CodingKey {
case year, value, sex, reliabilty
}
}

Alamofire and TableView/CollectionView

I created a reusable Alamofire request which works smoothly. I am trying to get the data from the request Decode it(works fine too) and append it to one of my arrays to display in tableView/collectionView.
I am using MVVM and I append my data in viewModel(you can see below). The thing is I have tableView in my viewController and inside my tableView methods( viewForSupplementaryElementOfKind for instance) the 'stories'(from viewModel) are always empty.
In my viewDidLoad method I call getMainPageData(from viewModel) first and then create my tableView. I assure you the request itself is a success, the only problem is displaying the data.
Please keep in mind that the project has many API calls so I need a solution which will work in all cases when I have to deal with "lists". Thank you in advance
class NetworkManager {
let keychain = KeychainManager()
let base = "SomeBase"
let storageManager = StorageManager()
func setupRequest(path: Paths, method: RequestMethod, body: Encodable? = nil, params: [String: Any]? = nil, header: HeaderType, completion: #escaping((Result<Data,NetworkError>) -> Void)) {
var queries = ""
if let params = params {
queries = params.passingQuery()
}
let url = URL(string: base + path.rawValue + queries)
var request = URLRequest(url: url!)
request.httpMethod = method.rawValue
request.setValue(header.value[0].value, forHTTPHeaderField: "Content-Type")
if let userToken = keychain.getAccessToken(), userToken.count > 0 {
request.setValue("Bearer " + userToken, forHTTPHeaderField: "Authorization")
}
if let body = body {
if let jsonData = body.toJSONData() {
request.httpBody = jsonData
}
}
AF.request(request).validate().responseJSON { response in
if (200...299) ~= response.response?.statusCode ?? -1 {
self.handlingHeaders(response: response)
completion(.success(response.data!))
} else {
do {
if let data = response.data {
let json = try JSONDecoder().decode(ErrorResponse.self, from: data)
completion(.failure(.responseError(json.message)))
}
} catch {
completion(.failure(.serverError))
}
}
}
}
private func handlingHeaders(response: AFDataResponse<Any>) {
let headers = response.response?.headers
if let accessToken = headers?.dictionary["Authorization"] {
keychain.saveToken(token: accessToken)
}
}
}
extension Encodable {
func toJSONData() -> Data? { try? JSONEncoder().encode(self) }
}
var stories = [Story]()
func getMainPageData(completion: #escaping(Result<Void, NetworkError>) -> ()) {
networkManager.setupRequest(path: .mainPageData, method: .get, body: nil, params: nil, header: .application_json) { [self] result in
switch result {
case .success(let data):
do {
let homePageData = try JSONDecoder().decode(MainPageResponse.self, from: data)
stories.append(contentsOf: homePageData.model.stories)

How can I access a single value within a JSON array | Swift

Essentially I have the following function execute when a tableview controller loads. Using one of the JSON response values I would like to create a conditional - how can I print the pawprint value where myid == 3 :
I have attempted to access it in DispatchQueue.main.async in the same function but I am confused how to refer to it? for example I cannot do myData.pawprint - because it is a JSON array.
private func fetchJSON() {
guard let url = URL(string: "test.com")
else { return }
var request = URLRequest(url: url)
request.httpMethod = "POST"
request.httpBody = "mykey=\(keyValue)".data(using: .utf8)
URLSession.shared.dataTask(with: request) { [self] data, _, error in
guard let data = data else { return }
do {
let decoder = JSONDecoder()
self.structure.sort { $0.thisdate > $1. thisdate }
let res = try decoder.decode([thisStructure].self, from: data)
let grouped = Dictionary(grouping: res, by: { $0. thisdate })
_ = grouped.keys.sorted()
sections = grouped.map { thisSections(date: $0.key, items: $0.value) }
.sorted { $0.date > $1.date }
print(sections.map(\.date))
sections.map.
let myData = try JSONDecoder().decode(thisStructure.self, from: data)
DispatchQueue.main.async {
self.tableView.reloadData()
print("TableView Loaded")
}
}
catch {
print(error)
}
}.resume()
}
struct thisSections {
let date : String
var items : [thisStructure]
}
struct thisStructure: Decodable {
let myid: Int
let pawprint: String
let activationid: Int
let stopid: Int
}
Example of JSON response:
[
{
"myid": 3,
"pawprint": "Print Me",
"activationid": 2,
"stopid": 1
}
]
MyData.forEach {
item in
if item.myid == 3{
prin(\(item.pawprint))
}
}
are you looking for this ?
you could try this approach to select, then print the specific thisStructure
pawprint you want:
private func fetchJSON() {
guard let url = URL(string: "test.com")
else { return }
var request = URLRequest(url: url)
request.httpMethod = "POST"
request.httpBody = "mykey=\(keyValue)".data(using: .utf8)
URLSession.shared.dataTask(with: request) { [self] data, _, error in
guard let data = data else { return }
do {
let decoder = JSONDecoder()
self.structure.sort { $0.thisdate > $1. thisdate }
let res = try decoder.decode([thisStructure].self, from: data)
let grouped = Dictionary(grouping: res, by: { $0. thisdate })
_ = grouped.keys.sorted()
sections = grouped.map { thisSections(date: $0.key, items: $0.value) }
.sorted { $0.date > $1.date }
print(sections.map(\.date))
// --- here res is an array of `thisStructure`
if let selectedOne = res.first(where: {$0.myid == 3}) {
print(selectedOne.pawprint)
}
// do not try to decode this from the data
// let myData = try JSONDecoder().decode(thisStructure.self, from: data)
DispatchQueue.main.async {
self.tableView.reloadData()
print("TableView Loaded")
}
}
catch {
print(error)
}
}.resume()
}

how can I init struct: Codable without default value

I want to get Json from API, and use Codable protocol, but when I init published var, I get an error.
struct Search: Codable {
let result: [String]
}
class SearchViewModel: ObservableObject {
#Published var data = Search()
func loadData(search: String) {
var urlComps = URLComponents(string: getUrl)
let queryItems = [URLQueryItem(name: "result", value: search)]
urlComps!.queryItems = queryItems
let url = urlComps!.url!.absoluteString
guard let Url = URL(string: url) else { return }
URLSession.shared.dataTask(with: Url) { (data, res, err) in
do {
if let data = data {
let decoder = JSONDecoder()
let result = try decoder.decode(Search.self, from: data)
DispatchQueue.main.async {
self.data = result
}
} else {
print("there's no Data😭")
}
} catch (let error) {
print("Error!")
print(error.localizedDescription)
}
}.resume()
}
}
Change
#Published var data:Search?

How to make the right API call?

I am trying to access fixer.io by making an API call. It is the first time than I am trying to do so, but I don't get the result wanted. I would like to get the "rate" and the "result" from this JSON file.
{
"success": true,
"query": {
"from": "GBP",
"to": "JPY",
"amount": 25
},
"info": {
"timestamp": 1519328414,
"rate": 148.972231
},
"historical": ""
"date": "2018-02-22"
"result": 3724.305775
}
The method that I have implemented is this one, but I can not figure out how to retrieve "rate" and "result" when making this API call.
extension APIsRuler {
func getExchangeRate(from: String, to: String, amount: String, callback: #escaping (Bool, ConversionResult?) -> Void) {
var request = URLRequest(url: APIsRuler.exchangeURL)
let body = "convert?access_key=\(APIsRuler.exchangeAPI)&from=\(from)&to=\(to)&amount=\(amount)"
request.httpMethod = "GET"
request.httpBody = body.data(using: .utf8)
let session = URLSession(configuration: .default)
task?.cancel()
task = session.dataTask(with: request) { (data, response, error) in
DispatchQueue.main.async {
guard let data = data, error == nil else {
return callback(false, nil)
}
guard let response = response as? HTTPURLResponse, response.statusCode == 200 else {
return callback(false, nil)
}
guard let responseJSON = try? JSONDecoder().decode([String: Double].self, from: data),
let rate = responseJSON["rate"],
let result = responseJSON["result"] else {
return callback(false, nil)
}
let conversionResult = ConversionResult(exchangeRate: rate, exchangeResult: result)
callback(true, conversionResult)
}
}
task?.resume()
}
}
Use a real model object, like this:
struct Conversion: Codable {
let success: Bool
let query: Query
let info: Info
let historical, date: String
let result: Double
}
struct Info: Codable {
let timestamp: Int
let rate: Double
}
struct Query: Codable {
let from, to: String
let amount: Int
}
and parse your response into it using JSONDecoder:
do {
let conversion = try JSONDecoder().decode(Conversion.self, from: data)
let rate = conversion.info.rate
let result = conversion.result
} catch { print(error) }
You are mixing up two different APIs.
Either use JSONSerialization, the result is a dictionary and you get the values by key and index subscription. And you have to downcast every type and consider the nested rate value.
guard let responseJSON = try? JSONSerialization.jsonObject(with: data) as? [String:Any],
let info = responseJSON["info"] as? [String:Any],
let rate = info["rate"] as? Double,
let result = responseJSON["result"] as? Double else {
return callback(false, nil)
}
let conversionResult = ConversionResult(exchangeRate: rate, exchangeResult: result)
callback(true, conversionResult)
Or use JSONDecoder then you have to create structs, decoding to [String:Double] can work only if all values in the root object are Double which is clearly not the case.
struct Root: Decodable {
let info: Info
let result: Double
}
struct Info: Decodable {
let rate: Double
}
guard let responseJSON = try? JSONDecoder().decode(Root.self, from: data) else {
return callback(false, nil)
}
let conversionResult = ConversionResult(exchangeRate: responseJSON.info.rate, exchangeResult: responseJSON.result)
callback(true, conversionResult)
The code is only an example to keep your syntax. Practically you are strongly discouraged from using try? when decoding JSON. Always catch and handle errors
do {
let responseJSON = try JSONDecoder().decode(Root.self, from: data)
let conversionResult = ConversionResult(exchangeRate: responseJSON.info.rate, exchangeResult: responseJSON.result)
callback(true, conversionResult)
} catch {
print(error)
return callback(false, nil)
}