Fields in custom data model are "nil" after decoding API response with JSONDecoder().decode in Swift 5 - swift

I've defined a custom data model for a User Object in Swift like so:
user data model
I've got a function that pulls User data from an API like so:
get data from api
Here's the response when calling the same endpoint with Postman:
api response
And here's the console debug output from line 75 of my function, showing that I'm actually receiving that data: debug output
So that all looks good as far as I can tell.
I'm then using JSONDecoder().decode to decode the jsonData I receive from the api, for which I'm not getting any errors. However, when I'm then printing a field from the returned user object, that field (as well as all others) are "nil": all fields in user object are nil
I'm sure it's something small and stupid but I've spent hours now and can't figure out what it is. Can anyone spot the error and let me know what I'm doing wrong here?
Help much appreciated!!!

For Codable you need to give same name of properties to the json key. And make sure it's in correct scope. For example you email properties inside of detailresponse json object & detailresponse inside of main json object. If you don't wont more class you need to use it's init container method.
class Response: Codable {
var statuscode: Int?
var response_type: Int?
// Other properties
var detailresponse: DetailResponse?
}
class DetailResponse: Codable {
var id: Int?
// Other properties
var socialmediadata: User?
}
class User: Codable {
var id: Int?
var email: String?
// Other properties
}
Now, json will parse like this.
let response = try JSONDecoder().decode(Response.self, from: jsonData)
print(response.detailresponse?.socialmediadata?.email ?? "")

Related

I am trying to add followers & following properties inside a custom struct to my User object that is conforming to the Identifiable/decodable protocal

import FirebaseFirestoreSwift
import Firebase
// I created this object so that i can map the users data and access it threw this object. example I could say thigs like user.username
// The decodable protocall will read the data dictonary and looks for the exact name for the keys/property names I have listed in the data dictonary, this makes life easier when working with objects and downloading information from an api
struct User: Identifiable, Decodable {
// Im able to delete the uid field out of firebase because this will read the documentID from firebase and store it in this id property, so that I dont have to dupicate that data in the actual body of the object
#DocumentID var id: String?
let username: String
let fullname: String
let profileImageUrl: String
let email: String
let stats: UserStats
// This is a computed property saying if the currently logged in user's id is equal to the id on my object (#DocumentID)
var isCurrentUser: Bool { return Auth.auth().currentUser?.uid == id }
}
struct UserStats: Decodable {
let followers: Int
let following: Int
}
Add ? at the end of each variable.
#FirestoreQuery does little error handling when it comes to decoding.
Also, if you are not using #FirestoreQuery use do try catch instead of try?

How to design Complications for watchOS 6 independent app?

I have an independent application on watchOS 6 and in my app I am using the Firestore REST API to show the data to the user using URLSession.
Since the Cloud Firestore REST API returns a JSON string, in order to process the data, I have created nested structs. Example: To access the 'title' in a particular response, I do something like this: response.mapValue.fields.title.stringValue.
The app works fine for now. In the long run I plan on creating the URLSessions as background tasks. Right now, I am calling URLSession every time the view is rendered by using .onAppear(functionToUseURLSession())) on my data's List view.
Now the next thing I want to implement is the complication for this particular app.
I am new to Swift and SwiftUI and am having the hardest time just getting started. All the tutorials I've been through online use WKExtensionDelegate to get the data models for the complication.
But in my case, I don't even have a data model! I just have structs calling other structs in a nested fashion in order to process the JSON response I get from the API call.
Any help allowing me to understand this is highly appreciated!
Here is some code to better understand the structure of the App:
struct Firebase: Codable {
var name: String?
var createTime, updateTime: String?
var fields: FirebaseFields
}
Now, FirebaseFields is also another struct:
struct FirebaseFields: Codable {
var emailID, lastName, firstName: String?
var goalsRoutines: GoalsRoutines
enum CodingKeys: String, CodingKey {
case emailID = "email_id"
case lastName = "last_name"
case firstName = "first_name"
}
}
Similarly, GoalsRoutines is also a struct...
As mentioned above, I have made these structs to follow the exact structure of the JSON object is get in response from Firebase API. So I access fields like: Firebase.FirebaseFields.GoalsAndRoutines.whatever.is.my.firebase.schema
My GoalsView.swift is:
var body: some View {
List{
...
...
}.onAppear{
FirebaseServices.getFirebaseData() {
(data) in self.data = data
}
}
}
And finally, in FirebaseServices.swift, func getFirebaseData is:
func getFirebaseData(completion: #escaping ([Value]) -> ()) {
guard let url = URL(string: "FIRESTORE URL HERE") else { return }
URLSession.shared.dataTask(with: url) { (data, _, _) in
let data = try! JSONDecoder().decode(Firebase.self, from: data!)
DispatchQueue.main.async {
completion(data.fields.goalsRoutines.arrayValue.values)
}
}.resume()
}
So this is how I currently get the data and show it on the app.
I am really lost on how to do the same on my Complication.
Any help is really appreciated.

