Access a struct within a struct to return a sorted array - swift

I am parsing data to my Users object from a JSON url and attempting to return the object sorted alphabetically by names. I am having trouble accessing the names property inside of my Users object. I have nested my struct inside of a struct because of the way my JSON is structured. I would like to return the array sorted alphabetically by names in my sortedList array.
struct Response: Codable
{
struct Users: Codable {
var fullName :String
var biography:String
enum CodingKeys: String, CodingKey {
case fullName = "full_name"
case biography
}
}
var users:[Users]
}
// let sortedList = Response{ $0.fullName < $1.fullName }
//Cannot invoke initializer for type 'Response' with an argument list of type '((_, _) -> _)'
func parse(){
guard let url = URL(string: "samplePage.json") else {return}
let task = URLSession.shared.dataTask(with: url) { (data, response, error) in
guard let dataResponse = data,
error == nil else {
print(error?.localizedDescription ?? "Error")
return }
do{
let response = try! JSONDecoder().decode(Response.self, from: dataResponse)
for user in response.users{
print(user.fullName)
print(user.biography)
let sortedArr = user.fullName.sorted{ $0.fullName < $1.fullName }
//Value of type 'Character' has no member 'fullName'
}
} catch let parsingError {
print("Error", parsingError)
}
}
task.resume()
}

For sorting purpose code use like this :
let sortedArr = response.users.sorted{ $0.fullName < $1.fullName }
no need for for loop.
updated code:
func parse(){
guard let url = URL(string: "samplePage.json") else {return}
let task = URLSession.shared.dataTask(with: url) { (data, response, error) in
guard let dataResponse = data,
error == nil else {
print(error?.localizedDescription ?? "Error")
return }
do{
let response = try! JSONDecoder().decode(Response.self, from: dataResponse)
for user in response.users{
print(user.fullName)
print(user.biography)
}
// use code like this
let sortedArr = response.users.sorted{ $0.fullName < $1.fullName }
} catch let parsingError {
print("Error", parsingError)
}
}
task.resume()
}

Related

How to use a value from a struct dictionary and convert it to another type?

