Find better way to handle pagination JSON with MVVM in Swift 5 - swift

currently my project uses structure MVVM. I have a Pagination JSON like this:
{
"has_next": false,
"next_params": {
"limit": 10,
"offset": 10
},
"results": [
{ "id": 1, "name": "A Place" },
{ "id": 2, "name": "A Night" }
]
}
This is my ViewModel:
class LifeStoryViewModel: ObservableObject {
#Published var lifes: [Life] = []
var has_next: Bool = true
var next_params: [String:Any] = [:]
var fetching: Bool = false
func fetchLifeStories () {
let url = URL(string: STRINGURL)
URLSession.shared.dataTask(with: url!) { (data, res, err) in
DispatchQueue.main.async {
let vvv = try! JSONDecoder().decode(LifeStories.self, from: data!)
self.lifes = vvv.results
}
}.resume()
}
}
As you guys see, I have a model LifeStories:
struct Life: Identifiable, Codable {
var id: Int
var name: String
var description: String
var thumbnail: String
}
struct LifeStories: Codable {
var has_next: Bool
var results: [Life]
}
Can I remove LifeStories model and handle it inside the LifeStoryViewModel instead? How can I do this because I think LifeStories model is not necessary.

You can use a generic type for paginated responses. Here's an example from the app I'm working on:
struct PaginatedResponse<Element: Codable>: Codable {
var count: Int
var next: String?
var previous: String?
var results: [Element]
}

Related

I am working with an API and running into issues when trying to decode