How to add Dynamic key name and value in Encodable(Swift)?

I have a very simple request:
{"token": "abcd", "key": "value" }
I'm trying to add this request as an Encodable. Now, here the issue arises that the key name can be anything like "123", "311", the type of the key will be String, but it's name is dynamic. How can I add dynamic names in Encodable?
struct Answers: Encodable {
let token: String
let key: String
}
I tried using generics, but it didn't work. Any one have any idea?
I don't think Codable allows that kind of functionality currently. You can't create a Codable type with dynamic keys as of now.
Alternatively, if this is the model you're using, you can simply create a Dictionary from it and then encode it using JSONEncoder().
Example:
let dict = ["token": "abcd", "1234": "value"]
do {
let response = try JSONEncoder().encode(dict)
print(response)
} catch {
print(error)
}
Rob,
If you could change the JSON response you can use something like that {token: "AAA", data: {"key":"123"}}.
So you can create
struct Response<DataType: Codable>: Codable {
let token: String
let data: DataType
}
With this Struct, you can pass many combinations of dynamic values.
My solution doesn't work with your actual data, but maybe you can talk with the team about API and maybe change the data.

Vapor 3 API: embed Future<Model> in response object

Suppose I have a model called Estimate. I have a Vapor 3 API that I want to return a list of these models, filtered by query parameters. Doing so currently returns a Future<[Estimate]>, which results in the API returning JSON that looks like this:
[{estimate object}, {estimate object}, ...]
Instead, I'd like make it return something this:
{"estimates": [{estimate object}, {estimate object}, ...]}
So, the same thing as before, but wrapped in a JSON object with a single key, "estimates".
According to the documentation, any time I want to return something non-default, I should make a new type for it; this suggests to me I should create a type like:
final class EstimatesResponse: Codable {
var estimates: [Estimate]?
}
However, after filtering I get a Future<[Estimate]> and NOT a pure [Estimate] array, meaning that I couldn't assign it to my EstimatesResponse estimates property. It seems weird to make the type of estimates be Future<[Estimate]>, and I'm not sure how that'd turn out.
How, then, can I return JSON of the correct format?
First, you need to create Codable object, I prefer struct as below. Must implement protocol Content for routing.
struct EstimatesResponse: Codable {
var estimates: [Estimate]
}
extension EstimatesResponse: Content { }
I assumed that you are using a controller and inside the controller, you can use the following pseudo-code. Adjust your code so that val is Future<[Estimate]>, then use flatmap/map to get [Estimate].
func getEstimates(_ req: Request) throws -> Future<EstimatesResponse> {
let val = Estimate.query(on: req).all()
return val.flatMap { model in
let all = EstimatesResponse(estimates: model)
return Future.map(on: req) {return all }
}
}

Alamofire POST request replacing characters in output

