Generic decode function not working (Swift) - 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 }

Related

How to decode a model correctly in swift [duplicate]

I am new to swift programming and Xcode and am try to call mysql data from the database to Xcode using Json encoding. I was able to successfully call all the data (array) but when I decide to call only one value(column) say Courses.name I get the "Decoding Error -- Expected to decode Dictionary but found an array instead." How do I work my way around this problem? My goal is to print only courses.name
import UIKit
struct Course: Decodable {
let id: String
let name: String
let member: String
}
class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
let jsonUrlString = "http://oriri.ng/aapl/service.php"
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 courses = try JSONDecoder().decode(Course.self, from: data)
print(courses.name)
} catch let jsonErr {
print("Error serializing json:", jsonErr)
}
}.resume()
}
}
[{"id":"1","name":"sobande_ibukun","member":"blue"}]
The [] around denotes that it is an array. Decode with the following and it should work:
let courses = try JSONDecoder().decode([Course].self, from: data)
If you are sure that it will always be one course you can do:
print(courses.first!.name)
If there may be many courses you can print every name:
courses.forEach { course in print(course.name) }
Just wanted to add to Fabians response above.
The fix for showing all of them is to have a struct with each of your keys (in the array) like so:
struct Advisor_Info: Decodable {
let advisor_email: String
let advisor_firstname: String
let advisor_lastname: String
let advisor_phonenumber: String
}
Then extract your items from the JSON dictionary string like so:
do {
let decodedResponse = try JSONDecoder().decode([Advisor_Info].self, from: data)
print(decodedResponse as Any)
for each in decodedResponse {
print(each)
print(each.advisor_email)
}
} catch DecodingError.dataCorrupted(let context) {
print(context)
} catch DecodingError.keyNotFound(let key, let context) {
print("Key '\(key)' not found:", context.debugDescription)
print("codingPath:", context.codingPath)
} catch DecodingError.valueNotFound(let value, let context) {
print("Value '\(value)' not found:", context.debugDescription)
print("codingPath:", context.codingPath)
} catch DecodingError.typeMismatch(let type, let context) {
print("Type '\(type)' mismatch:", context.debugDescription)
print("codingPath:", context.codingPath)
} catch {
print("error: ", error)
}
}

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
}

Access a struct within a struct to return a sorted array

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

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