Decode data of Object type and generic type for base JSON response using Decodable - swift

I have a base model -
struct BaseModel<T:Decodable>: Decodable {
let jsonData: [T]?
let status: Bool?
let message: String?
enum CodingKeys: String, CodingKey {
case jsonData = "data"
case status = "success"
case message
}
init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
do {
if let string = try container.decodeIfPresent(T.self, forKey: .jsonData) {
print(string)
jsonData = [string]
} else {
jsonData = nil
}
} catch DecodingError.typeMismatch {
jsonData = try container.decodeIfPresent([T].self, forKey: .jsonData)
}
status = try container.decodeIfPresent(Bool.self, forKey: .status)
message = try container.decodeIfPresent(String.self, forKey: .message)
}
}
I am getting response in two types under jsonData
Object
Array
Getting error while decoding if I receive response as Object. And if I choose let jsonData: T?, Then getting issue in decoding of Array response.
I am using this model in my Network Model. That looks like -
func performOperation<T:Decodable>(urlEndPoint: String, method: HTTPMethod, param: Parameters?, isJsonAvailable: Bool, completion: #escaping(_ response: T?, [T]?, String?, Bool?) ->Void) {
AF.request(urlEndPoint, method: method, parameters: param, headers: header).validate(statusCode: 200..<500).responseDecodable(of: BaseModel<T>.self, decoder: decoder) { (response) in
}
Json response in case of Object -
{
"success": true,
"data": {
"heading": "Same text 1",
"title": "Sample Text 2",
"content": "Sample text 3"
},
"message": "Api response received"
}
Json response in case of ArrayList -
{
"success": true,
"data": [
{
"id": 1,
"name": "Home"
},
{
"id": 2,
"name": "Profile"
}
],
"message": "Menu List"
}

You don't need a generic structure. Just create a optional property to assign your object in case there is no user array:
struct BaseModel {
let data: [User]
let post: Post?
let success: Bool
let message: String
}
struct User: Codable {
let id: Int
let name: String
}
struct Post: Codable {
let heading: String
let title: String
let content: String
}
extension BaseModel: Codable {
init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
do {
data = try container.decode([User].self, forKey: .data)
post = nil
} catch DecodingError.typeMismatch {
data = []
post = try container.decode(Post.self, forKey: .data)
}
success = try container.decode(Bool.self, forKey: .success)
message = try container.decode(String.self, forKey: .message)
}
}
If there is other responses not shown in your post you can do the same approach above using a generic structure as well:
struct BaseModel<T: Codable> {
let array: [T]
let element: T?
let success: Bool
let message: String
}
extension BaseModel: Codable {
init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
do {
array = try container.decode([T].self, forKey: .array)
element = nil
} catch DecodingError.typeMismatch {
array = []
element = try container.decode(T.self, forKey: .array)
}
success = try container.decode(Bool.self, forKey: .success)
message = try container.decode(String.self, forKey: .message)
}
}

Related

How to get any type array field from alamofire response?