Here I want to be able to use the value returned from an array. It returns as a type from a struct. I'm unsure of how to use the value as an integer.
struct Item: Codable {
let data: [String : Datum]
}
struct Datum: Codable {
let value: Int
}
var array = Item(data: ["1" : Datum(value: 1),"2": Datum(value: 2), "3":Datum(value: 3)])
var keyArray = ["1", "2", "3"]
print(array.data[keyArray[0]]!)
// Prints Datum(value: 1)
print(array.data[keyArray[0]]! + 1)
//This produces an error "Cannot convert value of type 'Datum' to expected argument type 'Int'"
//Expected result should be 2
My use case is when I get returned a decoded JSON it normally comes back as a dictionary. I'm wanting to use the values returned with a key but I feel like I'm one step short.
Context
Full JSON Link
I'm going to retrieve values from this JSON. (Example from large JSON file)
{"data":{"2":{"high":179,"highTime":1628182107,"low":177,"lowTime":1628182102},"6":{"high":189987,"highTime":1628179815,"low":184107,"lowTime":1628182100},"8":{"high":190800,"highTime":1628181435,"low":188100,"lowTime":1628182095}
}}
The string in front refers to an item ID.
The struct that I came up to decode goes like this.
// MARK: - Single
struct Single: Codable {
let data: [String: Datum]
}
// MARK: - Datum
struct Datum: Codable {
let high, highTime: Int
let low, lowTime: Int?
}
From there I'm planning to iterate through the JSON response to retrieve the item prices I'd want.
#available(iOS 15.0, *)
struct ContentView: View {
#State var dataFromURL: Single = Single(data: [:])
var body: some View {
VStack {
Text("Hello, world!")
.padding()
}
.onAppear {
async {
try await decode()
}
}
}
func decode() async throws -> Single {
let decoder = JSONDecoder()
let urlString = "https://prices.runescape.wiki/api/v1/osrs/latest"
guard let url = URL(string: urlString) else { throw APIError.invalidURL }
let (data, response) = try await URLSession.shared.data(from: url)
guard let response = response as? HTTPURLResponse, response.statusCode == 200 else { throw APIError.invalidServerResponse }
guard let result = try? decoder.decode(Single.self, from: data) else { throw APIError.invalidData }
//We copy our result to an existing variable
dataFromURL = result
return result
}
}
enum APIError: Error {
case invalidURL
case invalidServerResponse
case invalidData
}
extension APIError: CustomStringConvertible {
public var description: String {
switch self {
case.invalidURL:
return "Bad URL"
case .invalidServerResponse:
return "The server did not return 200"
case .invalidData:
return "Their server returned bad data"
}
}
}
I haven't gotten further than grabbing the response from the URL. That is why once I start manipulating the data I'd like to use the response to find other things like what would a profit/loss with another item become. Which isn't the goal of this question at the moment.
The object model to parse that JSON would be:
struct Price: Decodable {
let high: Int?
let highTime: Date?
let low: Int?
let lowTime: Date?
}
struct ResponseObject: Decodable {
let prices: [String: Price]
enum CodingKeys: String, CodingKey {
case prices = "data"
}
}
(Note, the documentation says that either high or low might be missing, so we have to make them all optionals.)
Now, the id number is being passed as a string in the JSON/ResponseObject. But that is a number (look at mapping). So, I would remap that dictionary so that the key was an integer, e.g.
enum ApiError: Error {
case unknownError(Data?, URLResponse?)
}
func fetchLatestPrices(completion: #escaping (Result<[Int: Price], Error>) -> Void) {
let url = URL(string: "https://prices.runescape.wiki/api/v1/osrs/latest")!
let task = URLSession.shared.dataTask(with: url) { data, response, error in
guard
error == nil,
let responseData = data,
let httpResponse = response as? HTTPURLResponse,
200..<300 ~= httpResponse.statusCode
else {
completion(.failure(error ?? ApiError.unknownError(data, response)))
return
}
let decoder = JSONDecoder()
decoder.dateDecodingStrategy = .secondsSince1970
do {
let responseObject = try decoder.decode(ResponseObject.self, from: responseData)
let keysAndValues = responseObject.prices.map { (Int($0.key)!, $0.value) }
let prices = Dictionary(uniqueKeysWithValues: keysAndValues)
completion(.success(prices))
} catch {
completion(.failure(error))
}
}
task.resume()
}
The code that converts that [String: Price] to a [Int: Price] is this:
let keysAndValues = responseObject.prices.map { (Int($0.key)!, $0.value) }
let prices = Dictionary(uniqueKeysWithValues: keysAndValues)
I must say that this is a questionable API design, to have keys returned as integers in one endpoint and as strings as another. But it is what it is. So, the above is how you handle that.
Anyway, now that you have a dictionary of prices, keyed by the id numbers, you can use that in your code, e.g.
var prices: [Int: Price] = [:]
var products: [Product] = []
let group = DispatchGroup()
group.enter()
fetchLatestPrices { result in
defer { group.leave() }
switch result {
case .failure(let error):
print(error)
case .success(let values):
prices = values
}
}
group.enter()
fetchProducts { result in
defer { group.leave() }
switch result {
case .failure(let error):
print(error)
case .success(let values):
products = values }
}
group.notify(queue: .main) {
for product in products {
print(product.name, prices[product.id] ?? "no price found")
}
}
Where
func fetchProducts(completion: #escaping (Result<[Product], Error>) -> Void) {
let url = URL(string: "https://prices.runescape.wiki/api/v1/osrs/mapping")!
let task = URLSession.shared.dataTask(with: url) { data, response, error in
guard
error == nil,
let responseData = data,
let httpResponse = response as? HTTPURLResponse,
200..<300 ~= httpResponse.statusCode
else {
completion(.failure(error ?? ApiError.unknownError(data, response)))
return
}
do {
let products = try JSONDecoder().decode([Product].self, from: responseData)
completion(.success(products))
} catch {
completion(.failure(error))
}
}
task.resume()
}
And
struct Product: Decodable {
let id: Int
let name: String
let examine: String
let members: Bool
let lowalch: Int?
let limit: Int?
let value: Int
let highalch: Int?
let icon: String
}
(As an aside, I do not know if some of these other properties should be optionals or not. I just used optionals where I empirically discovered that they are occasionally missing.)

