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()
}
}
Related
I am new to iOS and trying to store User object in UserDefaults. So that when the app is launched again, I can check user type and based on it, I need to navigate to relevant screen.
For that, I have created a User class as below (Codable) and it has one userType enum property!
enum UserType: Int, Codable {
case userType1 = 0
case userType2 = 1
case notDetermined = 2
init(from decoder: Decoder) throws {
let label = try decoder.singleValueContainer().decode(Int.self)
self = UserType(rawValue: label) ?? .notDetermined
}
}
class User: Codable {
public var userFullName: String? = ""
public var userType: UserType? //= .notDetermined
enum CodingKeys: String, CodingKey {
case userFullName
}
}
In my view Controller class, I am creating a new instance for User object and trying to store in user defaults as below:
let newUser = User()
newUser.userFullName = "Test"
newUser.userType = userTypeBtn.isSelected ? .userType1 : .userType2
when I print the newUser's userType, I can see proper value whichever is selected. But after that, when I am trying to store it in userDefaults as below, it returns nil for userType property.
do {
let encoded = try JSONEncoder().encode(newValue)
UserDefaults.standard.set(encoded, forKey: UserDefaultKey.currentUser)
UserDefaults.standard.sync()
} catch {
print("Unable to Encode User Object: (\(error))")
}
when I tried to print this encoded variable, and decoded it in console
JSONDecoder().decode(User.self, from: encoded).userType
it prints nil.
Please help me how can I store optional enum property in UserDefaults and retrieve it when needed using Codable
You should include userType in your CodingKeys enum:
enum CodingKeys: String, CodingKey {
case userFullName
case userType
}
Or just delete the CodingKeys enum entirely, since by default, all the properties are included as coding keys. The keys in the CodingKeys enum determines what the synthesised Codable implementation will encode and decode. If you don't include userType, userType will not be encoded, so it will not be stored into UserDefaults.
I am not getting it from Server and userType is an external property outside the JSON response
This is fine, because userType is optional. If the JSON does not have the key, it will be assigned nil. This might be a problem if you are also encoding User and sending it to the server, and that the server can't handle extra keys in the request, in which case you need two structs - one for storing to/loading from UserDefaults, one for parsing/encoding server response/request.
Remember to encode a new User to UserDefaults when you try this out, since the old one still doesn't have the userType encoded with it.
Observations
Having a custom implementation for Decodable part of enum UserType: Int, Codable is probably not the best idea. Swift compiler supports encoding/decoding enum X: Int out of the box without having you to write custom implementation for it. (In fact, starting with Swift 5.5, Swift compiler can now do this for enums that have cases with associated values as well.)
You should try to avoid having cases like .notDetermined. Either user has a type that's well defined or user.type is nil. You can easily define convenience getters on user itself to know about it's type.
Swift allows nesting of types, so having User.Kind instead of UserType is more natural in Swift.
Following implementation takes care of all of these points.
import Foundation
class User: Codable {
enum Kind: Int, Codable {
case free = 1
case pro = 2
}
public var fullName: String?
public var kind: Kind?
}
let newUser = User()
newUser.fullName = "Test"
newUser.kind = .free
do {
let encoded = try JSONEncoder().encode(newUser)
UserDefaults.standard.set(encoded, forKey: "appUser")
if let fetched = UserDefaults.standard.value(forKey: "appUser") as? Data {
let decoded = try JSONDecoder().decode(User.self, from: fetched)
print(decoded)
}
}
Above code includes definition, construction, encodeAndStore, fetchAndDecode and it does everything you need without any custom implementation.
Bonus
Above code does not print a nice description for the User. For that, you can add CustomStringConvertible conformance like this.
extension User: CustomStringConvertible {
var description: String {
"""
fullName: \(fullName ?? "")
kind: \(kind?.description ?? "")
"""
}
}
extension User.Kind: CustomStringConvertible {
var description: String {
switch self {
case .free: return "free"
case .pro: return "pro"
}
}
}
If you try print(decoded) after implementing this, you will clearly see what you want to see for User instance.
User.kind can be nil and I don't want to handle it with if let every time I need to check this from different screens in the app.
No worries, it can be simplified to this.
extension User {
var isFreeUser: Bool { kind == .free }
var isProUser: Bool { kind == .pro }
}
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.
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 ?? "")
So I have this enum that I use for the few url requests I use in my app :
enum Netwrok {
case popular
case topRated
case latest
// ...
static let baseUrl = "http://..."
func path() -> String {
switch self {
case .popular: return "/popular"
// ...
}
}
}
And I would like to add a function that returns the Decodable Type of model the network stack should decode the data with.
So I thought something like that would do the job :
func returnType<T>() -> T.Type where T : Decodable {
switch self {
case .popular:
return Popular.self
// ...
}
}
But I can't make it work, it says :
Cannot convert return expression of type 'Popular.Type' to return type 'T.Type'
Asking me to force cast in T.Type.
How can I make a function that returns the decodable so that type can be handled but the JSONDecoder's decode function ?
Thanks.
What you're asking is straightforward, but it probably isn't what you want. What you're asking to do is to return a type. There's nothing generic about that.
func returnType<T>() -> T.Type where T : Decodable {
This syntax defines a type parameter, T, that is passed by the caller. It's not defined by your function. That means the caller may pass any type that is Decodable and your function will return it. For example, the caller can set T to be Int (since that's Decodable), and you will return Int.Type. That's easy to implement (return T.self), but not what you mean.
What you mean is that the function returns some type that is Decodable that the function knows, but the caller doesn't:
func returnType() -> Decodable.Type { ... }
This will work fine, and do exactly what you are asking for, but it suggests you're probably building this network stack incorrectly and will have headaches later.
The reason this approach is likely to be a problem is that you probably want to write a line of code like this:
let result = JSONDecoder().decode(networkType.returnType(), from: data)
That's going to break, because Decodable.Type is not itself a Decodable type. (You you decode Int, but you can't decode the type of Int.) Say it did work. What type would result be? What could you do with it? The only thing you'd know about it is that it's Decodable (and you've already decoded it).
You likely want something more like Vasu Chand's implementation, or the similar approach discussed in my blog series.
You can use escaping closure for your returning result of an API Call.
Assuming you are hitting a get request . A simple working example for passing Codable model for get request api.
class func GETRequest<ResponseType :Decodable>(url : URL,responseType : ResponseType.Type ,completion: #escaping (ResponseType? ,Error? ) -> Void){
var request = URLRequest(url: url)
request.httpMethod = "GET"
let task = URLSession.shared.dataTask(with: request) { (data, response, error) in
guard let data = data else{
completion(nil,error)
return
}
let decoder = JSONDecoder()
do{
let responseData = try decoder.decode(ResponseType.self, from: data)
completion(responseData, nil)
}
catch let error{
completion(nil, error)
}
}
task.resume()
}
How to call this network function.
Network.GETRequest(url: url, responseType: Model.self) { (model, error) in
completion(model,error)
}
Model class contains
struct Model : Codable{
}
You can pass any response model for any get request to network class .
Similarly you can build api network for post request where request body is simply Codable model .
For sorry you can't as according to your need the supply for the first parameter here
JSONDecoder().decode(AdecodableType.self,from:data)
need to be inferred right when you write the code so it can't be Any 1 from a collection of types that conform to Decodable
I have a class that contains some Alamofire code to get JSON from a server, convert it into a pre-defined model and then return that model as an array.
Here is the code
func GetLights(completionHandler: #escaping (DataResponse<[LightList]>) -> Void) -> Alamofire.DataRequest {
return AF.request(APIString + "/lights").responseJSON { response in
let LightListResponse = response.flatMap { json in
try JSONDecoder().decode([LightList].self, from: response.data!)
}
completionHandler(LightListResponse)
}
}
func GetLightList() {
GetLights { response in
if let lights = response.value {
print(lights)
}
}
}
I can breakpoint through to the JSONDecoder and see the json via debug but the print line at the end prints nothing, it doesn't even hit a breakpoint.
Can anyone see what I'm doing wrong? I think I'm using the completion handler correctly?
I am calling the GetLightList via a SwiftUI file like so:
func InitList() {
let requests = Requests()
requests.GetLightList()
}
You shouldn't be doing this using responseJSON, as that method has already parsed the JSON using JSONSerialization and made it available to you as part of the response. Instead, you should use responseDecodable, since you already have a Decodable type.
return AF.request(apiString + "/lights").responseDecodable(of: [LightList].self) { response in
completionHandler(response)
}
However, it's often best not to expose the DataResponse type produced by Alamofire but instead use the Result from the response in your completion handler.
Additionally, updating your styling to match Swift's recommended style will help you write consistent code. Namely, methods and variable names should start with a lowercase letter to separate them from type declarations. You can see this in your code samples where it thinks things like "APIString" are types and not variables.
Finally, it's often helpful to not overload get as a method prefix. For network calls I like using fetch when requesting a resource. e.g. fetchLights.