Swift Mapping Custom Object Using Codable - ios

I am trying to practice mapping custom objects using swift & replace all manual mapping by using Codable. This concept is new to me but seems very worthwhile. In this project I want to fetch the user and make sure that all their data is stored in their User Document on firebase (including the custom object 'FavouriteItems'.
My User Model struct is:
import SwiftUI
import FirebaseFirestoreSwift
struct UserModel: Identifiable, Codable{
#DocumentID var id: String?
var username : String
var pic : String
var bio: String
var uid : String
var isVerified: Bool
var favouriteItems: [FavouriteItems]
}
My struct for an Item is:
import SwiftUI
import FirebaseFirestoreSwift
import Firebase
struct Item: Identifiable, Codable {
#DocumentID var id: String?
var item_name: String
var item_type: String
var item_image: String
var item_details: String
var item_uid : String
var didFavourite: Bool? = false
var isFavourite: Bool = false
}
My struct for FavouriteItems is:
import SwiftUI
import FirebaseFirestoreSwift
//MARK: Make Hashable (See Tag Example)?
struct FavouriteItems: Identifiable, Codable {
#DocumentID var id: String?
var item: Item
}
My fetch user function is (edited):
func fetchUser(uid: String, completion: #escaping (UserModel) -> ()){
let db = Firestore.firestore()
let docRef = db.collection("Users").document(uid).getDocument(as: UserModel.self) { result in
switch result {
case .success(let user):
// A `UserModel` value was successfully initialized from the DocumentSnapshot.
print("UserModel: \(user)")
DispatchQueue.main.async {
completion(user)
}
case .failure(let error):
// A `UserModel` value could not be initialized from the DocumentSnapshot.
print("Error decoding UserModel: \(error)")
// handle errors todo
}
}
}
I am getting errors in the fetchUser code including:
Cannot convert value of type 'UserModel.Type' to expected argument type 'FirestoreSource'
Contextual closure type '(DocumentSnapshot?, Error?) -> Void' expects 2 arguments, but 1 was used in closure body
Incorrect argument label in call (have 'as:_:', expected 'source:completion:')
All of these errors occur on the line:
let docRef = db.collection("Users").document(uid).getDocument(as: UserModel.self) { result in
Here is my Podfile:

Related

How to represent a JSON file in SwiftUI?

I'm quite a beginner and trying to get the OpenWeather API JSON to show up in my challenge project.
I managed to model it
struct WeatherRespose: Codable {
var weather: [Weather]
var name: String
}
&
import Foundation
struct Weather: Hashable, Codable {
var main: String
var description: String
}
In addition to fetch the data in ContentView. However, when I try to present it:
#State var weatherForcast = Weather() or #State var weatherForcast = WeatherResponse() I get this error: Missing argument for parameter 'from' in call, insert 'from: <#Decoder#>'
The only thing that worked for me is to present the data in an array:
#State var weatherForcast = [Weather]()
Any idea what am I missing? thank you so much! Ran
I made pretty simple example of how you can do this. There are several additional files, so it's easier to understand how it works in details:
Create additional file called NetworkService, it will fetch weather data:
import Foundation
final class NetworkService{
private let url = "https://example.com"
func performWeatherRequest(completion: #escaping (Result<WeatherResponse, Error>) -> Void){
URLSession.shared.dataTask(with: URL(string: url)!) { data, response, error in
guard let data = data, error == nil else {
completion(.failure(WeatherError.failedToDownload))
return
}
let weatherResponse: WeatherResponse = try! JSONDecoder().decode(WeatherResponse.self, from: data)
completion(.success(weatherResponse))
}.resume()
}
public enum WeatherError: Error {
case failedToDownload
}
}
Create simple ViewModel which will retrieve data from our NetworkService and prepare to present it in ContentView
import Foundation
import SwiftUI
extension ContentView {
#MainActor class ContentViewVM: ObservableObject {
private var networkService = NetworkService()
#Published var currentWeatherMain: String?
#Published var currentWeatherDescription: String?
func fetchWeather(){
networkService.performWeatherRequest { [weak self] result in
DispatchQueue.main.async {
switch result {
case .success(let weatherResponse):
self?.currentWeatherMain = weatherResponse.weather[0].main
self?.currentWeatherDescription = weatherResponse.weather[0].description
case .failure(_):
print("oops, error occurred")
}
}
}
}
}
}
Add our ContentViewVM to our ContentView:
import SwiftUI
struct ContentView: View {
#StateObject var viewModel = ContentViewVM()
var body: some View {
VStack {
Text("The main is: \(viewModel.currentWeatherMain ?? "")")
Text("The description is: \(viewModel.currentWeatherDescription ?? "")")
}
.onAppear{
viewModel.fetchWeather()
}
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}
Hope it helps.

Decoding deeply nested object in Swift using nested structs

my goal is to decode a deeply nested json response from an api and render it in a list, below is the raw response data and my attempt.
The problem is that when I attempt to initialize my data to then further populated when I make my API call it gives me an error
“ Listing(data: []) { ERROR: No exact matches in call to initializer”
Is this the correct way to declare my structs to decode a deeply nested JSON Object?
Api response data ->
https://pastebin.com/7pgswZqk
import Foundation
import SwiftUI
import Combine
struct Listing: Decodable {
var kind: String
struct data: Decodable {
var modhash: String
var dist: Int
struct children: Decodable {
var kind: String
struct data: Decodable { <--- this level is where my iterable children are
var title: String
... <---- there is more properties here but I just put title for now
}
}
}
}
class NetworkingManager : ObservableObject {
var didChange = PassthroughSubject<NetworkingManager, Never>()
var ListingList = Listing(data: []) { *ERROR: No exact matches in call to initializer*
didSet {
didChange.send(self)
}
}
init() {
guard let url = URL(string: "https://www.reddit.com/best.json?limit=25") else { return }
URLSession.shared.dataTask(with: url) { (data, _, _) in
guard let data = data else { return }
let List = try! JSONDecoder().decode(Listing.self, from: data)
DispatchQueue.main.async {
self.ListingList = List
}
}.resume()
}
}
Then to iterate over the list, would it be like this?
List(ListingList.data.children.data.identified(by ./title) {listItem in
Text(listItem.title)
}
You defined your custom Decodable nested structs but missing their application.
Your Listing should look something like this...
struct Listing: Decodable {
var kind: String
var data: Data // instance of custom Listing.Data struct
struct Data: Decodable { // your custom decodable struct
var modhash: String
var dist: Int
var children: [Children] // instance of your custom Listing.Data.Children struct
struct Children: Decodable { // your custom decodable struct
var kind: String
var data: Data
struct Data: Decodable {
var title: String
// ...
}
}
}
}
...but it becomes unreadable very quickly so better create separate structs instead of nesting. In the following example you can see that naming can become kindof tedious when having deeply nested responses.
struct Listing: Decodable {
var kind: String
var data: ListingData
}
struct ListingData: Decodable {
var modhash: String
var dist: Int
var children: [ListingDataChildren]
}
struct ListingDataChildren: Decodable {
var kind: String
var data: ListingDataChildrenData
}
struct ListingDataChildrenData: Decodable {
var title: String
// ...
}
The optimal solution would be to create extensions to still have that kind of hierarchy that you get via nesting.
struct Listing: Decodable {
var kind: String
var data: Data
}
extension Listing {
struct Data: Decodable {
var modhash: String
var dist: Int
var children: [Children]
}
}
extension Listing.Data {
struct Children: Decodable {
var kind: String
var data: Data
}
}
extension Listing.Data.Children {
struct Data: Decodable {
var title: String
// ...
}
}

How do i call the parsed data from the GET request that have a longer nested JSON structure in Swift?

NOTE: Forgive my cluelessness, i am still new in regards to this. The full code is posted at the bottom.
ISSUE: It seems that when i have a short nest, i am able to call it for my #Published property however when i try an api request with a longer nest, like this. and type Decodable structs that follows the structure of the GET request
struct TripScheduleTest: Codable {
let TripList: InitialNest
}
struct InitialNest: Codable {
var Trip: [TravelDetail]
}
struct TravelDetail: Codable {
var Leg: [TripTest]
}
struct TripTest: Codable, Hashable {
var name: String
var type: String
}
I am not able to call it for the #Published var dataSet1 = [TripTest]()
self.dataSet1 = tripJSON.TripList.Trip.Leg
I get an error message, that says "Value of type '[TravelDetail]' has no member 'Leg'
I am not sure why, however it works when i use [TravelDetail]() instead of [TripTest]() in the #Published var and stop at Trip before Leg for the dataSet1, then it seems to at least build successfully. But now i am not able to get the name and type information from the request
Full code
import SwiftUI
struct TripScheduleTest: Codable {
let TripList: InitialNest
}
struct InitialNest: Codable {
var Trip: [TravelDetail]
}
struct TravelDetail: Codable {
var Leg: [TripTest]
}
struct TripTest: Codable, Hashable {
var name: String
var type: String
}
class TripViewModel: ObservableObject {
#Published var dataSet1 = [TripTest]()
init() {
let urlString = "http://xmlopen.rejseplanen.dk/bin/rest.exe/trip?originId=8600790&destId=6553&format=json"
guard let url = URL(string: urlString) else { return }
URLSession.shared.dataTask(with: url) { (data, resp, err) in
guard let data = data else { return }
do {
let tripJSON = try
JSONDecoder().decode(TripScheduleTest.self, from: data)
print(data)
DispatchQueue.main.async {
self.dataSet1 = tripJSON.TripList.Trip.Leg
}
} catch {
print("JSON Decode error: ", error)
}
}.resume()
}
}
struct TripView: View {
#ObservedObject var vm = TripViewModel()
var body: some View {
List(vm.dataSet1, id: \.self) { day in
Text("Test")
.font(.system(size: 12, weight: .bold))
Text(" \(day.name)")
.font(.system(size: 12))
}
}
}
Trip is an array (note the [])
You need to get one item of the array by index for example
tripJSON.TripList.Trip.first?.Leg
To assign the value to a non-optional array write
self.dataSet1 = tripJSON.TripList.Trip.first?.Leg ?? []

Does not conform to protocol Decodable

I have the following code which represents a Hockey Stick and some information about it. I have an issue where the stick isn't conforming to Decodable. I understand that every type used in the struct needs to also be codeable, and they are. For some reason however the "var conditions" line causes the error that I am unsure how to fix. Thank you!
enum StickLocation: Int, Codable, Hashable, CaseIterable {
case handle, mid, bottom
}
enum StickCondition: Int, Codable, Hashable, CaseIterable {
case pristine, scuffed, damaged, broken
}
struct HockeyStick: Identifiable, Codable {
var barcode: Int
var brand: String
var conditions: [StickLocation:(condition:StickCondition, note:String?)] // Offending line
var checkouts: [CheckoutInfo]
var dateAdded: Date
var dateRemoved: Date?
// Conform to Identifiable.
var id: Int {
return self.barcode
}
// If the stick was never removed then its in service.
var inService: Bool {
return self.dateRemoved == nil
}
}
The value type of your conditions dictionary is (StickCondition, String?), which is a tuple. Tuples are not Decodable/Encodable, and you cannot make them conform to protocols, so to fix this I recommend you make a new struct to replace the tuple like this:
enum StickLocation: Int, Codable, Hashable, CaseIterable {
case handle, mid, bottom
}
enum StickCondition: Int, Codable, Hashable, CaseIterable {
case pristine, scuffed, damaged, broken
}
struct StickConditionWithNote: Codable, Hashable {
var condition: StickCondition
var note: String?
}
struct HockeyStick: Identifiable, Codable {
var barcode: Int
var brand: String
var conditions: [StickLocation: StickConditionWithNote]
var checkouts: [CheckoutInfo]
var dateAdded: Date
var dateRemoved: Date?
// Conform to Identifiable.
var id: Int {
return self.barcode
}
// If the stick was never removed then its in service.
var inService: Bool {
return self.dateRemoved == nil
}
}

Failing with the decode of JSON

I am trying to decode a JSON response from the youtube API in swift.
The JSON information is:
I made a Decodable structure:
// Build a model object to import the JSON data.
struct PlaylistInformation: Decodable {
struct Items: Decodable {
struct VideoNumber: Decodable {
struct Snippet: Decodable {
let title: String
}
let snippet: Snippet
}
let videoNumber: VideoNumber
}
let items: Items
}
And I get the error when trying to decode:
// We decode the JSON data get from the url according to the structure we declared above.
guard let playlistInformation = try? JSONDecoder().decode(PlaylistInformation.self, from: data!) else {
print("Error: could not decode data into struct") <-- HERE IS THE ERROR
return
}
// Comparing DB Versions.
let videoTitle = playlistInformation.items.videoNumber.snippet.title as NSString
print(videoTitle)
The error I get is:
Error: could not decode data into struct
I guess it has something to do with the "items" in the struct, as it is an array... but I have no idea about how to solve that.
Given that items is an array, you have to model it as an array and not a struct:
// Build a model object to import the JSON data.
struct PlaylistInformation: Decodable {
struct Item: Decodable {
struct Snippet: Decodable {
let title: String
}
let snippet: Snippet
}
let items: [Item]
}
And then access each item using its index, e.g.
let videoTitle = playlistInformation.items[0].snippet.title as NSString
print(videoTitle)
Yes, the error was coming from the "items" in the struct as it is an array.
The correct Decodable struct is:
struct PlaylistInformation: Decodable {
struct Items: Decodable {
struct Snippet: Decodable {
struct Thumbnails: Decodable {
struct High: Decodable {
let url: String
}
let high: High
}
struct ResourceId: Decodable {
let videoId: String
}
let publishedAt: String
let title: String
let thumbnails: Thumbnails
let resourceId: ResourceId
}
let snippet: Snippet
}
let items: [Items]
}
Thank you for your help.

Resources