Cannot Assign the variables of a function to the Labels on Swift

I am working on an app that Decode a JSON file and creates three variables out of the function: Status,emptySlots,freeBikes. I want to assign these values to labels. However, no matter what I do, I was unable to get any output with any method.
The function code:
func getBikeData(stationName:String){
if let url = URL(string: "https://api.citybik.es//v2/networks/baksi-bisim"){
let task = URLSession.shared.dataTask(with: url) { (data, response, error) in
guard let dataResponse = data, error == nil else {
print(error?.localizedDescription ?? "Response Error")
return
}
do {
//here dataResponse received from a network request
let jsonResponse = try JSONSerialization.jsonObject(with:
dataResponse, options: [])
print(jsonResponse) //Response result
do {
//here dataResponse received from a network request
let decoder = JSONDecoder()
//Decode JSON Response Data
let model = try decoder.decode(ResponseJSON.self, from: dataResponse)
print(model)//Output - 1221
if let station = model.network.stations.first(where: { $0.name == stationName }) {
//get the properties of station here as per requirement
let emptySlots: Int = station.empty_slots
let freeBikes: Int = station.free_bikes
let Status: String = station.extra.status
print(emptySlots, freeBikes, Status)
}
}
catch let parsingError {
print("Error", parsingError)
}
} catch let parsingError {
print("Error", parsingError)
}
}
task.resume()
}
}
Any help is gladly appreciated. I have already tried to return the variables, and using completion block.
ResponseJSON Struct:
struct ResponseJSON: Codable {
let network: Stations
}
One way to solve this is to use a closure. To simplify things create a struct that holds the values
struct BikeResponse {
let status: String
let freeBikes: Int
let emptySlots: Int
}
And then modify your function declaration to
func getBikeData(stationName:String, completion: (BikeResponse)->(Void)){
and then after the decoding you call the completion handler
if let station = model.network.stations.first(where: { $0.name == stationName }) {
let response = BikeResponse(status: station.extra.status,
freeBikes: station.free_bikes,
emptySlots: station.empty_slots)
completion(response)
And then in your completion code you can assign the values to your labels
getBikeData(stationName: "ABC") { response in
DispatchQueue.main.async {
someLabel.text = response.status
//...
}
}
Simplest solution:
if let station = model.network.stations.first(where: { $0.name == stationName }) {
DispatchQueue.main.async {
self.emptySlotsLabel.text = String(station.empty_slots)
self.freeBikesLabel.text = String(station.free_bikes)
self.statusLabel.text = station.extra.status
}
}
emptySlotsLabel, freeBikesLabel and statusLabel are the labels, change the names to the real names
you need to add completion handler to you function. Because you are trying to make async query.
After calling getbikedata func you can assign value to your labels.
Sample code looks like this:
func getBikeData(stationName:String, completion: #escaping (Station) -> Void) {
// ... your code here
if let station = model.network?.stations?.first(where: { $0.name == stationName }) {
//get the properties of station here as per requirement
// let emptySlots: Int = station.emptySlots!
// let freeBikes: Int = station.freeBikes!
// let Status: String = (station.extra?.status)!.rawValue
completion(station)
}
// ... other your code here
}
And usage:
getBikeData(stationName: stationName) { (station) in
print(station)
// For example
label.text = station.emptySlots
}

Generic decode function not working (Swift)

I'm trying to create a generic decode function to decode my two different models. I get the error "Argument type 'PrivateSchoolType.Type' does not conform to expected type 'Decodable'".
Model
struct PrivateSchoolModel: Decodable {
var name: String
var id: String
var city: String
var state: String
}
Calling Function
function getInfo() {
// does not work => ERROR
guard let schools = decode(jsonData: jsonData, using: PrivateSchoolModel) else { return }
// does work
guard let schools = specificDecode()
}
Specific Decode Function (DOES WORK)
private func specificDecode() -> [PrivateSchoolModel]? {
guard let jsonData = getJSONData(from: .privateSchool) else { return }
do {
let decoder = JSONDecoder()
let schools = try decoder.decode([PrivateSchoolModel].self, from:
jsonData)
return schools
} catch let error {
print("Error: \(error.localizedDescription)")
}
return nil
}
Generic Decode Function (DOES NOT WORK)
private func decode<M: Decodable>(jsonData: Data, using model: M) -> [M]? {
do {
//here dataResponse received from a network request
let decoder = JSONDecoder()
let schools = try decoder.decode([M].self, from:
jsonData) //Decode JSON Response Data
return schools
} catch let parsingError {
print("Error", parsingError)
}
return nil
}
Change the method signature as below,
private func decode<M: Decodable>(jsonData: Data, using modelType: M.Type) -> M? {
do {
//here dataResponse received from a network request
let decoder = JSONDecoder()
let schools = try decoder.decode(modelType, from: jsonData) //Decode JSON Response Data
return schools
} catch let parsingError {
print("Error", parsingError)
}
return nil
}
Usage
guard let schools = decode(jsonData: jsonData, using: [PublicSchoolModel].self) else { return }

How to get specific properties from a JSON object from HTTP request using URLSession

With the following code I'm able to effectively get a JSON object, what I'm not sure how to do is retrieve the specific properties from the object.
Swift Code
#IBAction func testing(_ sender: Any) {
let url = URL(string: "http://example.com/cars/mustang")
let task = URLSession.shared.dataTask(with: url!) { data, response, error in
guard error == nil else {
print(error!)
return
}
guard let data = data else {
print("Data is empty")
return
}
let json = try! JSONSerialization.jsonObject(with: data, options: [])
print(json)
}
task.resume()
}
Here is what I see when I run the above code...
Output - JSON Object
(
{
color = "red";
engine = "5.0";
}
)
How can I get just the property color?
Thanks
Create a class which confirm the decodable protocol; CarInfo for example in your case
class CarInfo: Decodable
Create attributes of the class
var color: String
var engine: String
Create JSON key enum which inherits from CodingKey
enum CarInfoCodingKey: String, CodingKey {
case color
case engine
}
implement init
required init(from decoder: Decoder) throws
the class will be
class CarInfo: Decodable {
var color: String
var engine: String
enum CarInfoCodingKey: String, CodingKey {
case color
case engine
}
public init(from decoder: Decodabler) throws {
let container = try decoder.container(keyedBy: CarInfoCodingKey.self)
self.color = try container.decode(String.self, forKey: .color)
self.engine = try contaire.decode(String.self, forKey: .engine)
}
}
call decoder
let carinfo = try JsonDecoder().decode(CarInfo.self, from: jsonData)
Here is how I did it...
#IBAction func testing(_ sender: Any) {
let url = URL(string: "http://example.com/cars/mustang")
let task = URLSession.shared.dataTask(with: url!) { data, response, error in
guard error == nil else {
print(error!)
return
}
guard let data = data else {
print("Data is empty")
return
}
let json = try! JSONSerialization.jsonObject(with: data, options: [])
guard let jsonArray = json as? [[String: String]] else {
return
}
let mustangColor = jsonArray[0]["color"]
print(mustangColor!)
}
task.resume()
}

