Parse Codable classes and avoid repetition - swift

I have a JSON response as the following:
{
"http_status": 200,
"success": true,
"has_error": false,
"error": [
""
],
"response": {
"token": "",
"verified": false,
"message": ""
}
}
As far as i can say for an app API usage http_status, success, has_error, error are shared between all APIS and thus i will create a Codable class to handle it, but the response could be different model, so here is what I'm trying to do.
I have created a general response class as the below, so this class i can use for all apis in the project to avoid duplication of same class but different names:
class GeneralResponse:Codable {
let http_status: Int?
let success, has_error: Bool?
let error: [String]?
enum CodingKeys: String, CodingKey {
case http_status = "http_status"
case success = "success"
case has_error = "has_error"
case error = "error"
}
init(http_status: Int?, success: Bool?, has_error: Bool?,error: [String]?) {
self.http_status = http_status
self.success = success
self.has_error = has_error
self.error = error
}
}
Now i have created the response class which will handle for now the registration response:
class RegistrationResponseDetails: Codable {
let token: String?
let verified: Bool?
let message: String?
init(token: String?, verified: Bool?, message: String?) {
self.token = token
self.verified = verified
self.message = message
}
}
And lets say i need to parse the registration the response so here is what i did, i have created a class and used both of them:
class RegistrationResponse: Codable {
let generalResponse:GeneralResponse?
let response: RegistrationResponseDetails?
init(generalResponse: GeneralResponse?, response: RegistrationResponseDetails?) {
self.generalResponse = generalResponse
self.response = response
}
}
So i will mainly use RegistrationResponse to parse the response which will parse "generalResponse" which includes http_status, success, has_error, error, and then response will parse the desired response object.
But at some point generalResponse object is always nil and response has the data parsed correctly, what should i do to make generalResponse get parsed without duplication in each api, because in each api i will have generalResponse object so is it possible to solve it ?
Note: I'm using Alamofire as the networking library.

You can make your GeneralResponse generic and tell it what type to use when parsing the response:
class GeneralResponse<T: Codable>: Codable {
let http_status: Int?
let success, has_error: Bool?
let error: [String]?
let response: T?
}
class RegistrationResponseDetails: Codable {
let token: String?
let verified: Bool?
let message: String?
}
Then you can give it the inner response class when you parse the json:
let generalResponse = try JSONDecoder().decode(GeneralResponse<RegistrationResponseDetails>.self, from: jsonData)
// generalResponse.response is of type RegistrationResponseDetails?

First of all if
http_status, success, has_error, error are shared between all APIS
why are the class properties optional?
If the mentioned keys are the same but the value for key response is different use generics.
In most cases structs are sufficient.
struct JSONParser<T : Decodable> {
struct Response<U : Decodable> : Decodable {
let httpStatus: Int
let success, hasError: Bool
let error: [String]
let response : U
}
let responseData : Response<T>
init(data: Data) throws {
let decoder = JSONDecoder()
decoder.keyDecodingStrategy = .convertFromSnakeCase
responseData = try decoder.decode(Response.self, from: data)
}
}
Then create the different structs e.g.
struct RegistrationResponseDetails : Decodable {
let token: String
let verified: Bool
let message: String
}
and parse it
let parser = try JSONParser<RegistrationResponseDetails>(data: data)
let registrationResponseDetails = parser.responseData.response

For a simple case
class Main:Decodable {
let name:String? // insert all shared vars
}
class Sub:Main {
let id:String?
}
This will parse
{
"name" : "rr" ,
"id" : "oo"
}

Related

Dynamic responseDecodable to handle responses from Alamofire

Please help me, I am writing a class for sending requests to the API. And I ran into the problem of dynamic substitution of the necessary structures for different answers.
I run into an error with different data types. How can this problem be solved?
open class Api {
***
func data (_ method: Methods, _ parameters: (String, Any) ..., callback: #escaping (Any) -> Void) {
***
var ofDecodable = [String: Any]()
ofDecodable = [
"auth.logout": saveResponse.self,
"playlist.get": dataResponse.self
]
let of = ofDecodable[_method].self
AF.request(url!, method: .post, parameters: parameters) // Generic parameter 'T' could not be inferred
.responseDecodable(of: of) { response in // Error: Cannot convert value of type 'Any?' to expected argument type 'T.Type'
switch response.result {
case let .success(data):
print(data)
callback(data)
case let .failure(error):
print(error)
}
}
}
}
enum Methods: String {
case auth_logout = "auth.logout"
case playlist_get = "playlist.get"
}
public struct saveResponse: Codable {
let response: Response
struct Response: Codable {
let save: String
}
}
public struct dataResponse: Codable {
let response: Response
struct Response: Codable {
let id: String?
let name: String?
}
}

