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)
}
}
}
Related
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
}
}
I am trying to decode the error as follows, most of the error that I am handling in array format [String], but in few cases the error is not in array format, just a String.
If error comes in array format name comes as errors, but if it is string format then it comes as error. How could I handle this scenario?
How could I able to handle this scenario?
struct CustomError: Codable {
let errors: [String]
}
private func errorDecoding(data : Data) {
let decoder = JSONDecoder()
do {
let errorData = try decoder.decode(CustomError.self, from: data)
} catch {
// TODO
}
}
You'd have to manually implement init(from:) and try decoding one type, failing that, decode another:
struct CustomError {
let errors: [String]
}
extension CustomError: Decodable {
enum CodingKeys: CodingKey { case errors, error }
init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
do {
self.errors = try container.decode([String].self, forKey: .errors)
} catch DecodingError.typeMismatch,
DecodingError.keyNotFound {
let error = try container.decode(String.self, forKey: .error)
self.errors = [error]
}
}
}
The decoding part is normal:
do {
let error = try JSONDecoder().decode(CustomError.self, from: data)
} catch {
// ..
}
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)
}
}
I am trying to find a clean way to remove data model optional attributes if its nil when custom Encoding/Decoding my data model in Swift.
My use case:
import Foundation
public struct Message {
public let txnID: UUID
public var userId: String?
public var messageID: UUID?
public init(txnID: UUID, userId: String? = nil, messageID: UUID? = nil) {
self.txnID = txnID
self.userId = userId
self.messageID = messageID
}
}
extension Message: Codable {
private enum CodingKeys: CodingKey {
case txnID, userId, messageID
}
public init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
txnID = try container.decode(UUID.self, forKey: .txnID)
// FIXME: Remove `userId, messageID` if `nil`
self.userId = try? container.decode(String.self, forKey: .userId)
self.messageID = try? container.decode(UUID.self, forKey: .messageID)
}
public func encode(to encoder: Encoder) throws {
var container = encoder.container(keyedBy: CodingKeys.self)
try container.encode(self.txnID, forKey: .txnID)
// FIXME: Remove `userId, messageID` if `nil`
try container.encode(self.userId, forKey: .userId)
try container.encode(self.messageID, forKey: .messageID)
}
}
/// The test case I have is basically is:
/// 1. Custom encode and decode my data model using `JSONEncoder` and `JSONDecoder`
/// 2. Remove optional attributes from the resulting encoded/decoded values
let msg = Message(txnID: UUID())
guard let encodedMsg = try? JSONEncoder().encode(msg), let jsonMessage = String(data: encodedMsg, encoding: String.Encoding.utf8) else {
fatalError()
}
// Now decode message
guard let origianlMsg = try? JSONDecoder().decode(Message.self, from: encodedMsg) else {
fatalError()
}
print("Encoded Message to json: \(jsonMessage)")
I am getting the following json when encoding my model
Encoded Message to json: {"txnID":"6211905C-8B72-4E19-81F0-F95F983F08CC","userId":null,"messageID":null}
However, I would like to remove null values from my json for nil values.
Encoded Message to json: {"txnID":"50EFB999-C513-4DD0-BD3F-EEAE3F2304E9"}
I found that decodeIfPresent, encodeIfPresent are used for this use case.
try? is no longer used as well to validate those fields if they're present.
extension Message: Codable {
private enum CodingKeys: CodingKey {
case txnID, userId, messageID
}
public init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
txnID = try container.decode(UUID.self, forKey: .txnID)
self.userId = try container.decodeIfPresent(String.self, forKey: .userId)
self.messageID = try container.decodeIfPresent(UUID.self, forKey: .messageID)
}
public func encode(to encoder: Encoder) throws {
var container = encoder.container(keyedBy: CodingKeys.self)
try container.encode(self.txnID, forKey: .txnID)
try container.encodeIfPresent(self.userId, forKey: .userId)
try container.encodeIfPresent(self.messageID, forKey: .messageID)
}
}
I have a struct with two types, both enum, one of them with different associated values in each enum case (see code). There's any workaround to avoid the switch on UDPCommand? Every case added makes the switch longer and kind of repeated code. And needs to be done on the encoder again. I'm trying to do it with generics but can't make it work. Thanks
struct UDPMessage {
let command: UDPCommand
var data: UDPCommandData
func jsonString() -> String {
let encoder = JSONEncoder()
guard let data = try? encoder.encode(self) else { return "" }
guard let string = String(data: data, encoding: .utf8) else { return "" }
return string
}
}
enum UDPCommand: String, Codable {
case DISCOVER
case FILTER
case TOGGLE
case SWIPE_CHAT
case FORWARD
}
enum UDPCommandData {
case discover(Discover)
case filter(Filter)
case toggle(Toggle)
case swipe(SwipeChat)
case forwardedMessage(ForwardedMessage)
case unkkown
}
extension UDPMessage: Codable {
private enum CodingKeys: String, CodingKey {
case command, data
}
init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
let command = try container.decode(UDPCommand.self, forKey: .command)
switch command {
case .DISCOVER:
let data = try container.decode(Discover.self, forKey: .data)
self.init(command: command, data: UDPCommandData.discover(data))
case .FILTER:
let data = try container.decode(Filter.self, forKey: .data)
self.init(command: command, data: UDPCommandData.filter(data))
case .TOGGLE:
let data = try container.decode(Toggle.self, forKey: .data)
self.init(command: command, data: UDPCommandData.toggle(data))
case .SWIPE_CHAT:
let data = try container.decode(SwipeChat.self, forKey: .data)
self.init(command: command, data: UDPCommandData.swipe(data))
default:
self.init(command: command, data: UDPCommandData.unkkown)
}
}
func encode(to encoder: Encoder) throws {
var container = encoder.container(keyedBy: CodingKeys.self)
try container.encode(self.command, forKey: .command)
try self.data.encode(to: encoder)
}
}