Trying to show all driver names in a JSON Api with codable protocol in swift 4 - swift4

I am working with an F1 API and want to show all driver names in a swift project. I am using the codable protocol and I understand how I would access one name or property from the JSON API. It is just if I wanted to access all a selected properties from that JSON API. I have tried countless different ways to achieve this but with little success. Below is the code I have.
struct MRData: Codable {
let xmlns: String
let series: String
let url: String
let limit, offset, total: String
let standingsTable: StandingsTable
enum CodingKeys: String, CodingKey {
case xmlns, series, url, limit, offset, total
case standingsTable = "StandingsTable"
}
}
struct StandingsTable: Codable {
let season: String
let standingsLists: [StandingsList]
enum CodingKeys: String, CodingKey {
case season
case standingsLists = "StandingsLists"
}
}
struct StandingsList: Codable {
let season, round: String
let driverStandings: [DriverStanding]
enum CodingKeys: String, CodingKey {
case season, round
case driverStandings = "DriverStandings"
}
}
struct DriverStanding: Codable {
let position, positionText, points, wins: String
let driver: Driver
let constructors: [Constructor]
enum CodingKeys: String, CodingKey {
case position, positionText, points, wins
case driver = "Driver"
case constructors = "Constructors"
}
}
struct Constructor: Codable {
let constructorId: String
let url: String
let name: String
let nationality: String
}
struct Driver: Codable {
let driverId: String
let url: String
let givenName, familyName, dateOfBirth, nationality: String
}
class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
let jsonUrlString = "https://ergast.com/api/f1/1981/driverstandings.json"
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 f1Data = try JSONDecoder().decode(F1Data.self, from: data)
let season = f1Data.mrData.standingsTable.season
print(f1Data.mrData.standingsTable.standingsLists[0].
driverStandings[1].driver.familyName)
print(season)
} catch {
print(error)
}
}.resume()
}
Any help would be appreciated.

Add a for loop.
let firstDriver = f1Data.mrData.standingsTable.standingsLists[0].driverStandings
for driver in firstDriver {
//print(firstDriver)
print(driver.driver.familyName)
}

Related

Fetch API Swift