Using decoded data from an API into an algorithm

I successfully fetched and decoded data from an API and now have access to all the data I need to be used in the algorithm I want to write in my App.
The issue is that I don't know how to access this data after I decoded it, I can print it immediately after it's decoded but I have no idea how to use it in another function or place in my app.
Here is my Playground:
import PlaygroundSupport
PlaygroundPage.current.needsIndefiniteExecution = true
enum MyError : Error {
case FoundNil(String)
}
struct Level: Codable {
let time: Double
let close: Double
let high: Double
let low: Double
let open: Double
}
struct Response: Codable {
let data: [Level]
private enum CodingKeys : String, CodingKey {
case data = "Data"
}
}
func fetchData(completion: #escaping (Response?, Error?) -> Void) {
let url = URL(string: "https://min-api.cryptocompare.com/data/histominute?fsym=BTC&tsym=USD&limit=60&aggregate=3&e=CCCAGG")!
let task = URLSession.shared.dataTask(with: url) { (data, response, error) in
guard let data = data else { return }
do {
if let marketData = try? JSONDecoder().decode(Response.self, from: data) {
print(marketData.data[0].open)
print(marketData.data[1].open)
print("Average=", (marketData.data[0].open + marketData.data[1].open) / 2)
//completion(marketData, nil)
throw MyError.FoundNil("data")
}
} catch {
print(error)
}
}
task.resume()
}
fetchData() { items, error in
guard let items = items,
error == nil else {
print(error ?? "Unknown error")
return
}
print(items)
}
How can I use .data[0], .data[1], ..., somewhere else?
You data will be available in your fecthData() call. Probably what you want is your items variable, where you're printing it. But make sure to call the completion in your fetchData implementation.
WARNING: Untested code.
import PlaygroundSupport
PlaygroundPage.current.needsIndefiniteExecution = true
enum MyError: Error {
case FoundNil(String)
case DecodingData(Data)
}
struct Level: Codable {
let time: Double
let close: Double
let high: Double
let low: Double
let open: Double
}
struct Response: Codable {
let data: [Level]
private enum CodingKeys : String, CodingKey {
case data = "Data"
}
}
func fetchData(completion: #escaping (Response?, Error?) -> Void) {
let url = URL(string: "https://min-api.cryptocompare.com/data/histominute?fsym=BTC&tsym=USD&limit=60&aggregate=3&e=CCCAGG")!
let task = URLSession.shared.dataTask(with: url) { (data, response, error) in
guard let data = data else {
completion(nil, MyError.FoundNil("data"))
}
do {
if let marketData = try? JSONDecoder().decode(Response.self, from: data) {
completion(marketData, nil)
} else {
completion(nil, MyError.DecodingData(data)) // work on this duplicated call
}
} catch {
completion(nil, MyError.DecodingData(data)) // work on this duplicated call
}
}
task.resume()
}
fetchData() { items, error in
if let error == error {
switch(error) {
case .foundNil(let whatsNil):
print("Something is nil: \(whatsNil)")
case .decodingData(let data):
print("Error decoding: \(data)")
}
} else {
if let items = items {
print(items.data[0].open)
print(items.data[1].open)
print("Average=", (items.data[0].open + items.data[1].open) / 2)
print(items)
} else {
print("No items to show!")
}
}
}
I don't understand what is your real issue, because you have written everything you need here, but as far I understand , to pass data
just uncomment this line completion(marketData, nil)
and in
fetchData() { items, error in
guard let items = items,
error == nil else {
print(error ?? "Unknown error")
return
}
print(items)
}
items is an object of your struct Response. You can pass this anywhere in your other class , by just creating an another variable like:
var items : Response!
for example :
class SomeOtherClass : NSObject{
var items : Response!
func printSomeData()
{
print(items.data[0].open)
print(items.data[1].open)
print("Average=", (items.data[0].open + items.data[1].open) / 2)
}
}
and in fetchData method write this:
fetchData() { items, error in
guard let items = items,
error == nil else {
print(error ?? "Unknown error")
return
}
let otherObject = SomeOtherClass()
otherObject.items = items
otherObject.printSomeData()
}