I have such field in my json response:
"title": [2402, "Dr.", "Prof.", "Prof. Dr.", "HM"]
And I would like to parse it. I have my model class:
struct AppDataModel:Decodable {
...
let title = Dictionary<String,Any>()
enum CodingKeys: String,CodingKey{
case title
...
}
...
}
As you can see I tried to use Dictionary<String,Any>() for it. And also I thought about array of Any -> [Any] but I usually get such error:
Type 'AppDataModel' does not conform to protocol 'Decodable'
I think that I have to process it like an ordinary json. But I didn't find such data type in Swift, only dictionary. So, maybe someone knows how to process such response fields?
This is an example to decode a single key as heterogenous array
let jsonString = """
{"title": [2402, "Dr.", "Prof.", "Prof. Dr.", "HM"], "name":"Foo"}
"""
struct AppDataModel : Decodable {
let titles : [String]
let name : String
private enum CodingKeys: String, CodingKey { case title, name }
init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
var titlesContainer = try container.nestedUnkeyedContainer(forKey: .title)
var titleArray = [String]()
let _ = try titlesContainer.decode(Int.self) // decode and drop the leading integer
while !titlesContainer.isAtEnd { // decode all following strings
titleArray.append(try titlesContainer.decode(String.self))
}
titles = titleArray
self.name = try container.decode(String.self, forKey: .name)
}
}
let data = Data(jsonString.utf8)
do {
let result = try JSONDecoder().decode(AppDataModel.self, from: data)
print(result)
} catch {
print(error)
}
struct AppDataModel: Codable {
let title: [Title]?
}
extension AppDataModel {
init(data: Data) throws {
self = try newJSONDecoder().decode(AppDataModel.self, from: data)
}
init(_ json: String, using encoding: String.Encoding = .utf8) throws {
guard let data = json.data(using: encoding) else {
throw NSError(domain: "JSONDecoding", code: 0, userInfo: nil)
}
try self.init(data: data)
}
init(fromURL url: URL) throws {
try self.init(data: try Data(contentsOf: url))
}
func with(
title: [Title]?? = nil
) -> AppDataModel {
return AppDataModel(
title: title ?? self.title
)
}
func jsonData() throws -> Data {
return try newJSONEncoder().encode(self)
}
func jsonString(encoding: String.Encoding = .utf8) throws -> String? {
return String(data: try self.jsonData(), encoding: encoding)
}
}
enum Title: Codable {
case integer(Int)
case string(String)
init(from decoder: Decoder) throws {
let container = try decoder.singleValueContainer()
if let x = try? container.decode(Int.self) {
self = .integer(x)
return
}
if let x = try? container.decode(String.self) {
self = .string(x)
return
}
throw DecodingError.typeMismatch(Title.self, DecodingError.Context(codingPath: decoder.codingPath, debugDescription: "Wrong type for Title"))
}
func encode(to encoder: Encoder) throws {
var container = encoder.singleValueContainer()
switch self {
case .integer(let x):
try container.encode(x)
case .string(let x):
try container.encode(x)
}
}
}
func newJSONDecoder() -> JSONDecoder {
let decoder = JSONDecoder()
if #available(iOS 10.0, OSX 10.12, tvOS 10.0, watchOS 3.0, *) {
decoder.dateDecodingStrategy = .iso8601
}
return decoder
}
func newJSONEncoder() -> JSONEncoder {
let encoder = JSONEncoder()
if #available(iOS 10.0, OSX 10.12, tvOS 10.0, watchOS 3.0, *) {
encoder.dateEncodingStrategy = .iso8601
}
return encoder
}
//use
do {
let appDataModel = try AppDataModel(json)
}
catch{
//handle error
}

How to handle two different JSON responses with Alamofire