Previously the API I was working on was as below
{
"kurumsicilno": 457.0,
"yillikizin": 30.0,
}
and I built my model as below
struct Leave: Decodable, Identifiable {
var id: Double? {
return registerNumber
}
let registerNumber: Double
let annualLeave: Double
enum CodingKeys: String, CodingKey {
case registerNumber = "kurumsicilno"
case annualLeave = "yillikizin"
}
}
This was my network function
let task = session.dataTask(with: request) { (data, response, error) in
if error == nil {
let decoder = JSONDecoder()
if let safeData = data {
do {
let result = try decoder.decode(Leave.self, from: safeData)
DispatchQueue.main.async {
completion(result)
}
} catch {
print(error)
}
}
}
}
task.resume()
But for some reason, they have changed the API to this.
{
"isSucceed": true,
"singleData": {
"sicilNo": "457",
"yillikIzin": "30",
},
How should I modify my model so that I can reach and fetch the data as before?
Just create a new root struct
struct Response : Decodable {
let isSucceed: Bool
let singleData: Leave
}
and you have to change the types and (one of) the CodingKeys
struct Leave: Decodable, Identifiable {
var id: String { // no Optional!!
return registerNumber
}
let registerNumber: String
let annualLeave: String
enum CodingKeys: String, CodingKey {
case registerNumber = "sicilNo"
case annualLeave = "yillikIzin" // is this really a capital `I`?
}
}
Finally change the decoding code
let result = try decoder.decode(Response.self, from: safeData)
DispatchQueue.main.async {
completion(result.singleData)
}
And you might have to manage the type change Double → String in your other code.

How I can use [String: Any]? for Decodable object?

For my response api look like this
{
"properties": {
"is_enable_widget": true,
"showing_widget": 10,
"onboard_time": 5,
"application_name": "magic_wondering"
.
.
.
}
}
In field of properties, It have dynamic of number of parameters and dynamic type also.
I declare my object like this
struct ConfigurationsModels: Codable {
let properties: [String: Any]?
enum CodingKeys: String, CodingKey {
case properties
}
}
But I got error Type 'ConfigurationsModels' does not conform to protocol 'Decodable'
I have no idea to cast my json to [String: Any]?. Has anyone done this before ?
You can't use Any with Codable , you should use JSONSerialization for dynamic value types and keys
do {
if let json = try JSONSerialization.jsonObject(with: data, options: []) as? [String: Any] {
}
} catch {
print(error)
}
You need to create a custom model for your properties. Declare you data models something like this:
struct ConfigurationsModels: Codable {
let properties: Properties
}
struct Properties: Codable {
let isEnabledWidget: Bool
let showingWidget: Int
let onboardTime: Int
let applicationName: String
// Add more property here if needed
enum CodingKeys: String, CodingKey {
case isEnabledWidget = "is_enable_widget"
case showingWidget = "showing_widget"
case onboardTime = "onboard_time"
case applicationName = "application_name"
}
}
EDIT:
You can use like that:
let properties = try JSONDecoder().decode(ConfigurationsModels.self, from: data)
Shortest way will be
Your decodable model (use optionals in property type if it can be avoided from backend)
struct ResponseModel: Decodable {
let properties: Properties
struct Properties: Decodable {
let isEnableWidget: Bool
let showingWidget: Int
let onboardTime: Int
let applicationName: String
}
}
Your decoder, using keyDecodingStrategy your be able not to implement enum CodingKeys
let decoder = JSONDecoder()
decoder.keyDecodingStrategy = .convertFromSnakeCase
let model = try? decoder.decode(ResponseModel.self, from: data)decoder.decode(ResponseModel.self, from: data)

Swiift - Decoding json data from server

Im working on a project where I have to display data from a network call. The problem is Im having trouble decoding data the data that I received from the network call and storing it into structs variable to use for other calls. The deadline is coming close and Im not sure why my code does not work. This is json that I receive back
{"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-26T20:21:43Z","isSuccess":true}
This is the different struct that I made in my project
struct ApiResponse: Decodable {
let results: Results?
let isAuthorized: Bool?
let version: Version?
let isSystemDown: Bool?
let errors: [serverError]?
let timestamp: Date?
let isSuccess: Bool?
}
// MARK: - Result
struct Results: Decodable {
let login: Login
let parameters: Parameters?
}
// MARK: - Login
struct Login: Decodable {
let isAuthorized, isEmpty: Bool?
let userName: UserName
let username: String?
}
// MARK: - UserName
struct UserName: Decodable {
let isEmpty: Bool?
let name: Name
let canDelete: Bool?
let id: Int
let canModify: Bool?
}
// MARK: - Name
struct Name: Decodable {
let firstName, lastName, displayName: String
let isEmpty: Bool?
let fullName: String
}
// MARK: - Parameters
struct Parameters: Decodable {
let isEmpty, keepLoggedInIndicator: Bool?
let username: String?
enum CodingKeys: String, CodingKey {
case isEmpty
case keepLoggedInIndicator
case username
}
}
// MARK: - Version
struct Version: Decodable {
let major, minor, fix, display: String?
let isEmpty: Bool?
}
// Mark: - Error
struct serverError: Decodable {
let password: String?
}
The code that I am using to decode the json data is this
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(error))
}
let decoder = JSONDecoder()
// decodes the Server response
guard let response = try? decoder.decode(ApiResponse.self, from: data) else {
print("Something happen here")
completion(.failure(AppError.errorDecoding))
return
}
// Returns if an error occurs
if let error = response.errors {
completion(.failure(AppError.serverError(error)))
return
}
// Decodes the data received from server
if let decodedData = response.results {
completion(.success(decodedData as! T))
} else {
completion(.failure(AppError.errorDecoding))
}
case .failure(let error):
completion(.failure(error))
}
}
If anyone could help me understand why my code isnt working that would be much appreciated.
Your struct is wrong. Try using this?
struct YourAPIData {
struct Root: Codable {
struct Result: Codable {
struct Login: Codable {
let isAuthorized: Bool
let isEmpty: Bool
struct UserName: Codable {
let isEmpty: Bool
struct Name: Codable {
let firstName: String
let lastName: String
let displayName: String
let isEmpty: Bool
let fullName: String
}
let name: Name
let canDelete: Bool
let id: Int
let canModify: Bool
}
let userName: UserName
let username: String
}
let login: Login
struct Parameters: Codable {
let isEmpty: Bool
let keepLoggedInIndicator: Bool
let username: String
private enum CodingKeys: String, CodingKey {
case isEmpty
case keepLoggedInIndicator = "keep_logged_in_indicator"
case username
}
}
let parameters: Parameters
}
let result: Result
let isAuthorized: Bool
struct Version: Codable {
let major: String
let minor: String
let fix: String
let display: Date
let isEmpty: Bool
}
let version: Version
let isSystemDown: Bool
let timestamp: String
let isSuccess: Bool
}
}
and try using
do {
let APIData = try JSONDecoder().decode(YourAPIData.Root.self, from: jsonData)
} catch let jsonErr { print("Error: ", jsonErr) }
And if you would like to display your json (for testing, i suppose?)
if let data = jsonData, let body = String(data: jsonData, encoding: .utf8) {
print(body)
}
} else {
print(error ?? "Unknown error")
}

