Swift 4 Codable : Common struct for all model - ios

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}
}

Related

Swift 5: Handling and decoding error of JSON

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?
}

Managing Dynamic Keys in response through Codable Protocol

I need to make the codable model for the dynamic keys of dictionary coming from response below is the response I'm getting.
{
"data" : [
{
"desc1" : null,
"file1" : "uploads\/posts\/Aug-2021\/1629271422310452767"
},
{
"desc2" : "hello",
"file2" : "uploads\/posts\/Aug-2021\/162927142279356160WhatsApp+Image+2021-07-02+at+12.09.14+PM.jpeg"
}
],
"status" : "success"
}
This desc1 and file1 is dynamic till like file1, file2 and so on, I need to have codable model for that below is my model that is not supportive.
struct ListModel: Codable {
public var data: [data]?
}
struct data: Codable {
let file : String?
let desc : String?
}
Anything support by codable protocol for that. Thanks in Advance.
You need a custom initializer. Of course this will only work if your json will always come formatted as described:
struct File {
var file: String? = ""
var desc: String? = ""
}
struct Response {
let files: [File]
let status: String
enum CodingKeys: String, CodingKey {
case files = "data", status
}
}
extension Response: Decodable {
init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
self.status = try container.decode(String.self, forKey: .status)
let elements = try container.decode([[String: String?]].self, forKey: .files)
self.files = elements.reduce(into: []) {
var file = File()
for (key, value) in $1 {
if key.hasPrefix("file") {
file.file = value
} else if key.hasPrefix("desc") {
file.desc = value
}
}
$0.append(file)
}
}
}
Playground testing:
let json = """
{
"data" : [
{
"desc1" : null,
"file1" : "uploads/posts/Aug-2021/1629271422310452767"
},
{
"desc2" : "hello",
"file2" : "uploads/posts/Aug-2021/162927142279356160WhatsApp+Image+2021-07-02+at+12.09.14+PM.jpeg"
}
],
"status" : "success"
}
"""
do {
let response = try JSONDecoder().decode(Response.self, from: Data(json.utf8))
print(response)
} catch {
print(error)
}
This will print:
Response(files: [File(file: Optional("uploads/posts/Aug-2021/1629271422310452767"), desc: nil), File(file: Optional("uploads/posts/Aug-2021/162927142279356160WhatsApp+Image+2021-07-02+at+12.09.14+PM.jpeg"), desc: Optional("hello"))], status: "success")

How to use Alamofire with Codable protocol and parse data

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

parsing nested Array using codable in swift 4

I am getting many random issues. Mostly like some structure is not decodable not able to understand how to define structure.
Please find the code snipped
var JSON = """
{"variants":{"variant_groups":[{"group_id":"1","name":"Crust","variations":[{"name":"Thin","price":0,"default":1,"id":"1","inStock":1},{"name":"Thick","price":0,"default":0,"id":"2","inStock":1,"isVeg":1},{"name":"Cheese burst","price":100,"default":0,"id":"3","inStock":1,"isVeg":1}]},{"group_id":"2","name":"Size","variations":[{"name":"Small","price":0,"default":1,"id":"10","inStock":1,"isVeg":0},{"name":"Medium","price":100,"default":0,"id":"11","inStock":1,"isVeg":1},{"name":":Large","price":200,"default":0,"id":"12","inStock":1,"isVeg":0}]},{"group_id":"3","name":"Sauce","variations":[{"name":"Manchurian","price":20,"default":0,"id":"20","inStock":1,"isVeg":0},{"name":"Tomato","price":20,"default":0,"id":"21","inStock":1,"isVeg":1},{"name":"Mustard","price":20,"default":0,"id":"22","inStock":1,"isVeg":0}]}],"exclude_list":[[{"group_id":"1","variation_id":"3"},{"group_id":"2","variation_id":"10"}],[{"group_id":"2","variation_id":"10"},{"group_id":"3","variation_id":"22"}]]}}
""".data(using: .utf8)
/*
not sure is this the right way to define Root
*/
struct Root : Codable {
let variants : varientStruct
let exclude_list : exclude_list
}
struct exclude_list : Codable{
let variation_id : String
let group_id : String
}
struct varientStruct: Codable {
let variant_groups = [variant_groups_struct]
}
struct variant_groups_struct : Codable {
let group_id : String
let name :String
let variations: [variationsStruct]
}
struct variationsStruct :Codable {
let name : String
let price : Int
let selected: Int
let id : String
let inStock: Bool
enum CodingKeys : String, CodingKey {
case name
case price
case selected = "default"
case id
case inStock
}
}
}
do {
let data = Data(person.utf8)
let result = try JSONDecoder().decode(Root.self, from: JSON)
print(result)
} catch {
print(error)
}
First of all and once again, please conform to the naming convention:
struct and class names start with a uppercase letter.
Variable and function names start with a lowercase letter.
All variable and struct / class names are camelCased rather than snake_cased.
Second of all, JSON is very easy to read. There are only two collection types (array [] and dictionary {}) and four value types.
Format the JSON string to be able to recognize the structure more conveniently
let jsonString = """
{"variants":{"variant_groups":[{"group_id":"1","name":"Crust","variations":
[{"name":"Thin","price":0,"default":1,"id":"1","inStock":1},
{"name":"Thick","price":0,"default":0,"id":"2","inStock":1,"isVeg":1},
{"name":"Cheese burst","price":100,"default":0,"id":"3","inStock":1,"isVeg":1}]
},{"group_id":"2","name":"Size","variations":
[{"name":"Small","price":0,"default":1,"id":"10","inStock":1,"isVeg":0},
{"name":"Medium","price":100,"default":0,"id":"11","inStock":1,"isVeg":1},
{"name":":Large","price":200,"default":0,"id":"12","inStock":1,"isVeg":0}]
},{"group_id":"3","name":"Sauce","variations":
[{"name":"Manchurian","price":20,"default":0,"id":"20","inStock":1,"isVeg":0},
{"name":"Tomato","price":20,"default":0,"id":"21","inStock":1,"isVeg":1},
{"name":"Mustard","price":20,"default":0,"id":"22","inStock":1,"isVeg":0}]
}],
"exclude_list":[[{"group_id":"1","variation_id":"3"}, {"group_id":"2","variation_id":"10"}],
[{"group_id":"2","variation_id":"10"},{"group_id":"3","variation_id":"22"}]]
}
}
"""
Then build the structs according to the JSON structure step by step
struct Root : Decodable {
let variants : Variant
}
struct Variant : Decodable {
private enum CodingKeys : String, CodingKey {
case groups = "variant_groups"
case excludeList = "exclude_list"
}
let groups : [VariantGroup]
let excludeList : [[ExcludeList]]
}
struct VariantGroup : Decodable {
private enum CodingKeys : String, CodingKey {
case groupID = "group_id"
case name, variations
}
let groupID : String
let name : String
let variations : [Variation]
}
struct Variation : Decodable {
let name : String
let price : Int
let `default` : Int
let id : String
let inStock : Int
}
struct ExcludeList : Decodable {
private enum CodingKeys : String, CodingKey {
case groupID = "group_id"
case variationID = "variation_id"
}
let groupID : String
let variationID : String
}
Then decode the stuff
do {
let data = Data(jsonString.utf8)
let result = try JSONDecoder().decode(Root.self, from: data)
print(result)
} catch { print(error) }