Getting error : - The data couldn’t be read because it isn’t in the correct format in Swift using Alamofire and Codable

I am calling api using alamofire and codable . And i am getting api response succesfully. But when i am getting extra parameter in this api i am getting error like "The data couldn’t be read because it isn’t in the correct format".
My code is here
My Model class is:
import Foundation
struct SingleUserProfile: Codable {
let result: [SingleProfileResult]?
let success: Int?
let message: String?
enum CodingKeys: String, CodingKey {
case result = "Result"
case success = "Success"
case message = "Message"
}
}
struct SingleProfileResult: Codable {
let userID: Int?
let firstName: String?
let location: String?
let state, about: String?
let totalScore: Int?
let sun, rising, moon: String?
let ego, communication, birthStar, sexual: Int?
let intellectual, temperament, emotional, healthGenetic: Int?
let profilePic: String?
let numberOfPhotos: Int?
let imagelist: [UserImagelist]?
let verified: Int?
let dateOfBirth, timeOfBirth: String?
let age, isLastPage: Int?
enum CodingKeys: String, CodingKey {
case userID = "User_Id"
case firstName = "First_Name"
case location = "Location"
case state = "State"
case about = "About"
case totalScore = "TotalScore"
case sun
case rising = "Rising"
case moon = "Moon"
case ego = "Ego"
case communication = "Communication"
case birthStar = "Birth_star"
case sexual = "Sexual"
case intellectual = "Intellectual"
case temperament = "Temperament"
case emotional = "Emotional"
case healthGenetic = "Health_Genetic"
case profilePic = "Profile_Pic"
case numberOfPhotos = "NumberOfPhotos"
case imagelist, verified
case dateOfBirth = "DateOfBirth"
case timeOfBirth = "TimeOfBirth"
case age = "Age"
case isLastPage = "IsLastPage"
}
}
struct UserImagelist: Codable {
let userID: Int?
let imagePath, photoID: String?
enum CodingKeys: String, CodingKey {
case userID = "User_Id"
case imagePath = "Image_path"
case photoID = "Photo_Id"
}
var getFullURL : String{
return BASE_URL + (imagePath ?? "")
}
}
In my Constant class
let SINGLE_USER_PROFILE_DETAILS = URL_BASE + GET_USER_PROFILELIST
typealias singleUserProfileDetailsCompletion = (SingleUserProfile?) -> Void
In my Api Request class
//MARK:- Get profile details for
func getSingleUseProfileDetails(currentUserId: Int, accessToken: String, completion: #escaping singleUserProfileDetailsCompletion) {
guard let url = URL(string: "\(SINGLE_USER_PROFILE_DETAILS)?UserID=\(currentUserId)") else{return}
var urlRequest = URLRequest(url: url)
urlRequest.httpMethod = HTTPMethod.get.rawValue
urlRequest.addValue(accessToken, forHTTPHeaderField: "Authorization")
Alamofire.request(urlRequest).responseJSON { (response) in
if let error = response.result.error {
debugPrint(error)
completion(nil)
return
}
guard let data = response.data else { return completion(nil)}
Common.sharedInstance().printURLRequestParameter(url: url, urlRequest: urlRequest, accessToken: accessToken)
Common.sharedInstance().printRequestOutput(data: data)
let jsonDecoder = JSONDecoder()
do {
let person = try jsonDecoder.decode(SingleUserProfile.self, from: data)
completion(person)
} catch {
debugPrint(error)
completion(nil)
}
}
}
The code is working fine. But if new extra parameter is coming i am getting error like "The data couldn’t be read because it isn’t in the correct format". How to write the model class, so that if new data will come, it will handle the situation automatically
Please help me to resolve the issue.

How to convert Dictionary to JSON in Vapor 3?