Cannot map protocol compliant elements to generic elements

As you can see below, I downloaded an array of structures containing heterogeneous objects that were decoded into enums containing nested objects.
I would now like to put said objects into a generic Model structure, but the compiler won't allow this - the error is described below in the code comment. I am relatively new to programming in Swift, I would appreciate your help.
import Foundation
let jsonString = """
{
"data":[
{
"type":"league",
"info":{
"name":"NBA",
"sport":"Basketball",
"website":"https://nba.com/"
}
},
{
"type":"player",
"info":{
"name":"Kawhi Leonard",
"position":"Small Forward",
"picture":"https://i.ibb.co/b5sGk6L/40a233a203be2a30e6d50501a73d3a0a8ccc131fv2-128.jpg"
}
},
{
"type":"team",
"info":{
"name":"Los Angeles Clippers",
"state":"California",
"logo":"https://logos-download.com/wp-content/uploads/2016/04/LA_Clippers_logo_logotype_emblem.png"
}
}
]
}
"""
struct Response: Decodable {
let data: [Datum]
}
struct League: Codable {
let name: String
let sport: String
let website: URL
}
extension League: Displayable {
var text: String { name }
var image: URL { website }
}
struct Player: Codable {
let name: String
let position: String
let picture: URL
}
extension Player: Displayable {
var text: String { name }
var image: URL { picture }
}
struct Team: Codable {
let name: String
let state: String
let logo: URL
}
extension Team: Displayable {
var text: String { name }
var image: URL { logo }
}
enum Datum: Decodable {
case league(League)
case player(Player)
case team(Team)
enum DatumType: String, Decodable {
case league
case player
case team
}
private enum CodingKeys : String, CodingKey { case type, info }
init(from decoder : Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
let type = try container.decode(DatumType.self, forKey: .type)
switch type {
case .league:
let item = try container.decode(League.self, forKey: .info)
self = .league(item)
case .player:
let item = try container.decode(Player.self, forKey: .info)
self = .player(item)
case .team:
let item = try container.decode(Team.self, forKey: .info)
self = .team(item)
}
}
}
protocol Displayable {
var text: String { get }
var image: URL { get }
}
struct Model<T: Displayable> {
let text: String
let image: URL
init(item: T) {
self.text = item.text
self.image = item.image
}
}
do {
let response = try JSONDecoder().decode(Response.self, from: Data(jsonString.utf8))
let items = response.data
let models = items.map { (item) -> Model<Displayable> in // error: only struct/enum/class types can conform to protocols
switch item {
case .league(let league):
return Model(item: league)
case .player(let player):
return Model(item: player)
case .team(let team):
return Model(item: team)
}
}
} catch {
print(error)
}
You do not need generics here.
Change Model to accept any type that conforms to Displayable in the init
struct Model {
let text: String
let image: URL
init(item: Displayable) {
self.text = item.text
self.image = item.image
}
}
and then change the closure to return Model
let models = items.map { (item) -> Model in
If you want to keep your Model struct generic then you need to change the map call to
let models: [Any] = items.map { item -> Any in
switch item {
case .league(let league):
return Model(item: league)
case .player(let player):
return Model(item: player)
case .team(let team):
return Model(item: team)
}
}
This will give the following output when conforming to CustomStringConvertible
extension Model: CustomStringConvertible {
var description: String {
"\(text) type:\(type(of: self))"
}
}
print(models)
[NBA type:Model<League>, Kawhi Leonard type:Model<Player>, Los Angeles Clippers type:Model<Team>]
If you are only interested in name and the key representing the URL parse the JSON directly into Model by using nestedContainer this way
struct Response: Decodable {
let data: [Model]
}
struct Model: Decodable {
let name: String
let image: URL
enum DatumType: String, Decodable {
case league
case player
case team
}
private enum CodingKeys : String, CodingKey { case type, info }
private enum CodingSubKeys : String, CodingKey { case name, website, picture, logo }
init(from decoder : Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
let type = try container.decode(DatumType.self, forKey: .type)
let subContainer = try container.nestedContainer(keyedBy: CodingSubKeys.self, forKey: .info)
self.name = try subContainer.decode(String.self, forKey: .name)
let urlKey : CodingSubKeys
switch type {
case .league: urlKey = .website
case .player: urlKey = .picture
case .team: urlKey = .logo
}
self.image = try subContainer.decode(URL.self, forKey: urlKey)
}
}
do {
let response = try JSONDecoder().decode(Response.self, from: Data(jsonString.utf8))
let data = response.data
print(data)
} catch {
print(error)
}

Swift Codable - decode nested dictionary

Lets say I have a dictionary like this:
{"user_data":{"id":3,"name":"Damian D","email":"aaa#aaa.pl"},"status":true}
How can I use Codable protocol to decode just user_data into such struct:
struct User: Codable {
private enum CodingKeys: String, CodingKey {
case id
case username = "name"
case email
}
let id: Int
let username: String
let email: String
}
Do I need to convert this sub dictionary into Data, or is there a easier way?
If you create nested Coding Keys you will accomplish decoding the response using only one data model.
Given the following JSON response:
let data = """
{
"user_data": {
"id":3,
"name":"Damian D",
"email":"aaa#aaa.pl"
},
"status":true
}
""".data(using: .utf8, allowLossyConversion: false)!
and the following data model:
public struct User: Decodable {
var id: Int
var name: String
var email: String
// MARK: - Codable
private enum RootCodingKeys: String, CodingKey {
case userData = "user_data"
enum NestedCodingKeys: String, CodingKey {
case id
case name
case email
}
}
required public init(from decoder: Decoder) throws {
let rootContainer = try decoder.container(keyedBy: RootCodingKeys.self)
let userDataContainer = try rootContainer.nestedContainer(keyedBy: RootCodingKeys.NestedCodingKeys.self, forKey: .userData)
self.id = try userDataContainer.decode(Int.self, forKey: .id)
self.name = try userDataContainer.decode(String.self, forKey: .name)
self.email = try userDataContainer.decode(String.self, forKey: .email)
}
}
You can decode your response into a single object:
let decoder = JSONDecoder()
let user = try? decoder.decode(User.self, from: data)
Create a new struct that has a userData member of type User.
struct Response: Codable {
private enum CodingKeys: String, CodingKey {
case userData = "user_data"
case status
}
let userData: User
let status: Bool
}