I am making a login functionality in a SwiftUI app.
When the login in successful the JSON response is:
{
"user_id": 41,
"token": "Token",
"token_type": "bearer",
"expires_in": 12096000
}
When the login is failed the JSON response is:
{
"message": "this is a failure message"
}
I made two different structs to encode the responses
struct LoginResponseModelFailure:Codable {
let message:String
}
struct LoginResponseModelSuccess:Codable{
let user_id: Int
let token: String
let token_type : String
let expires_in: Int
}
Do I need to merge these two structs to single one? if so how to do that?
How can I handle two different responses using alamofire or urlSession?
To avoid optionals my suggestion is to declare the root object as enum with associated values
enum Response : Decodable {
case success(ResponseSuccess)
case failure(ResponseFailure)
init(from decoder: Decoder) throws {
let container = try decoder.singleValueContainer()
do {
self = .success(try container.decode(ResponseSuccess.self))
} catch {
self = .failure(try container.decode(ResponseFailure.self))
}
}
}
The other structs can remain as they are except the names became camelCase
struct ResponseFailure : Decodable {
let message : String
}
struct ResponseSuccess : Decodable {
let userId : Int
let token : String
let tokenType : String
let expiresIn : Int
}
To decode the data switch on the result, data represents the received raw data. The key decoding strategy is added to handle the snake_case keys
do {
let decoder = JSONDecoder()
decoder.keyDecodingStrategy = .convertFromSnakeCase
let result = try decoder.decode(Response.self, from: data)
switch result {
case .success(let data): print(data)
case .failure(let error): print(error)
}
} catch {
print(error)
}
As mentioned in the comment, one way is to create one Struct but then you have to make all the properties Optional as below,
struct LoginResponse: Codable {
let user_id: Int?
let token: String?
let token_type : String?
let expires_in: Int?
let message: String?
}
So now you have to deal with all the optionals.
Another way that seems more appropriate is to introduce another Struct that holds success and failure but you have to implement the init(from decoder: Decoder) method as below,
struct LoginFailure: Codable {
let message:String
}
struct LoginSuccess: Codable {
let user_id: Int
let token: String
let token_type : String
let expires_in: Int
}
struct LoginRespone: Codable {
var data: LoginSuccess?
var message: LoginFailure?
init(from decoder: Decoder) throws {
let container = try decoder.singleValueContainer()
if let data = try? container.decode(LoginSuccess.self) {
self.data = data
} else {
self.message = try container.decode(LoginFailure.self)
}
}
}
let success = """
{
"user_id": 41,
"token": "Token",
"token_type": "bearer",
"expires_in": 12096000
}
""".data(using: .utf8)!
let failure = """
{
"message": "this is a failure message"
}
""".data(using: .utf8)!
do {
let r = try JSONDecoder().decode(LoginRespone.self, from: failure)
print(r.message?.message)
} catch {
print(error)
}
You can use one structure to handle the response. Based on status code, you can differentaite the response.
struct LoginModel:Codable {
let message:String
let userId: Int
let token: String
let tokenType : String
let expiresIn: Int
enum CodingKeys : String,CodingKey {
case message, token
case userId = "user_id"
case tokenType = "token_type"
case expiresIn = "expires_in"
}
init(from decoder: Decoder) throws {
let value = try decoder.container(keyedBy: CodingKeys.self)
self.message = try value.decodeIfPresent(String.self, forKey: .message) ?? ""
self.userId = try value.decodeIfPresent(Int.self, forKey: .userId) ?? 0
self.token = try value.decodeIfPresent(String.self, forKey: .token) ?? ""
self.tokenType = try value.decodeIfPresent(String.self, forKey: .tokenType) ?? ""
self.expiresIn = try value.decodeIfPresent(Int.self, forKey: .expiresIn) ?? 0
}
}

Codable for mapping different key-values with Single Model

JSON 1:
{
"error_code" : 100,
"error_message" : "Something went wrong"
}
JSON 2
{
"failure_code" : 100,
"failure_message" : "Something not right here"
}
Change to below code to map above JSON:
class Failure: Codable {
var code: Int?
var message: String?
private enum CodingKeys: String, CodingKey {
case code
case message
}
}
How can we do it?
Here is one way you could do it:
import Cocoa
import Foundation
struct CustomError {
var code: Int
var message: String
}
extension CustomError : Decodable {
private enum FirstKeys: String, CodingKey {
case error_code, error_message
}
private enum SecondKeys: String, CodingKey {
case failure_code, failure_message
}
init(from decoder: Decoder) throws {
do {
print("Case 1")
let container = try decoder.container(keyedBy: FirstKeys.self)
code = try container.decode(Int.self, forKey: .error_code)
message = try container.decode(String.self, forKey: .error_message)
print("Error with code: \(code) and message: \(message)")
} catch {
print("Case 2")
let container = try decoder.container(keyedBy: SecondKeys.self)
code = try container.decode(Int.self, forKey: .failure_code)
message = try container.decode(String.self, forKey: .failure_message)
print("Error with code: \(code) and message: \(message)")
}
}
}
let json = """
{
"failure_code": 1,
"failure_message": "test"
}
""".data(using: .utf8)!
let error = try JSONDecoder().decode(CustomError.self, from: json)
print(error)
Sent from my iPhone
Write a custom init method which handles the cases. A class is not needed.
struct Failure: Decodable {
var code: Int
var message: String
private enum CodingKeys: String, CodingKey {
case error_code, error_message
case failure_code, failure_message
}
init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
do {
code = try container.decode(Int.self, forKey: .error_code)
message = try container.decode(String.self, forKey: .error_message)
} catch DecodingError.keyNotFound {
code = try container.decode(Int.self, forKey: .failure_code)
message = try container.decode(String.self, forKey: .failure_message)
}
}
}