I am trying to decode from an API but I am getting this error:
keyNotFound(CodingKeys(stringValue: "search", intValue: nil), Swift.DecodingError.Context(codingPath: [], debugDescription: "No value associated with key CodingKeys(stringValue: "search", intValue: nil) ("search").", underlyingError: nil))
I was able to get the data from the original games struct but I wasn't able to use it the way I wanted, it was limiting some of my access. So I tried to create a new struct so I can access everything but now I am unable to decode it here is all my code any help is appreciated.
struct Games: Decodable {
var status: Int?
var time: String?
var games: Int?
var skip: Int?
var results: [GameResult]?
}
struct GameResult: Decodable {
var schedule: GameSchedule?
var summary: String?
var details: GameDetails?
var status: String?
var teams: TeamInfo?
var lastUpdated: String?
var gameId: Int?
var venue: GameVenue?
var odds: [GameOdds]?
}
struct GameSchedule: Decodable {
var date: String?
var tbaTime: Bool?
}
struct GameDetails: Decodable {
var league: String?
var seasonType: String?
var season: Int?
var conferenceGame: Bool?
var divisionGame: Bool?
}
struct GameVenue: Decodable {
var name: String?
var city: String?
var state: String?
var neutralSite: Bool?
}
struct GameOdds: Decodable {
var spread: GameSpread?
var moneyLine: GameMoneyLine?
var total: GameTotal?
var openDate: String?
var lastUpdated: String?
}
struct GameSpread: Decodable {
var open: OpenSpread?
var current: CurrentSpread?
}
struct OpenSpread: Decodable {
var away: Double?
var home: Double?
var awayOdds: Int?
var homeOdds: Int?
}
struct CurrentSpread: Decodable {
var away: Double?
var home: Double?
var awayOdds: Int?
var homeOdds: Int?
}
struct GameMoneyLine: Decodable {
var open: OpenMoney?
var current: CurrentMoney?
}
struct OpenMoney: Decodable {
var awayOdds: Int?
var homeOdds: Int?
}
struct CurrentMoney: Decodable {
var awayOdds: Int?
var homeOdds: Int?
}
struct GameTotal: Decodable {
var open: OpenTotal?
var current: CurrentTotal?
}
struct OpenTotal: Decodable {
var total: Double?
var overOdds: Int?
var underOdds: Int?
}
struct CurrentTotal: Decodable {
var total: Double?
var overOdds: Int?
var underOdds: Int?
}
struct TeamInfo: Decodable {
var away: AwayTeams?
var home: HomeTeams?
}
struct AwayTeams: Decodable {
var team: String?
var location: String?
var mascot: String?
var abbreviation: String?
var conference: String?
var division: String?
}
struct HomeTeams: Decodable {
var team: String?
var location: String?
var mascot: String?
var abbreviation: String?
var conference: String?
var division: String?
}
Above is the original structs but this wouldn't allow me to access some of the values when I tried to assign results to use for UI so I tried to create this here:
class GameSearch: Decodable {
var search = [Games]()
var result = [GameResult]()
var schedule = [GameSchedule]()
var details = [GameDetails]()
var venue = [GameVenue]()
var odds = [GameOdds]()
var gameSpread = [GameSpread]()
var openSpread = [OpenSpread]()
var currentSpread = [CurrentSpread]()
var moneyLine = [GameMoneyLine]()
var openMoney = [OpenMoney]()
var currentMoney = [CurrentMoney]()
var gameTotal = [GameTotal]()
var openTotal = [OpenTotal]()
var currentTotal = [CurrentTotal]()
var teamInfo = [TeamInfo]()
var away = [AwayTeams]()
var home = [HomeTeams]()
}
Now I am unable to decode the data.
Here is my decoding process:
func getGames() {
let headers = [
"x-rapidapi-host": "sportspage-feeds.p.rapidapi.com",
"x-rapidapi-key": "MY API KEY"
]
let urlString = "https://sportspage-feeds.p.rapidapi.com/games"
let url = URL(string: urlString)
if url != nil {
var request = URLRequest(url: url!, cachePolicy: .reloadIgnoringLocalCacheData, timeoutInterval: 10.0)
request.httpMethod = "GET"
request.allHTTPHeaderFields = headers
let session = URLSession.shared
let dataTask = session.dataTask(with: request) { data, response, error in
if error == nil {
do {
let decoder = JSONDecoder()
let result = try decoder.decode(GameSearch.self, from: data!)
DispatchQueue.main.async {
print(result)
}
}
catch {
print(error)
}
}
}
dataTask.resume()
}
}
}
Here is the JSON that I am getting:
{
"status": 200,
"time": "2022-02-08T20:09:45.316Z",
"games": 60,
"skip": 0,
"results": [
{
"schedule": {
"date": "2022-02-09T00:00:00.000Z",
"tbaTime": false
},
"summary": "Phoenix Suns # Philadelphia 76ers",
"details": {
"league": "NBA",
"seasonType": "regular",
"season": 2021,
"conferenceGame": false,
"divisionGame": false
},
"status": "scheduled",
"teams": {
"away": {
"team": "Phoenix Suns",
"location": "Phoenix",
"mascot": "Suns",
"abbreviation": "PHX",
"conference": "Western",
"division": "Pacific"
},
"home": {
"team": "Philadelphia 76ers",
"location": "Philadelphia",
"mascot": "76ers",
"abbreviation": "PHI",
"conference": "Eastern",
"division": "Atlantic"
}
},
"lastUpdated": "2022-02-08T20:09:12.610Z",
"gameId": 268285,
"venue": {
"name": "Wells Fargo Center",
"city": "Philadelphia",
"state": "PA",
"neutralSite": false
},
"odds": [
{
"spread": {
"open": {
"away": 1,
"home": -1,
"awayOdds": -110,
"homeOdds": -110
},
"current": {
"away": 1,
"home": -1,
"awayOdds": -110,
"homeOdds": -115
}
},
"moneyline": {
"open": {
"awayOdds": -107,
"homeOdds": -113
},
"current": {
"awayOdds": -101,
"homeOdds": -119
}
},
"total": {
"open": {
"total": 218,
"overOdds": -110,
"underOdds": -110
},
"current": {
"total": 217.5,
"overOdds": -110,
"underOdds": -110
}
},
"openDate": "2022-02-08T12:04:00.174Z",
"lastUpdated": "2022-02-08T20:02:42.787Z"
}
]
},
Any help is appreciated thank you all!

Issue decoding JSON | Swift

I am having an issue dynamically populating the date associated with each section.
The value of grouped is currently derived from grouping the raw JSON data by routeid
You can see that I am currently using a static value for date as a test, while $0.key represent the value of myID dynamically.
How can also add the dynamic value of myDate intead of "test"?
Variables:
var sections = [mySections]()
var structure = [myStructure]()
Currently this is true
sections.map(\.id) == [8,4]
At the end this must be true as well
sections.map(\.date) == [2021-01-20, 2021-01-18]
Decoding:
do {
let decoder = JSONDecoder()
let res = try decoder.decode([myStructure].self, from: data)
let grouped = Dictionary(grouping: res, by: { $0.myID })
_ = grouped.keys.sorted()
sections = grouped.map { mySections(date: "test", id: $0.key, items: $0.value) }
.sorted { $0.id > $1.id }
}
Struct:
struct mySections {
let date : String
let id : Int
var items : [myStructure]
}
struct myStructure: Decodable {
let name: String
let type: String
let myID: Int
let myDate: String
}
Example JSON:
[
{
"name": "Jeff",
"type": "large",
"myID": 8,
"myDate": "2021-01-20"
},
{
"name": "Jessica",
"type": "small",
"myID": 4,
"myDate": "2021-01-18"
},
{
"name": "Beth",
"type": "medium",
"myID": 4,
"myDate": "2021-01-18"
}
]
So, you want it grouped by both id number and date string? I would create a Hashable structure for those two values.
struct Section {
let date: String
let id: Int
var items: [Item]
}
extension Section {
struct Header: Hashable {
let date: String
let id: Int
}
}
struct Item: Decodable {
let name: String
let type: String
let myID: Int
let myDate: String
}
And
let decoder = JSONDecoder()
let res = try decoder.decode([Item].self, from: data)
let sections = Dictionary(grouping: res) { Section.Header(date: $0.myDate, id: $0.myID) }
.sorted { $0.key.id > $1.key.id }
.map { Section(date: $0.key.date, id: $0.key.id, items: $0.value) }

Firebase The data couldn’t be read because it isn’t in the correct format

I imported json manually (json file). I get this error when I decode the json.
The data couldn’t be read because it isn’t in the correct format.
where am I doing wrong? Is my model wrong? Is my reference wrong?
JSON:
{
"allQuiz": [
{
"title":"Ağustos Test 1",
"test": [
{
"id": 1,
"question":"Şekle göre aşağıdakiler hangisi doğrudur",
"isQuestionImage": true,
"isSectionImage": false,
"imageName":"1.png",
"sections": {
"A":"2 numaralı aracın öncelikle geçmesi",
"B":"1 numaralı aracın hızını arttırarak kavşağa girmesi",
"C":"2 numaralı aracın 3 numaralı aracın geçmesini beklemesi",
"D":"3 numaralı aracın 2 numaralı aracı ikaz ederek durdurması"
},
"correct": "A"
}
]
}
]
}
Model:
struct QuizContainer: Codable {
var allQuiz: [Quiz]
}
struct Quiz: Codable {
var title: String
var test: [Test]
}
enum TestSectionType: String, Codable, Hashable {
case A = "A"
case B = "B"
case C = "C"
case D = "D"
}
struct Test: Codable {
var id: Int
var question: String
var isQuestionImage: Bool
var isSectionImage: Bool
var imageName: String
var sections: [TestSectionType.RawValue : String]
var correct: String
}
JSON Decode:
func getQuizQuestion() {
let databaseReference = Database.database().reference().child("allQuiz")
databaseReference.observeSingleEvent(of: .value) { snapshot in
do {
let foo = try FirebaseDecoder().decode(QuizContainer.self, from: snapshot.value ?? "")
print(foo)
} catch {
print(error.localizedDescription)
}
}
}
Could the getQuizQuestion function be faulty?
I solved my problem.
I don't need reference.
func getQuizQuestion() {
let databaseReference = Database.database().reference() // here
databaseReference.observeSingleEvent(of: .value) { snapshot in
do {
let foo = try FirebaseDecoder().decode(QuizContainer.self, from: snapshot.value ?? "")
print(foo)
} catch {
print(error.localizedDescription)
}
}
}

Swift: Help For Json parsing codeable/decodable

I am new to iOS, and want to parse the JSON using Decodable but cant get through this, how should I work this out?
The view controller where I am trying to parse the data
class ViewController: UIViewController {
var servers = [Server]()
let apiUrl = "https://someurl/api/dashboard"
override func viewDidLoad() {
super.viewDidLoad()
guard let url = URL(string: self.apiUrl) else { return }
getDataFrom(url)
}
fileprivate func getDataFrom(_ url: URL) {
URLSession.shared.dataTask(with: url){ (data, response, error) in
guard let data = data else { return }
do {
let apiResponse = try JSONDecoder().decode(Server.self, from: data)
print(apiResponse)
} catch let jsonError {
print(jsonError)
}
}.resume()
}
}
The Server.swift file where I am confirming to the decodable protocol
struct Server: Decodable {
let current_page: Int
let data: [ServerData]
let first_page_url: String
}
struct ServerData: Decodable {
let hostname: String
let ipaddress: String
let customer: [Customer]
let latest_value: [LatestValue]
}
struct Customer: Decodable {
let name: String
let contact_person :String
let email: String
}
struct LatestValue: Decodable {
let systemuptime: String
let memtotal: Float
let memfree: Double
let loadaverage: Float
}
No value associated with key CodingKeys I get this error,
The response from the server
{
"servers": {
"current_page": 1,
"data": [
{
"hostname": "johndoes",
"ipaddress": "10.0.2.99",
"id": 7,
"latest_value_id": 1130238,
"customers": [
{
"name": "Jane Doe",
"contact_person": "John Doe",
"id": 2,
"email": "john.#example.com",
"pivot": {
"server_id": 7,
"customer_id": 2
}
}
],
"latest_value": {
"id": 1130238,
"server_id": 7,
"systemuptime": "80days:10hours:23minutes",
"memtotal": 3.7,
"memfree": 1.6400000000000001,
"loadaverage": 2.25,
"disktotal": {
"dev-mapper-centos-root_disktotal": "38",
"dev-mapper-proquote-xfs-lvm_disktotal": "200"
},
"diskused": "{\"dev-mapper-centos-root_diskused\":\"16\",\"dev-mapper-proquote-xfs-lvm_diskused\":\"188\"}",
"custom_field": "[]",
"additional_attributes": {
"fathom": {
"name": "fathom",
"status": 1
},
"trenddb": {
"name": "trenddb",
"status": 1
},
"trendwi": {
"name": "trendwi",
"status": 1
},
"appsrv": {
"name": "appsrv",
"status": 1
}
},
"created_at": "2019-06-15 02:25:02",
"updated_at": "2019-06-15 02:25:02"
}
}
]
},
"message": "Success"
}
You seem to have few different errors in your data structure.
First of all, you are trying to decode Server while your json has servers inside a dict {"servers": ... }, So use a parent root object for it.
Your latest_value inside ServerData is defined as array, while it should be LatestValue struct not [LatestValue].
There is no first_page_url element in your json, but your Server struct has the property, make it optional, so that JSONDecoder decodes it only if it is present.
Here is your refined data models.
struct Response: Decodable {
let servers: Server
}
struct Server: Decodable {
let current_page: Int
let data: [ServerData]
let first_page_url: String?
}
struct ServerData: Decodable {
let hostname: String
let ipaddress: String
let customers: [Customer]
let latest_value: LatestValue
}
struct Customer: Decodable {
let name: String
let contact_person :String
let email: String
}
struct LatestValue: Decodable {
let systemuptime: String
let memtotal: Float
let memfree: Double
let loadaverage: Float
}
And decode Response instead of decoding Server, like so,
do {
let apiResponse = try JSONDecoder().decode(Response.self, from: data)
let server = apiResponse.server // Here is your server struct.
print(server)
} catch let jsonError {
print(jsonError)
}

Swift 4 decoding json using Codable and Realm

A few months back I started learning how to use the new codable and I posted this question Swift 4 decoding json using Codable. Right now I'm trying to use Realm and I've changed the model to follow the documents but I keep getting the error that it doesn't conform to decodable. I'm pretty lost as to how to make this work.
{
"success": true,
"message": "got the locations!",
"data": {
"LocationList": [
{
"LocID": 1,
"LocName": "Downtown"
},
{
"LocID": 2,
"LocName": "Uptown"
},
{
"LocID": 3,
"LocName": "Midtown"
}
]
}
}
class Location: Object, Decodable {
#objc dynamic var success: Bool = true
let data = List<LocationData>()
}
class LocationData: Object, Decodable {
let LocationList = List<LocationItem>()
}
class LocationItem: Object, Decodable {
#objc dynamic var LocID: Int!
#objc dynamic var LocName: String!
}
Instead of declaring your lists like this:
let data = List<LocationData>()
let LocationList = List<LocationItem>()
Declare them like this instead:
var data = LocationData()
var LocationList = [LocationItem]()
This confirms to the codable/decodable
Update:
I made a test which worked out with your models, tryout this:
struct Location: Decodable {
let success: Bool
let message: String
var data = LocationData()
}
struct LocationData: Decodable {
var LocationList = [LocationItem]()
}
struct LocationItem: Decodable {
var LocID: Int
var LocName: String
}
let json = "{ \"success\": true, \"message\": \"got the locations!\", \"data\": { \"LocationList\": [ { \"LocID\": 1, \"LocName\": \"Downtown\" }, { \"LocID\": 2, \"LocName\": \"Uptown\" }, { \"LocID\": 3, \"LocName\": \"Midtown\" } ] } }"
do {
let data = Data(json.utf8)
let decoded = try JSONDecoder().decode(Location.self, from: data)
print(decoded)
} catch let error {
print(error)
}
This will output:
Location(success: true, message: "got the locations!", data: CodableDecodable.LocationData(LocationList: [CodableDecodable.LocationItem(LocID: 1, LocName: "Downtown"), CodableDecodable.LocationItem(LocID: 2, LocName: "Uptown"), CodableDecodable.LocationItem(LocID: 3, LocName: "Midtown")]))
In your case your models should be:
class Location: Object, Decodable {
#objc dynamic var success: Bool = true
var data = LocationData()
}
class LocationData: Object, Decodable {
var LocationList = [LocationItem]()
}
class LocationItem: Object, Decodable {
#objc dynamic var LocID: Int!
#objc dynamic var LocName: String!
}