In Vapor 1.5 I used to convert Dictionary to JSON as shown below.
How should I do it in Vapor 3.1?
In docs it says I need to create struct type and conform it to codable protocol.
Is there another method that would enable to convert the existing dictionary without creating new struct?
func makeCustomJSON(clientData: DataFromClientChargeWithCard, paymentID:String,customer: String) throws -> JSON {
var dictionaryOfStrings = [String:String]()
dictionaryOfStrings["BookingState"] = "Active"
dictionaryOfStrings["BookingStateTimeStamp"] = clientData.TimeStampBookingSavedInDB
dictionaryOfStrings["ChangesMadeBy"] = "User"
dictionaryOfStrings["PaymentID"] = paymentID
dictionaryOfStrings["DateAndTime"] = clientData.DateAndTime
dictionaryOfStrings["RatePriceClient"] = clientData.RatePriceClient
dictionaryOfStrings["Locality"] = clientData.Locality
dictionaryOfStrings["StripeCustomerID"] = customer
//some more properties below...
let response:JSON
do {
response = try JSON(node: dictionaryOfStrings)
}catch let error as NSError {
let message = "dictionaryOfStrings can't be converted in JSON"
drop.log.error(message)
logErr.prints(message: message, code: error.code)
throw NSError(domain: "errorDictionary", code: 400, userInfo: ["error" : error.localizedDescription])
}
return response
}
If you have a type like the struct that I have defined here you can simply return it, as long as it conforms to Content.
import Vapor
import FluentSQLite
struct Employee: Codable {
var id: Int?
var name: String
init(name:String) {
self.name = name
}
}
extension Employee: SQLiteModel {}
extension Employee: Migration {}
extension Employee: Parameter {}
extension Employee: Content { }
and then you can simply define a route and return it.
router.get("test") { req in
return Employee(name: "Alan")
}
if you want to use dictionary you can use the JSONSerialization and return a string.
router.get("test2") { req -> String in
let employeeDic: [String : Any] = ["name":"Alan", "age":27]
do {
let data = try JSONSerialization.data(withJSONObject: employeeDic, options: .prettyPrinted)
let jsonString = String(data: data, encoding: .utf8)
return jsonString ?? "FAILED"
}catch{
return "ERROR"
}
}

Parse complex json code

