Using Decodable with inheritance raises an exception - swift

I'm working against Rest API service, where the responses are divided into Base response, and all other responses inherit from it.
I'm trying to building the same structure for my response model classes, using the Decoder interface.
However i'm having issues with the decoding of an inherited class.
I tried to follow this issue:
Using Decodable in Swift 4 with Inheritance
But with no luck.
This is the initial structure:
class LoginResponse: BaseResponse{
var Message: String?
private enum CodingKeys: String, CodingKey{
case Message
}
required init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
Message = try container.decode(String.self, forKey: .Message)
let superDecoder = try container.superDecoder()
try super.init(from: superDecoder)
}
}
class BaseResponse: Decodable {
var Status: Int?
private enum CodingKeys: String, CodingKey{
case Status
}
required init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self) // This line throws the exception
Status = try container.decode(Int.self, forKey: .Status)
}
}
Here's how I'm trying to decode:
let decoder = JSONDecoder()
let json = "{\"Message\":\"saa\",\"Status\":200}"
let login = try! decoder.decode(LoginResponse.self, from: json.data(using: .utf8)!)
As I wrote above, this line throws the exception (in BaseResponse class)
let container = try decoder.container(keyedBy: CodingKeys.self)
Fatal error: 'try!' expression unexpectedly raised an error: Swift.DecodingError.valueNotFound(Swift.KeyedDecodingContainer<SampleProject.BaseResponse.(CodingKeys in _084835F8074C7E8C5E442FE2163A7A00)>, Swift.DecodingError.Context(codingPath: [Foundation.(_JSONKey in _12768CA107A31EF2DCE034FD75B541C9)(stringValue: "super", intValue: nil)], debugDescription: "Cannot get keyed decoding container -- found null value instead.", underlyingError: nil))
Not sure how to deal with it.
Thanks in Advance!

