Im running a service, which when I get an error it should show me the error message that comes from the backend. So far I have done this, and it works.
However, I would like to know if it is possible to parse the back error in an enum for example, or something like that so that I would not have to call response.errors.result... But I can't understand how to do it to later obtain the string of the message that comes from the back in case of error.
On the other hand, I have a question about whether it is possible not to have the default when the optional value is nil in the service. I mean the cases where I have to perform ?? ErrorMutation(code: 10, message: "error") for example. If exist a way where i don't have to pass it a default value.
struct ResponseData: Codable {
let data: Data?
let errors: Errors?
}
struct Data: Codable {
let result: Mutation?
}
struct Mutation: Codable {
let id: String?
let active: Bool?
}
struct Errors: Codable, Error {
let result: [ErrorMutation]?
}
struct ErrorMutation: Codable, Error {
let code: Int?
let message: String?
}
final class MutationManager: Service {
func getTDMutation() -> Observable<Result<Mutation, ErrorMutation>> {
let mutationQuery = Bundle.main.value ?? ""
let body = ["query": "mutation \(mutationQuery)"]
return response(body: body)
.map {
try JSONDecoder().decode(ResponseData.self, from: $0) }
.flatMap { response -> Observable<Result<Mutation, ErrorMutation>> in
guard response.data != nil else {
let error = response.errors?.result.first
return .just(.failure(error ?? ErrorMutation(code: 10, message: "error")))
}
return .just(.success(response.data?.result ?? Mutation(id: "", active: false)))
}
}
}
Parsing with Codable you have to rewrite code ResponseData class and Data class Like This.
struct ResponseData: Codable {
let dataMutation: DataMutation?
let errors: Errors?
private enum CodingKeys : String, CodingKey {
case errors, dataMutation = "data"
}
}
struct DataMutation: Codable {
let result: Mutation?
}
Related
I want to get document id to work with it for editing and deleting document after decoding. How can I do this?
My model:
struct MoodRecord: Codable, Hashable, Identifiable {
#DocumentID var id: String?
let user: String
let date: String
let time: String
let mood: Int
}
My function:
class func getRecords <T: Decodable> (
reference: CollectionReference,
type: T.Type,
completion: #escaping (Result<[T], Error>) -> Void
) {
reference.whereField("user", isEqualTo: AuthManager.shared.getUserId() ?? "")
.getDocuments { snapshot, error in
if let documents = snapshot?.documents {
do {
let records: [T] = try documents.map { try $0.decoded(type: T.self) }
completion(.success(records))
} catch let error {
completion(.failure(error))
}
} else if let error = error {
completion(.failure(error))
}
}
}
My decoder:
extension QuerySnapshot {
func decoded <T: Decodable> (type: T.Type) throws -> [T] {
let objects: [T] = try documents.map { try $0.decoded(type: T.self) }
return objects
}
}
extension QueryDocumentSnapshot {
func decoded <T: Decodable> (type: T.Type) throws -> T {
let jsonData = try JSONSerialization.data(withJSONObject: data(), options: [])
let object = try JSONDecoder().decode(type.self, from: jsonData)
return object
}
}
I use only auto-ID in Firestore and want to work with them in this task. Can I do this?
You can use Firestore's Codable support to map document IDs. No need to implement a custom decoder - we've done all the hard work for you.
Here is how.
1. Create a model for your data
You already did this. Looking at the attributes in your MoodRecord struct, I assume you want to use date and time to track timestamps, and mood to capture the value of an enum. I've updated the struct accordingly:
struct MoodRecord: Codable, Hashable, Identifiable {
#DocumentID var id: String?
var user: String
var date: Date
var time: Date
var mood: Mood
}
enum Mood: String, Codable {
case great
case ok
case good
case bad
case terrible
}
2. Map data using Codable
Fetching Firestore documents and mapping them to Swift structs becomes a one-liner thanks to Codable:
docRef.getDocument(as: MoodRecord.self) { result in
// handle result
}
Here is a complete code snippet for fetching a single document:
private func fetchMoodRecord(documentId: String) {
let docRef = db.collection("moodrecords").document(documentId)
docRef.getDocument(as: MoodRecord.self) { result in
switch result {
case .success(let moodRecord):
// A MoodRecord value was successfully initialized from the DocumentSnapshot.
self.moodRecord = moodRecord
self.errorMessage = nil
case .failure(let error):
// A MoodRecord value could not be initialized from the DocumentSnapshot.
self.errorMessage = "Error decoding document: \(error.localizedDescription)"
}
}
}
3. Updating a document
To update a document using Codable, use the following code snippet:
func updateMoodRecord(moodRecord: MoodRecord) {
if let id = moodRecord.id {
let docRef = db.collection("moodRecords").document(id)
do {
try docRef.setData(from: moodRecord)
}
catch {
print(error)
}
}
}
4.Adding new documents
Adding new documents is even easier:
func addMoodRecord(moodRecord: MoodRecord) {
let collectionRef = db.collection("moodRecords")
do {
let newDocReference = try collectionRef.addDocument(from: moodRecord)
print("Mood record stored with new document reference: \(newDocReference)")
}
catch {
print(error)
}
}
More
To learn more about how to map Firestore documents using Swift's Codable protocol, including how to map advanced data types such as date, time, colors, enums, how to fetch data using snapshot listeners, and how to handle any errors that might occur during the mapping process, check out Mapping Firestore Data in Swift - The Comprehensive Guide and the accompanying sample project on GitHub
I have several URLs and, accordingly, there is a data structure for each of them.
URLS:
case "Get Day":
return "time/get_day.php"
case "Get Time":
return "time/get_time.php"
case "Get Current Time":
return "user/get_current_time.php"
STRUCTS:
struct Day: Codable {
var status: Int? = nil
var error_message: String? = nil
var result: [Result]? = nil
}
struct Time: Codable {
let status: Int?
let error_message: String?
let result: [Result]?
struct Result: Codable {
let id: String
let startTime: String
let endTime: String
}
}
struct CurrentTime: Codable {
let status: Int?
let error_message: String?
let current_time: Int?
}
struct Result: Codable {
let id: String
let name_en: String
let name_ru: String
let name_kk: String
}
At the moment I have a parseJson () function. In which I can manually change the type of structure for parsing one by one. But I cannot think of how to do this so that I would not change anything in the code manually.
func parseJson(data: Data) {
let decoder = JSONDecoder()
do {
let parsedData = try decoder.decode(Day.self, from: data)
print(parsedData)
} catch {
print("Error parsing Json:\(error)")
}
}
Please, if you have an example or ideas, share with me.
// Generic function to decode any decodable struct
func parseJson<T: Decodable>(data: Data) -> T? {
let decoder = JSONDecoder()
do {
let parsedData = try decoder.decode(T.self, from: data)
return parsedData
} catch {
return nil
}
}
// Usage
let someDay: Day? = parseJson(data: dayData)
let sometime: Time? = parseJson(data: timeData)
Firs time using Codable protocol so many things not clear. After going through several tutorials started with the Codable thing to parse data. Below is the code:
struct MyTasks : Codable {
var strDateDay : String = ""
var strReminder : String = ""
var strRepetitions : String = ""
var name : String = ""
var strNotes : String = ""
var strUserId : String = ""
private enum CodingKeys: String, CodingKey {
case strDateDay = "duedate"
case strReminder = "reminder"
case strRepetitions = "recurring"
case name = "name"
case strNotes = "notes"
case strUserId = "userId"
}
}
let networkManager = DataManager()
networkManager.postLogin(urlString: kGetMyTasks, header: header, jsonString: parameters as [String : AnyObject]) { (getMyTasks) in
print(getMyTasks?.name as Any) // -> for log
Common_Methods.hideHUD(view: self.view)
}
// Network manager:
func postLogin(urlString: String, header: HTTPHeaders, jsonString:[String: AnyObject], completion: #escaping (MyTasks?) -> Void) {
let apiString = KbaseURl + (kGetMyTasks as String)
print(apiString)
Alamofire.request(apiString, method: .post, parameters: jsonString , encoding: URLEncoding.default, headers:header).responseJSON
{ response in
let topVC = UIApplication.shared.keyWindow?.rootViewController
DispatchQueue.main.async {
//Common_Methods.showHUD(view: (topVC?.view)!)
}
guard let data = response.data else { return }
do {
let decoder = JSONDecoder()
let loginRequest = try decoder.decode(MyTasks.self, from: data)
completion(loginRequest)
} catch let error {
print(error)
completion(nil)
}
}
}
Now, this is the response:
keyNotFound(CodingKeys(stringValue: "strDateDay", intValue: nil), Swift.DecodingError.Context(codingPath: [], debugDescription: "No value associated with key CodingKeys(stringValue: \"strDateDay\", intValue: nil) (\"strDateDay\").", underlyingError: nil))
Below is the json response i am trying to parse:
{
"data": [
{
"userId": 126,
"name": "My task from postman ",
"notes": null,
"totalSteps": 0,
"completedSteps": 0,
"files": 0
},
{
"userId": 126,
"name": "My task from postman 1",
"notes": null,
"totalSteps": 0,
"completedSteps": 0,
"files": 0
}
]
}
I know i am missing something but even after spending more than half day i haven't properly understood what is wrong and where. Please guide.
problem is with your struct the names of the properties in the struct should be match with json data or if you want to use custom name you should use enum CodingKeys to convert them to your liking
struct MyTasks: Codable {
let data: [Datum]
}
struct Datum: Codable {
let userID: Int?
let name: String
let notes: String?
let totalSteps, completedSteps, files: Int
enum CodingKeys: String, CodingKey {
case userID = "userId"
case name, notes, totalSteps, completedSteps, files
}
}
func postLogin(urlString: String, header: HTTPHeaders, jsonString:[String: AnyObject], completion: #escaping (MyTasks?) -> Void) {
let apiString = KbaseURl + (kGetMyTasks as String)
print(apiString)
Alamofire.request(apiString, method: .post, parameters: jsonString , encoding: URLEncoding.default, headers:header).responseJSON
{ response in
let topVC = UIApplication.shared.keyWindow?.rootViewController
DispatchQueue.main.async {
//Common_Methods.showHUD(view: (topVC?.view)!)
}
guard let data = response.data else { return }
do {
let decoder = JSONDecoder()
let loginRequest = try decoder.decode(MyTasks.self, from: data)
completion(loginRequest)
} catch let error {
print(error)
completion(nil)
}
}
}
And one more thing keep in mind that you know the exact type of notes and make it optional otherwise it rise error, there was no type so I put a optional String in there.
Hope this will help.
The problem is that in your top JSON you have a single "data" property of type array.
You are asking JSONDecoder to decode a JSON object containing only "data" property into a Swift object called "MyTasks" with the stored properties you defined (including strDateDay).
So the decoder sends you this response because he can't find the strDateDay in that top object.
You have to make a top object for deserialization. Something like :
struct MyResponse: Codable {
var data: [MyTasks]
}
Then just give it to your JSONDecoder like you have already done :
let loginRequest = try decoder.decode(MyResponse.self, from: data)
The data you send to the decoder is your full JSON stream (the data property of Alamofire's response object), and not only the "data" property of your JSON structure.
I'd suggest to use CodyFire lib cause it support Codable for everything related to requests.
Your POST request with it may look like
struct MyTasksPayload: JSONPayload {
let param1: String
let param2: String
}
struct MyTasksResponseModel: Codable {
let strDateDay: String
let strReminder: String
let strRepetitions: String
let name: String
}
let server = ServerURL(base: "https://server1.com", path: "v1")
APIRequest<[MyTasksResponseModel]>(server, "mytasks", payload: payloadModel)
.method(.post)
.onRequestStarted {
// show HUD here
}
.onError {
// hide hud and show error here
}
.onSuccess { tasks in
// here's your decoded tasks
// hide hud here
}
Use APIRequest<[MyTasksResponseModel]> to decode array
Use APIRequest to decode one object
Here i am getting API response of all of my api.
{
"success" : true,
"message" : "",
"data" : {
/multipal data parameter/
}
}
And here is my codable model
struct Login: Codable {
let success: Bool
let message: String
let data: Data
struct Data: Codable {
}
}
How can i create common Sturct for success and message parameter.
You can make the root struct representing the network response generic, this will allow you to keep the success and message parts common between all specialised responses.
struct NetworkResponse<ResponseData:Codable>: Codable {
let success: Bool
let message: String
let data: ResponseData
}
You shouldn't create custom types with the same name as built in types, since that will lead to confusion, especially for other people reading your code, so I renamed your custom Data type to ResponseData.
For instance you can create a LoginResponse model and decode it like below. You can do the same for other responses from the same API.
let loginResponse = """
{
"success" : true,
"message" : "",
"data" : {
"username":"test",
"token":"whatever"
}
}
"""
struct LoginResponse: Codable {
let username: String
let token: String
}
do {
print(try JSONDecoder().decode(NetworkResponse<LoginResponse>.self, from: Data(loginResponse.utf8)))
} catch {
print(error)
}
Common structure :
I have created something like that
struct statusModel<T:Codable>: Codable {
let message : String
let resultData : [T]?
let status : Int
enum CodingKeys: String, CodingKey {
case message = "message"
case resultData = "resultData"
case status = "status"
}
}
Regular model (resultData)
struct modelInitialize : Codable {
let profileimgurl : String?
let projecturl : String?
enum CodingKeys: String, CodingKey {
case profileimgurl = "profileimgurl"
case projecturl = "projecturl"
}
}
You can set like as below
do {
guard let reponseData = responseData.value else {return} //Your webservice response in Data
guard let finalModel = try?JSONDecoder().decode(statusModel<modelInitialize>.self, from: reponseData) else {return}
}
I am trying to parse some json but seem to be getting nil in the outputs.
I am not sure where I am going wrong and could use some help trying to figure this out.
struct albumInfo: Decodable {
var name: String?
var artist: String?
var url: String?
var playcount: String?
var listeners: String?
var releasedate: String?
var summary: String?
}
class SearchVC: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
Choice = "album"
Album = "Believe"
Artist = "Cher"
let tryURL = "\(BASE_URL)\(Choice!).getinfo&api_key=\(API_KEY)&artist=\(Artist!)&album=\(Album!)&format=json"
print(tryURL)
guard let url = URL(string: tryURL) else { return }
URLSession.shared.dataTask(with: url) { (data, response, err) in
guard let data = data else { return }
do {
let albumDescription = try JSONDecoder().decode(albumInfo.self, from: data)
print(albumDescription.artist)
}catch let jsonErr {
print("Error seroalizing json", jsonErr)
}
}.resume()
}
Here is the data as shown with the tryUrl.
First of all please conform to the naming convention that struct names start with a capital letter.
There are two major issues:
The root object is a dictionary with one key album containing the dictionary with keys name, listeners etc.
The key summary is in another dictionary for key wiki.
The structure of the JSON is very easy to identify. The body within each pair of braces ({}) represents a separate struct.
Further there is no key releasedate so this struct member has to be declared as optional, all other members can be declared as non-optional and as constants (let). url can be declared as URL for free.
Change your structs to
struct Root : Decodable {
let album : AlbumInfo
}
struct AlbumInfo: Decodable {
let name: String
let artist: String
let url: URL
let playcount: String
let listeners: String
let releasedate: String?
let wiki : Wiki
}
struct Wiki: Decodable {
let content: String
let published: String
let summary: String
}
and decode Root
let albumDescription = try JSONDecoder().decode(Root.self, from: data)
print(albumDescription.album.artist)
The first key of your response is "album", you need to parse that first.
The classes do not correspond to json, I guess you should use the following approach (new classes implement your decode, encode protocol):
class JsonInfo {
var album : albumInfo
}
do {
let albumDescription = try JSONDecoder().decode(albumInfo.self, from: data)
print(albumDescription.album.artist)
}catch let jsonErr {
print("Error seroalizing json", jsonErr)
}