I have the following JSON code and want to parse it in Swift. I use Alamofire to get the JSON and have created a struct for the parsing:
{
"-8802586561990153106-1804221538-5":{
"zug":{
"klasse":"RB",
"nummer":"28721"
},
"ankunft":{
"zeitGeplant":"1804221603",
"zeitAktuell":"1804221603",
"routeGeplant":[
"Wiesbaden Hbf",
"Mainz Hbf"
]
},
"abfahrt":{
"zeitGeplant":"1804221604",
"zeitAktuell":"1804221604",
"routeGeplant":[
"Gro\u00df Gerau",
"Klein Gerau",
"Weiterstadt"
]
}
},
"8464567322535526441-1804221546-15":{
"zug":{
"klasse":"RB",
"nummer":"28724"
},
"ankunft":{
"zeitGeplant":"1804221657",
"zeitAktuell":"1804221708",
"routeGeplant":[
"Aschaffenburg Hbf",
"Mainaschaff"
]
},
"abfahrt":{
"zeitGeplant":"1804221658",
"zeitAktuell":"1804221709",
"routeGeplant":[
"Mainz-Bischofsheim"
]
}
}
}
I have created a struct for this that looks like this:
struct CallResponse: Codable {
struct DirectionTrain: Codable {
struct Train: Codable {
let trainClass: String
let trainNumber: String
}
struct Arrival: Codable {
let line: String
let eta: Date
let ata: Date
let platform: String
let route: [String]
}
struct Departure: Codable {
let line: String
let etd: Date
let atd: Date
let platform: String
let route: [String]
}
}
}
The rest of my code is:
Alamofire.request(url!).responseJSON { response in
switch response.result {
case .success:
let decoder = JSONDecoder()
let parsedResult = try! decoder.decode(CallResponse.self, from: response.data!)
case .failure(let error):
print(error)
}
}
When I run this code the error message is:
Thread 1: Fatal error: 'try!' expression unexpectedly raised an error: Swift.DecodingError.keyNotFound(CodingKeys(stringValue: "train", intValue: nil), Swift.DecodingError.Context(codingPath: [], debugDescription: "No value associated with key CodingKeys(stringValue: \"train\", intValue: nil) (\"train\").", underlyingError: nil))
Can anyone help me find my problem? Thank you for your answers!
The problem is merely that your structs look nothing at all like your JSON!
Your JSON is a dictionary whose keys have names like "-8802586561990153106-1804221538-5" and "8464567322535526441-1804221546-15". But I don't see you declaring any struct that deals with those keys.
Then each of those turns out to be a dictionary with keys like "zug", "ankunft", and "abfahrt". But I don't see you declaring any struct that deals with those keys either.
And then the "zug" has keys "klasse" and "nummer"; you don't have those either.
And so on.
Either your structs must look exactly like your JSON, or else you must define CodingKeys and possibly also implement init(from:) to deal with any differences between your structs and your JSON. I suspect that the keys "-8802586561990153106-1804221538-5" and "8464567322535526441-1804221546-15" are unpredictable, so you will probably have to write init(from:) in order to deal with them.
For example, I was able to decode your JSON like this (I do not really recommend using try!, but we decoded without error and it's just a test):
struct Entry : Codable {
let zug : Zug
let ankunft : AnkunftAbfahrt
let abfahrt : AnkunftAbfahrt
}
struct Zug : Codable {
let klasse : String
let nummer : String
}
struct AnkunftAbfahrt : Codable {
let zeitGeplant : String
let zeitAktuell : String
let routeGeplant : [String]
}
struct Top : Decodable {
var entries = [String:Entry]()
init(from decoder: Decoder) throws {
struct CK : CodingKey {
var stringValue: String
init?(stringValue: String) {
self.stringValue = stringValue
}
var intValue: Int?
init?(intValue: Int) {
return nil
}
}
let con = try! decoder.container(keyedBy: CK.self)
for key in con.allKeys {
self.entries[key.stringValue] =
try! con.decode(Entry.self, forKey: key)
}
}
}
// d is a Data containing your JSON
let result = try! JSONDecoder().decode(Top.self, from: d)

Swift 4 + Alamofire Decodable Json URL format

I have a JSON format which I do not decodable with Alamofire.
Here is my json:
"data":[
{
"id":37,
"status":"A\u00e7\u0131k",
"department":"Muhasebe",
"title":"Y\u00f6netim Panelinden Deneme 4 - Mail Kontrol",
"message":"<p>Y\u00f6netim Panelinden Deneme 4 - Mail Kontrol<br><\/p>",
"file":null,
"created_at":{
"date":"2018-01-13 01:59:49.000000",
"timezone_type":3,
"timezone":"UTC"
},
"replies":[
{
"id":6,
"ticket_id":37,
"admin_id":null,
"user_id":8593,
"message":"<p>test<\/p>",
"file":"uploads\/tickets\/8593-P87wd8\/GFV6H5M94y5Pt27YAxZxHNRcVyFjD554i80og3xk.png",
"created_at":"2018-01-18 11:16:55",
"updated_at":"2018-01-18 11:16:55"
}
]
},
Here is my model for the JSON:
struct TeknikDestek : Decodable {
var id: Int?
var status: String?
var title: String?
var department: String?
var message: String?
var replies: [Replies]?
}
struct Replies: Decodable {
var replyid: Int?
var ticket_id: Int?
var admin_id: Int?
var user_id: Int?
var message: String?
}
I called it Alamofire, but it does not come back when I do response.data.
Alamofire.request("https://myurl.com.tr/api/tickets/\(userid)").responseJSON { (response) in
switch response.result {
case .success:
if((response.result) != nil) {
let jsonData = response.data
print("jsonData: \(test)")
do{
self.allReplies = try JSONDecoder().decode([TeknikDestek].self, from: jsonData!)
print(self.allReplies)
for reply in self.allReplies {
print("Reply: \(reply)")
}
}catch {
print("Error: \(error)")
}
self.view.dismissNavBarActivity()
}
case .failure(let error):
print(error)
}
}
This is the error console:
How can I make it work? I've spent several hours now but without success. Please help me. Many Thanks.
The question is not related to Alamofire. It's only related to JSONDecoder / Decodable
You need an umbrella struct for the root object, which is a dictionary containing the data key, not an array. That's what the error message states.
struct Root : Decodable {
let data : [TeknikDestek]
}
Then decode Root
let root = try JSONDecoder().decode(Root.self, from: jsonData!)
and get the replies with
self.allReplies = root.data.first?.replies // returns `nil` if data is empty
Note: It's highly recommended to name data structs in singular form (e.g. Reply), semantically you have a collection of singular items