How can I make a Decodable object from a dictionary?

I want to have a Struct that can be instantiated via normal Codable protocol or a Dictionary (Existing code requires the Dictionary instantiation).
I have this code in a playground, but I'm not sure what to do in my 2nd init that takes a Dictionary. How do I make a Decoder object from a dictionary?
import Foundation
public protocol Parsable: Decodable {
init(dict: [String: Any]) throws
}
struct LinkModel: Parsable {
var href: String
var text: String
init(dict: [String: Any]) throws {
href = "/store/options.aspx"
text = "Buy"
}
}
struct ResponseModel: Parsable {
var link: LinkModel?
let showCell: Bool
enum CodingKeys : String, CodingKey {
case link
case showCell = "show"
}
init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
let linkResponses = try container.decode([LinkModel].self, forKey: .link)
link = linkResponses.first
showCell = try container.decode(Bool.self, forKey: .showCell)
}
init(dict: [String: Any]) throws {
let jsonData = try JSONSerialization.data(withJSONObject: dict, options: [])
let decoder = ??? // what do I do here?
self.init(from: decoder)
}
}
let jsonText = """
{
"show": true,
"link": [
{
"text": "Buy",
"href": "/store/options.aspx"
}
]
}
"""
// test standard Decodable instantiation
let jsonData = jsonText.data(using: .utf8)!
let model = try! JSONDecoder().decode(ResponseModel.self, from: jsonData)
print(model.link?.href)
// test dictionary instantiation
...
Extend your Parsable protocol to automatically generate the initializer you're looking for.
extension Parsable {
init(dict: [String: Any]) throws {
let jsonData = try JSONSerialization.data(withJSONObject: dict, options: [])
let decoder = JSONDecoder()
self = try decoder.decode(Self.self, from: jsonData)
}
}
You're on the right path.
import Foundation
public protocol Parsable: Decodable {
init(dict: [String: Any]) throws
}
struct LinkModel: Parsable {
var href: String
var text: String
init(dict: [String: Any]) throws {
href = "/store/options.aspx"
text = "Buy"
}
}
struct ResponseModel: Parsable {
var link: LinkModel?
let showCell: Bool
enum CodingKeys : String, CodingKey {
case link
case showCell = "show"
}
init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
let linkResponses = try container.decode([LinkModel].self, forKey: .link)
link = linkResponses.first
showCell = try container.decode(Bool.self, forKey: .showCell)
}
init(dict: [String: Any]) throws {
let jsonData = try JSONSerialization.data(withJSONObject: dict, options: [])
// 1.
let decoder = JSONDecoder()
// 2.
let result = try decoder.decode(ResponseModel.self, from: jsonData)
// 3.
self = result
}
}
let jsonText = """
{
"show": true,
"link": [
{
"text": "Buy",
"href": "/store/options.aspx"
}
]
}
"""
// test standard Decodable instantiation
let jsonData = jsonText.data(using: .utf8)!
let model = try! JSONDecoder().decode(ResponseModel.self, from: jsonData)
print(model.link?.href)
All I did was:
create a JSONdecoder object.
use that JSONdecoder to decode an object of type ResponseModel
assign the result of the decoding to self. This way all properties of self are assigned.

Decoding dynamic JSON structure in swift 4

