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.
Related
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)
}
I want to parse JSON data into a struct object but i can't do it.
Actually the code is in different files but here i'm posting it as a whole.
Here is my code :
import Foundation
struct dataResponse: Decodable {
var results: [userData]
init(from decoder: Decoder) throws {
var results = [userData] ()
var container = try decoder.unkeyedContainer()
while !container.isAtEnd {
if let route = try? container.decode(userData.self) {
results.append(route)
}
else {
_ = try? container.decode(dummyData.self)
}
}
self.results = results
}
}
private struct dummyData: Decodable { }
enum dataError: Error {
case dataUnavailable
case cannotProcessData
}
struct userData: Codable {
var avatar: String
var city: String
var contribution: Int
var country: String
var friendOfCount: Int
var handle: String
var lastOnlineTimeSeconds: Int
var maxRank: String
var maxRating: Int
var organization: String
var rank: String
var rating: Int
var registrationTimeSeconds: Int
var titlePhoto: String
}
struct dataRequest {
let requestUrl: URL
init(){
self.requestUrl = URL(string: "https://codeforces.com/api/user.info?handles=abhijeet_ar")!
}
func getData(completionHandler: #escaping(Result<[userData], dataError>) -> Void) {
URLSession.shared.dataTask(with: self.requestUrl) { (data,response, error) in
guard let data = data else {
completionHandler(.failure(.dataUnavailable))
print("-------bye-bye--------")
return
}
do {
print("-------entered--------")
// let dataresponse = try JSONDecoder().decode([userData].self, from: data)
// print(type(of: dataresponse))
// completionHandler(.success(dataresponse))
let jsonResult = try JSONSerialization.jsonObject(with: data, options: JSONSerialization.ReadingOptions.mutableContainers) as AnyObject
print(jsonResult)
completionHandler(.success(jsonResult as! [userData]))
}
catch {
completionHandler(.failure(.cannotProcessData))
}
}.resume()
}
}
here userData is my struct
the error says : Could not cast value of type '__NSDictionaryM' (0x7fff8fe2dab0) to 'NSArray' (0x7fff8fe2dd30).
I would appreciate if anyone helps, thanks.
You are making a very common mistake.
You are ignoring the root object which is a dictionary and causes the error.
struct Root: Decodable {
let status : String
let result: [UserData]
}
struct UserData: Decodable {
let avatar: String
let city: String
let contribution: Int
let country: String
let friendOfCount: Int
let handle: String
let lastOnlineTimeSeconds: Int
let maxRank: String
let maxRating: Int
let organization: String
let rank: String
let rating: Int
let registrationTimeSeconds: Int
let titlePhoto: String
}
Forget JSONSerialization and use only JSONDecoder
And it's not a good idea to return meaningless enumerated errors. Use Error and return the real error.
You get the array with dataresponse.result
struct DataRequest { // name structs always with starting capital letter
let requestUrl: URL
init(){
self.requestUrl = URL(string: "https://codeforces.com/api/user.info?handles=abhijeet_ar")!
}
func getData(completionHandler: #escaping(Result<Root, Error>) -> Void) {
URLSession.shared.dataTask(with: self.requestUrl) { (data,response, error) in
guard let data = data else {
completionHandler(.failure(error!))
print("-------bye-bye--------")
return
}
do {
print("-------entered--------")
let dataresponse = try JSONDecoder().decode(Root.self, from: data)
completionHandler(.success(dataresponse))
}
catch {
completionHandler(.failure(error))
}
}.resume()
}
}
And consider that if status is not "OK" the JSON response could be different.
Your problem is you are using the wrong struct. Create and use a Response struct for decoding. You need to keep your Types to have the first letter in capital to avoid confusion. You could use the JSONDecoder() maybe you are using something that doesn't have the correct format. Try the below code.
struct Response: Codable {
var result: [UserData]
}
enum DataError: Error {
case dataUnavailable, cannotProcessData
}
struct DataRequest {
let requestUrl: URL
init(){
self.requestUrl = URL(string: "https://codeforces.com/api/user.info?handles=abhijeet_ar")!
}
func getData(completionHandler: #escaping(Result<Response, DataError>) -> Void) {
URLSession.shared.dataTask(with: self.requestUrl) { (data,response, error) in
guard let data = data else {
completionHandler(.failure(.dataUnavailable))
return
}
do {
let dataresponse = try JSONDecoder().decode(Response.self, from: data)
completionHandler(.success(dataresponse))
} catch {
completionHandler(.failure(.cannotProcessData))
}
}.resume()
}
}
Note: Parameters in UserData always needs to right. Try with and Empty struct to check if everything else is working then proceed to adding the variable one-by-one.
first, I'm very (very) new to Swift programming. Challenging but so interesting!
Right now, in a Playground, I'm trying to fetch the data from a JSON that I can access using a URL.
I need to store the data somewhere (in this case I need to store an array of BixiStationViewModel so I can later on play with the data I fetch from the URL.
I think the issue is coming from the asynchronous process that is fetching the data and then having my code processing it.
You can see at the end of the code the print(allBixi.allStations) statement: it returns an empty array.
import Foundation
// JSON structure
struct BixiStationDataModel: Codable {
let lastUpdated, ttl: Int?
let data: StationsData?
enum CodingKeys: String, CodingKey {
case lastUpdated = "last_updated"
case ttl, data
}
}
struct StationsData: Codable {
let stations: [StationData]?
}
struct StationData: Codable {
let stationID: String?
let numBikesAvailable, numEbikesAvailable, numBikesDisabled, numDocksAvailable: Int?
let numDocksDisabled, isInstalled, isRenting, isReturning: Int?
let lastReported: Int?
let eightdHasAvailableKeys: Bool?
let eightdActiveStationServices: [EightdActiveStationService]?
enum CodingKeys: String, CodingKey {
case stationID = "station_id"
case numBikesAvailable = "num_bikes_available"
case numEbikesAvailable = "num_ebikes_available"
case numBikesDisabled = "num_bikes_disabled"
case numDocksAvailable = "num_docks_available"
case numDocksDisabled = "num_docks_disabled"
case isInstalled = "is_installed"
case isRenting = "is_renting"
case isReturning = "is_returning"
case lastReported = "last_reported"
case eightdHasAvailableKeys = "eightd_has_available_keys"
case eightdActiveStationServices = "eightd_active_station_services"
}
}
struct EightdActiveStationService: Codable {
let id: String?
}
// Calling the API
class WebserviceBixiStationData {
func loadBixiStationDataModel(url: URL, completion: #escaping ([StationData]?) -> ()) {
URLSession.shared.dataTask(with: url) { data, response, error in
guard let data = data, error == nil else {
completion(nil)
return
}
let response = try? JSONDecoder().decode(BixiStationDataModel.self, from: data)
if let response = response {
DispatchQueue.main.async {
completion(response.data?.stations)
}
}
}.resume()
}
}
// Data Model
class BixiStationViewModel {
let id = UUID()
let station: StationData
init(station: StationData) {
self.station = station
}
var stationID: String {
return self.station.stationID ?? ""
}
var numBikesAvailable: Int {
return self.station.numBikesAvailable ?? 0
}
var numDocksAvailable: Int {
return self.station.numDocksAvailable ?? 0
}
var isInstalled: Int {
return self.station.isInstalled ?? 0
}
var isReturning: Int {
return self.station.isReturning ?? 0
}
}
class BixiStationListModel {
init() { fetchBixiApiDataModel() }
var allStations = [BixiStationViewModel]()
private func fetchBixiApiDataModel() {
guard let url = URL(string: "https://api-core.bixi.com/gbfs/en/station_status.json") else {
fatalError("URL is not correct")
}
WebserviceBixiStationData().loadBixiStationDataModel(url: url) { stations in
if let stations = stations {
self.allStations = stations.map(BixiStationViewModel.init)
}
}
}
}
// Checking if the data has been dowloaded
let allBixi = BixiStationListModel()
print(allBixi.allStations)
How can I fix the code so I could access the values in the var allStations = [BixiStationViewModel]()
Thanks in advance, I've Benn working on it this issue for a while now and this would help me a lot in my app development
In a playground you need continuous execution to work with a url response.
Add PlaygroundPage.current.needsIndefiniteExecution = true to the top of your file (doesn't matter where it's added but I always do the top)
To get your data to print, add a print statement inside your loadBixiStationDataModel callback
WebserviceBixiStationData().loadBixiStationDataModel(url: url) { stations in
if let stations = stations {
self.allStations = stations.map(BixiStationViewModel.init)
print(stations)
}
}
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)
}
when i call API and get response from server with Alamofire, i want use "data" object from json
this data come from API
{
"code": 200,
"hasError": false,
"data": [
{
"userSession": "43a1bd70-26bf-11e9-9ccd-00163eaf6bb4"
}
],
"message": "ok"
}
and i want map data to my AuthModel
this is my AuthModel:
struct AuthModel: Codable {
let userSession: String
enum CodingKeys: String, CodingKey {
case userSession = "userSession"
}
}
i coded this lines but it isn't work:
if let responseObject = response.result.value as? Dictionary<String,Any> {
if let hasError = responseObject["hasError"] as? Bool {
guard !hasError else { return }
do {
let decoder = JSONDecoder()
let authModel = try decoder.decode(AuthModel.self, from: responseObject["data"])
} catch {
print("Parse Error: ",error)
}
}
}
this does not work because responseObject["data"] is not NSData Type
Cannot convert value of type '[String : Any]' to expected argument type 'Data'
I think your API response is a pattern that indicates:
Do we have any problem (error)?
Do we have our expected data?
Based on these, we can use Enum and Generics. For example:
class ResponseObject<T: Codable>: Codable {
private var code : Int
private var hasError : Bool
private var message : String
private var data : T?
var result: Result {
guard !hasError else { return .error(code, message) }
guard let data = data else { return .error(0, "Data is not ready.") }
return .value(data)
}
enum Result {
case error(Int, String)
case value(T)
}
}
and we can use ResponseObject with our expected data:
let responseString = """
{
"code": 200,
"hasError": false,
"data": [
{
"userSession": "43a1bd70-26bf-11e9-9ccd-00163eaf6bb4"
}
],
"message": "ok"
}
"""
class AuthObject: Codable {
var userSession : String
}
if let jsonData = responseString.data(using: .utf8) {
do {
//ResponseObject<[AuthObject]> means: if we don't have error, the `data` object in response, will represent `[AuthObject]`.
let responseObject = try JSONDecoder().decode(ResponseObject<[AuthObject]>.self, from: jsonData)
//Using ResponseObject.Result Enum: We have error with related code and message, OR, we have our expected data.
switch responseObject.result {
case .error(let code, let message):
print("Error: \(code) - \(message)")
case .value(let authObjects):
print(authObjects.first!.userSession)
}
} catch {
print(error.localizedDescription)
}
}
Get the Data response rather than the deserialized Dictionary for example
Alamofire.request(url).responseData { response in
and decode
let decoder = JSONDecoder()
let authModel = try decoder.decode(AuthModel.self, from: response.data!)
into these structs
struct AuthModel : Decodable {
let code : Int
let hasError : Bool
let message : String
let data : [Session]
}
struct Session : Decodable {
let userSession: String
}
All CodingKeys are synthesized.