There is no need to use the superDecoder, you can simply do this (I changed the variable names to lowercase to conform to the naming convention)
class LoginResponse: BaseResponse {
let message: String
private enum CodingKeys: String, CodingKey{
case message = "Message"
}
required init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
message = try container.decode(String.self, forKey: .message)
try super.init(from: decoder)
}
}
class BaseResponse: Decodable {
let status: Int
private enum CodingKeys: String, CodingKey{
case status = "Status"
}
required init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
status = try container.decode(Int.self, forKey: .status)
}
}
decoder.decode(BaseResponse.self ... decodes only status
decoder.decode(LoginResponse.self ... decodes status and message
And never en-/decode with try!. Handle the error.

Related

Why doesn't my struct encode in the order I declared properties and coding keys?

This is the main struct that encodes properly as far as when I print print(String(data: encoded, encoding: .utf8) as Any)
the struct prints encoded with mainObject printing first then the rest of the variables in the struct but i want it to print: mainAccount, mainObject, reference in that order.
struct T: Codable {
init(){}
let mainAccount = "IMHOTECHPECOM"
let mainObject = mainObject()
let reference = UUID()
// Enum that allows easy encoding
enum CodingKeys: CodingKey {
case mainAccount, mainObject, reference;
}
// function to conform to encodable protocol
func encode(to encoder: Encoder) throws {
var container = encoder.container(keyedBy: CodingKeys.self)
try container.encode(mainAccount, forKey: .merchantAccount)
try container.encode(mainObject.self, forKey: .mainObject)
try container.encode(reference, forKey: .reference)
}
// conforms with decodable protocol
required init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
_ = try container.decode(String.self, forKey: .mainAccount)
_ = try container.decode(String.self, forKey: .mainObject)
_ = try container.decode(String.self, forKey: .reference)
}
}
This is the mainObject
struct mainObject: Codable {
var type = "kind"
var identifier: String = ""
var guide: String = ""
init(){}
enum CodingKeys: CodingKey{
case type, identifier, guide;
}
// function to conform to encodable protocol
func encode(to encoder: Encoder) throws {
var container = encoder.container(keyedBy: CodingKeys.self)
try container.encode(type, forKey: .type)
try container.encode(identifier, forKey: .identifier)
try container.encode(guide, forKey: .guide)
}
// conforms with decodable protocol
required init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
type = try container.decode(String.self, forKey: .type)
identifier = try container.decode(String.self, forKey: .identifier)
guide = try container.decode(String.self, forKey: .identifier)
}
}
This is the function in the actual view encoding the data from a button press
func getBalance() async {
let encoder = JSONEncoder()
encoder.outputFormatting = [.sortedKeys]
encoder.outputFormatting = [.withoutEscapingSlashes]
encoder.outputFormatting = [.prettyPrinted]
guard let encoded = try? encoder.encode(mainobject) else {
print("Failed to encode")
return
}
let url = URL(string: "https://testurl.com")!
var request = URLRequest(url: url)
request.setValue("application/json", forHTTPHeaderField: "Content-Type")
request.httpMethod = "POST"
do {
let (response, _) = try await URLSession.shared.upload(for: request, from: encoded)
print(String(data: encoded, encoding: .utf8) as Any)
print(String(data: response, encoding: .utf8) as Any)
} catch {
print("Encoding failed")
}
let _ = try? JSONDecoder().decode(mainObject.self, from: encoded)
In this particular case, the order you're asking for happens to match alphabetical order. So if you add .sortedKeys to your encoder's outputFormatting property, you'll get the order you want:
let encoder = JSONEncoder()
encoder.outputFormatting = [.sortedKeys]
let data = try encoder.encode(myT)
This will affect the order of keys in all objects in the JSON. So your T's keys will be output in the order mainAccount, mainObject, reference, and your mainObject's keys will be output in the order guide, identifier, type.
The general answer is that JSONEncoder doesn't remember the order in which you add keys to a keyed container. Internally, it uses a standard Swift Dictionary to store the keys and values. A Swift Dictionary doesn't guarantee any ordering of its keys, and the order can change each time your program is started.
If you want to guarantee that the order of your keys is preserved, you'll have to write your own Encoder implementation, which is not a trivial task.

Codable Encoder required for NSManagedObject subclass?

func encode(to encoder: Encoder) throws is required for the Codable protocol.
Is there any reason to implement this in your code for an NSManagedObject subclass?
Is there something I'm missing, or should a fatal error simply be returned from this function.
The example subclass is the same for all such subclasses
#objc(Commit)
public class Commit: NSManagedObject, Codable {
public func encode(to encoder: Encoder) throws {
// Note encode is blank here
}
required convenience public init(from decoder: Decoder) throws {
// return the context from the decoder userinfo dictionary
guard let contextUserInfoKey = CodingUserInfoKey.context,
let managedObjectContext = decoder.userInfo[contextUserInfoKey] as? NSManagedObjectContext,
let entity = NSEntityDescription.entity(forEntityName: "Commit", in: managedObjectContext)
else {
fatalError("decode failure")
}
// Super init of the NSManagedObject
self.init(entity: entity, insertInto: managedObjectContext)
let values = try decoder.container(keyedBy: CodingKeys.self)
do {
sha = try values.decode(String.self, forKey: .sha)
url = try values.decode(String.self, forKey: .url)
html_url = try values.decode(String.self, forKey: .html_url)
gitcommit = try values.decode(GitCommit.self, forKey: .gitcommit)
} catch {
print ("error")
}
}
enum CodingKeys: String, CodingKey {
case sha = "sha"
case gitcommit = "commit"
case url = "url"
case html_url = "html_url"
}
}
Adopting Codable implies that you want to decode and encode the objects.
If you have no reason to encode the class adopt only Decodable

How to remove data model nil fields from custom encoded/decoded json in Swift

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

How to confirm Codable protocol in structure that have NSError object?

I want to save swift structure object in UserDefaults for that I need to confirm Codable protocol. I am trying this way but getting some errors-:
1) No 'decode' candidates produce the expected contextual result type 'NSError'
2)No 'decode' candidates produce the expected contextual result type 'NSError'
How can solve this?
import Foundation
struct FailedImage: Codable {
let url: String
var downloadAttempt: Int
var error: NSError
enum CodingKeys:String,CodingKey
{
case error
}
init(from decoder: Decoder) throws {
let values = try decoder.container(keyedBy: CodingKeys.self)
error = try values.decode(NSError.self, forKey: .error)
}
func encode(to encoder: Encoder) throws {
var container = encoder.container(keyedBy: CodingKeys.self)
try container.encode(error, forKey: .error)
}
}
Screenshot of code with error

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