I am making this request:
Alamofire.request(path,method:.post, parameters:params, encoding: JSONEncoding.default,headers:headers).responseJSON { response in
print("Result: \(response.result.value)"
do {
self.list = try JSONDecoder().decode([list].self, from: result!) for event in self.lists {
print(event.title," : ",event.description)
}
} catch let parseError as NSError {
print("JSON Error \(parseError.localizedDescription)")
}
}
Data that ought to look like this (JSON?) - Postman output, all fields not included herein:
{
"start": "2016-02-01 11:30:00",
"end": "2016-02-01 14:42:24",
"id": 3192,
"ownership": false,
}
prints out looking like this in XCode:
{
start = "2016-02-01 11:30:00";
end = "2016-04-14 20:30:00";
"id" = 3192;
ownership = 0;
}
Result : I am not able to parse this using JSONDecoder, error:
"The data couldn’t be read because it isn’t in the correct format".
Newbie to Swift ... so, thanks in advance for the help!
Edit: Edited for clarity with more information. Thanks again!
Alamofire is not "replacing characters in output", it is giving you a different object than the one you expect. If you print out the type of your response.result you should be surprised by the NSDictionary you are likely to get at that point. Our trusted friend print(...) is nice enough to turn this into a String representation of whatever you pass it, but you are not likely to be able to parse this using JSONDecoder since it is not Data (which is what the decoder is expecting).
As I said before: use responseString in order to get the response and turn it into the appropriate Data for parsing using JSONDecoder. In order to be able to control this process properly you want to include your Codable derivative into the question and you are likely to set the date parsing strategy on the JSONDecoder.
Without your struct and some properly formatted JSON from your response (well, Postman will do if it is reasonably complete) we are unlikely to be able to help you any further.
P.S.: It is not an entirely good idea to change your question completely through an edit. You might be better of posting a new question and leaving a comment with a pointer to it on the old one so people revisiting it may be lead to the right place. If you update your question you should usually leave the old one intact and amend it with additional information in order to keep the existing discussion relevant.
As workaround you can just add CodingKey to decoded struct.
Just add to your struct/class
private enum CodingKeys: String, CodingKey {
case event_id = "id"
}
Please refer to https://benscheirman.com/2017/06/swift-json/
I can suggest the following solution:
Firstly you need a pojo class to refer your json object. Easiest way
that I know is the library called SwiftyJSON
(https://github.com/SwiftyJSON/SwiftyJSON) firstly you can add this
library to your project. Then you can create the following pojo class
for your output (optional: You can also install
SwiftyJSONAccelarator(https://github.com/insanoid/SwiftyJSONAccelerator)
to generate pojo classes using json outputs.):
import Foundation
import SwiftyJSON
public class MyOutput: NSObject {
// MARK: Declaration for string constants to be used to decode and also serialize.
internal let kMyOutputEndKey: String = "end"
internal let kMyOutputInternalIdentifierKey: String = "id"
internal let kMyOutputOwnershipKey: String = "ownership"
internal let kMyOutputStartKey: String = "start"
// MARK: Properties
public var end: String?
public var internalIdentifier: Int?
public var ownership: Bool = false
public var start: String?
// MARK: SwiftyJSON Initalizers
/**
Initates the class based on the object
- parameter object: The object of either Dictionary or Array kind that was passed.
- returns: An initalized instance of the class.
*/
convenience public init(object: AnyObject) {
self.init(json: JSON(object))
}
/**
Initates the class based on the JSON that was passed.
- parameter json: JSON object from SwiftyJSON.
- returns: An initalized instance of the class.
*/
public init(json: JSON) {
end = json[kMyOutputEndKey].string
internalIdentifier = json[kMyOutputInternalIdentifierKey].int
ownership = json[kMyOutputOwnershipKey].boolValue
start = json[kMyOutputStartKey].string
}
}
After that after calling url with Alomofire and getting response, you
can simply map the output to your pojo class. Finally, you can use any
field in your class(myOutput in my example):
Alamofire.request(path,method:.post, parameters:params, encoding: JSONEncoding.default,headers:headers).responseJSON { response in
switch response.result {
case .success(let value):
let json = JSON(value)
let myOutput = MyOutput.init(json: json)
//use myOutput class for your needs
case .failure( _):
self.createNetworkErrorPopup()
}
}