Parse Complex Nested Data From server

I'm trying to parse data i've received from the server so as to display it into a UIPicker view. but it's too complex for me to parse and get it displayed into UIPIcker View. Whats the best way i can parse the following data into and make it ready for a UIPickerView.
This is the session trying to parse the information from the server
let url = NSURL(string: "http://dummy.com/api")!
let request = URLRequest(url: url as URL)
URLSession.shared.dataTask(with: request) { data, response, error in
if error == nil {
do {
let json = try JSONSerialization.jsonObject(with: data!, options: .mutableContainers) as? NSDictionary
guard let parseJSON = json else{
print("Error While Parsing")
return
}
print(parseJSON)
let responseDic = parseJSON["response"] as? NSDictionary
let utilityCode = responseDic?["utility_code"] as? String
if utilityCode == "AFRICELL" {
let africellPackages = responseDic?["packages"] as! NSArray
print(africellPackages)
}
}catch{
return
}
}
}.resume()
The following data is the response from the server when the GET request is made.
{
"status": "OK",
"response": [
{
"utility_code": "AIRTEL",
"packages": [
{
"package_id": 33,
"package_name": "Daily 10MB",
"package_code": "6000",
"package_price": 300
},
{
"package_id": 34,
"package_name": "Daily 20MB",
"package_code": "6002",
"package_price": 500
},
{
"package_id": 65,
"package_name": "Weekly Roaming 200MB",
"package_code": "6030",
"package_price": 100000
}
]
},
{
"utility_code": "AFRICELL",
"packages": [
{
"package_id": 68,
"package_name": "Daily 10 MB",
"package_code": "5000",
"package_price": 290
}
]
},
{
"utility_code": "SMART",
"packages": [
{
"package_id": 69,
"package_name": "Daily 50 MB",
"package_code": "8000",
"package_price": 500
}
]
},
{
"utility_code": "SMILE",
"packages": [
{
"package_id": 70,
"package_name": "Smile 1GB",
"package_code": "7006",
"package_price": 32000
}
]
}
]
}
Cheers and thanks for the help!
All examples below are provided without error checking for brevity. For production code, you should handle errors properly.
Swift 3 without any external framework
Most of the work to decode JSON manually involves defining the data model:
struct JSONResponse {
var status: String
var response: [Utility]
init(jsonDict: [String: AnyObject]) {
self.status = jsonDict["status"] as! String
self.response = [Utility]()
let response = jsonDict["response"] as! [AnyObject]
for r in response.map({ $0 as! [String: AnyObject] }) {
let utility = Utility(jsonDict: r)
self.response.append(utility)
}
}
}
struct Utility {
var code: String
var packages: [Package]
init(jsonDict: [String: AnyObject]) {
self.code = jsonDict["utility_code"] as! String
self.packages = [Package]()
let packages = jsonDict["packages"] as! [AnyObject]
for p in packages.map({ $0 as! [String: AnyObject] }) {
let package = Package(jsonDict: p)
self.packages.append(package)
}
}
}
struct Package {
var id: Int
var name: String
var code: String
var price: Int
init(jsonDict: [String: AnyObject]) {
self.id = jsonDict["package_id"] as! Int
self.name = jsonDict["package_name"] as! String
self.code = jsonDict["package_code"] as! String
self.price = jsonDict["package_price"] as! Int
}
}
And how to use it:
let jsonDict = try! JSONSerialization.jsonObject(with: data) as! [String: AnyObject]
let jsonResponse = JSONResponse(jsonDict: jsonDict)
let utilities = jsonResponse.response
print(utilities)
Swift 3 with ObjectMapper
Decoding JSON is it of a pain in standard Swift. You need an external JSON framework like ObjectMapper to ease the pain in mapping between the JSON data and your data model. Install ObjectMapper from CocoaPod or Carthage.
First, define your data model in a separate file:
import ObjectMapper
struct JSONResponse : Mappable {
var status: String?
var response: [Utility]?
init?(map: Map) { }
mutating func mapping(map: Map) {
self.status <- map["status"]
self.response <- map["response"]
}
}
struct Utility : Mappable {
var code: String?
var packages: [Package]?
init?(map: Map) { }
mutating func mapping(map: Map) {
self.code <- map["code"]
self.packages <- map["packages"]
}
}
struct Package : Mappable {
var id: Int?
var name: String?
var code: String?
var price: Int?
init?(map: Map) { }
mutating func mapping(map: Map) {
self.id <- map["package_id"]
self.name <- map["package_name"]
self.code <- map["package_code"]
self.price <- map["package_price"]
}
}
Then you can use it to map JSON to your object like this:
// data is what your get in the dataTask's completion block
let jsonString = String(data: data, encoding: .utf8)!
if let jsonResponse = JSONResponse(JSONString: jsonString),
let utilities = jsonResponse.response {
print(utilities)
}
Everything has to be declared optional because you don't know if the value will be in the JSON string or not.
In Swift 4
Things get a lot simpler in Swift 4 with the new Encodable and Decodable protocols. Both combined to form the Encodable protocol (yes, protocol composition is a new thing in Swift 4). You no longer need ObjectMapper in Swift 4.
You still need to define your data model first:
struct JSONResponse : Codable {
var status: String
var response: [Utility]
}
struct Utility : Codable {
var code: String
var packages: [Package]
private enum CodingKeys: String, CodingKey {
case code = "utility_code"
case packages
}
}
struct Package : Codable {
var id: Int
var name: String
var code: String
var price: Int
private enum CodingKeys: String, CodingKey {
case id = "package_id"
case name = "package_name"
case code = "package_code"
case price = "package_price"
}
}
And here's how you use the new JSONDecoder struct in Swift 4:
let jsonRepsonse = try! JSONDecoder().decode(JSONResponse.self, from: data)
let utilities = jsonRepsonse.response
print(utilities)
So what's going on here?
Both ObjectMapper and Codable map the values in JSON to your data model.
In ObjectMapper, you have to explicitly list which property maps to which value in JSON in a function named mapping(map:):
mutating func mapping(map: Map) {
self.id <- map["package_id"]
self.name <- map["package_name"]
self.code <- map["package_code"]
self.price <- map["package_price"]
}
<- is the "mapping operator" defined by ObjectMapper (it's not in the Standard Library).
With Swift 4's Codable, the compiler automates a lot of things for you, which makes it appear magical and confusing at first. Essentially you define your mappings in an enum called CodingKeys:
struct JSONResponse : Codable {
var status: String
var response: [Utility]
// Define your JSON mappings here
private enum CodingKeys: String, CodingKey {
case status = "status"
case response = "response"
}
}
struct Package : Codable {
var id: Int
var name: String
var code: String
var price: Int
// Define your JSON mappings here
private enum CodingKeys: String, CodingKey {
case id = "package_id"
case name = "package_name"
case code = "package_code"
case price = "package_price"
}
}
If your JSON keys are the same as your property name, like in the JSONResponse struct, you don't have to define the explicit value for each case and you can simply write:
struct JSONResponse : Codable {
var status: String
var response: [Utility]
private enum CodingKeys: String, CodingKey {
case status // no explicit value here
case response
}
}
Better yet, since every JSON key is the same as your property name, you can omit the CodingKeys enum all together and let the compiler handle that for you:
// All you need to do is to conform it to Codable
// CodingKeys is generated automatically
struct JSONResponse : Codable {
var status: String
var response: [Utility]
}
To learn more about Codable, watch the What's new in Foundation session from WWDC 2017.

Resources