I have the following issue that I'm not sure how to handle.
My JSON response can look like this:
{
"data": {
"id": 7,
"token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHAiOjE1NDY1MTU0NDMsImRhdGEiOiJ2bGFkVGVzdCIsImlhdCI6MTU0NjUwODI0M30.uwuPhlnchgBG4E8IvHvK4bB1Yj-TNDgmi7wUAiKmoVo"
},
"error": null
}
Or like this:
{
"data": [{
"id": 12
}, {
"id": 2
}, {
"id": 5
}, {
"id": 7
}],
"error": null
}
So in short the data can be either a single objet or an Array. What i have is this:
struct ApiData: Decodable {
var data: DataObject?
var error: String?
}
struct DataObject: Decodable {
var userId: Int?
enum CodingKeys: String, CodingKey {
case userId = "id"
}
}
This works fine for the first use case, but it will fail once data turns into
var data: [DataObject?]
How do I make that dynamic without duplicating code?
Edit: This is how i decode the object as well
func makeDataTaskWith(with urlRequest: URLRequest, completion: #escaping(_ apiData: ApiData?) -> ()) {
let config = URLSessionConfiguration.default
let session = URLSession(configuration: config)
session.dataTask(with: urlRequest) {
(data, response, error) in
guard let _ = response, let data = data else {return}
if let responseCode = response as? HTTPURLResponse {
print("Response has status code: \(responseCode.statusCode)")
}
do {
let retreived = try NetworkManager.shared.decoder.decode(ApiData.self, from: data)
completion(retreived)
} catch let decodeError as NSError {
print("Decoder error: \(decodeError.localizedDescription)\n")
return
}
}.resume()
}
If data can be a single object or an array write a custom initializer which decodes first an array, if a type mismatch error occurs decode a single object. data is declared as an array anyway.
As token appears only in a single object the property is declared as optional.
struct ApiData: Decodable {
let data : [DataObject]
let error : String?
private enum CodingKeys : String, CodingKey { case data, error }
init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
do {
data = try container.decode([DataObject].self, forKey: .data)
} catch DecodingError.typeMismatch {
data = [try container.decode(DataObject.self, forKey: .data)]
}
error = try container.decodeIfPresent(String.self, forKey: .error)
}
}
struct DataObject: Decodable {
let userId : Int
let token : String?
private enum CodingKeys: String, CodingKey { case userId = "id", token }
}
Edit: Your code to receive the data can be improved. You should add a better error handling to return also all possible errors:
func makeDataTaskWith(with urlRequest: URLRequest, completion: #escaping(ApiData?, Error?) -> Void) {
let config = URLSessionConfiguration.default
let session = URLSession(configuration: config)
session.dataTask(with: urlRequest) {
(data, response, error) in
if let error = error { completion(nil, error); return }
if let responseCode = response as? HTTPURLResponse {
print("Response has status code: \(responseCode.statusCode)")
}
do {
let retreived = try NetworkManager.shared.decoder.decode(ApiData.self, from: data!)
completion(retreived, nil)
} catch {
print("Decoder error: ", error)
completion(nil, error)
}
}.resume()
}
Using power of generic, it simple like below:
struct ApiData<T: Decodable>: Decodable {
var data: T?
var error: String?
}
struct DataObject: Decodable {
private var id: Int?
var userId:Int? {
return id
}
}
Use
if let obj = try? NetworkManager.shared.decoder.decode(ApiData<DataObject>.self, from: data) {
//Do somthing
} else if let array = try NetworkManager.shared.decoder.decode(ApiData<[DataObject]>.self, from: data) {
// Do somthing
}
If you have only two possible outcomes for your data, an option would be to try and parse data to one of the expected types, if that fails you know that the data is of other type and you can then handle it accordingly.
See this
You can try
struct Root: Codable {
let data: DataUnion
let error: String?
}
enum DataUnion: Codable {
case dataClass(DataClass)
case datumArray([Datum])
init(from decoder: Decoder) throws {
let container = try decoder.singleValueContainer()
if let x = try? container.decode([Datum].self) {
self = .datumArray(x)
return
}
if let x = try? container.decode(DataClass.self) {
self = .dataClass(x)
return
}
throw DecodingError.typeMismatch(DataUnion.self, DecodingError.Context(codingPath: decoder.codingPath, debugDescription: "Wrong type for DataUnion"))
}
func encode(to encoder: Encoder) throws {
var container = encoder.singleValueContainer()
switch self {
case .dataClass(let x):
try container.encode(x)
case .datumArray(let x):
try container.encode(x)
}
}
}
struct Datum: Codable {
let id: Int
}
struct DataClass: Codable {
let id: Int
let token: String
}
let res = try? JSONDecoder().decode(Root.self, from:data)