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

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

Related

Decoding different type with and without array

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 {
// ..
}

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

Decoding invalid URLs as nil

Before you answer:
I'm aware:
That an empty string is an invalid URL
That I could write a custom decoder for Employee
That I could declare url as a String
What I'm looking for is a better solution for decoding the optional URL itself. I'm hoping there's some Codable magic I'm missing!
So, I have JSON such as
let json = Data("""
{
"name": "Fred",
"url": ""
}
""".utf8)
and a corresponding object that contains an optional URL…
struct Employee: Decodable {
let name: String
let url: URL?
}
As url in the JSON is invalid, I'd like it to decode as nil, rather than throwing an error.
Trying the following doesn't work (it doesn't get called)…
extension Optional where Wrapped == URL {
init(from decoder: Decoder) throws {
let container = try decoder.singleValueContainer()
do {
self = try container.decode(URL.self)
} catch {
self = nil
}
}
}
In the past I've used…
struct FailableDecodable<T: Decodable>: Deodable {
let wrapped: T?
init(from decoder: Decoder) throws {
let container = try decoder.singleValueContainer()
do {
self.wrapped = try container.decode(T.self)
} catch {
print("Error decoding failable object: \(error)")
self.wrapped = nil
}
}
}
struct Employee: Decodable {
let name: String
let url: FailableDecodable<URL>?
}
but this requires me to continually refer to url.wrapped.
Is there a better solution?
If you are using Swift 5.1, you can use #propertyWrapper:
let json = """
{
"name": "Fred",
"url": ""
}
""".data(using: .utf8)!
#propertyWrapper
struct FailableDecodable<Wrapped: Decodable>: Decodable {
var wrappedValue: Wrapped?
init(from decoder: Decoder) throws {
let container = try decoder.singleValueContainer()
wrappedValue = try? container.decode(Wrapped.self)
}
}
struct Employee: Decodable {
let name: String
#FailableDecodable
private(set) var url: URL?
}
let employee = try! JSONDecoder().decode(Employee.self, from: json)
employee.url // nil
Edit — Codable version
If you need the top level struct to be Encodable as well you can use Codable conformance to the property wrapper.
#propertyWrapper
struct FailableDecodable<Wrapped: Codable>: Codable {
var wrappedValue: Wrapped?
init(from decoder: Decoder) throws {
let container = try decoder.singleValueContainer()
wrappedValue = try? container.decode(Wrapped.self)
}
func encode(to encoder: Encoder) throws {
var container = encoder.singleValueContainer()
try container.encode(wrappedValue)
}
}
If url is nil this will output a JSON with url: null
{"name":"Fred","url":null}
If you wish to don't output the url property when nil you will need to implement a custom encoding (with encode(to:)) in Employee (which would mitigate the benefit of using a property wrapper).
Note: Using the default implementation of encode(to:) (not implementing it) works, but output an empty object when url is nil:
{"name":"Fred","url":{}}
If you get Return from initializer without initializing all stored properties warning for your Codable struct initializer that contains #FailableDecodable below will resolve it.
#propertyWrapper
struct FailableDecodable<Wrapped: Codable>: Codable {
var wrappedValue: Wrapped?
init(from decoder: Decoder) throws {
let container = try decoder.singleValueContainer()
wrappedValue = try? container.decode(Wrapped.self)
}
func encode(to encoder: Encoder) throws {
var container = encoder.singleValueContainer()
try container.encode(wrappedValue)
}
init() {
self.wrappedValue = nil
}
}

WrapperOfNSCoding fails with single value

A question from Implementing Codable for UIColor
struct WrapperOfNSCoding<Wrapped>: Codable where Wrapped: NSCoding {
var wrapped: Wrapped
init(_ wrapped: Wrapped) { self.wrapped = wrapped }
init(from decoder: Decoder) throws {
let container = try decoder.singleValueContainer()
let data = try container.decode(Data.self)
guard let object = NSKeyedUnarchiver.unarchiveObject(with: data) else {
throw DecodingError.dataCorruptedError(in: container, debugDescription: "failed to unarchive an object")
}
guard let wrapped = object as? Wrapped else {
throw DecodingError.typeMismatch(Wrapped.self, DecodingError.Context(codingPath: container.codingPath, debugDescription: "unarchived object type was \(type(of: object))"))
}
self.wrapped = wrapped
}
func encode(to encoder: Encoder) throws {
let data = NSKeyedArchiver.archivedData(withRootObject: wrapped)
var container = try encoder.singleValueContainer()
try container.encode(data)
}
}
let colors = [NSColor.red, NSColor.brown]
print(colors)
let w = WrapperOfNSCoding(colors[0])
let jsonData = try! JSONEncoder().encode(w) - fails
let jsonData = try! JSONEncoder().encode(colors.map({ WrapperOfNSCoding($0) })) - succeeds
print(jsonData)
let colors2 = try! JSONDecoder().decode([WrapperOfNSCoding<NSColor>].self, from: jsonData).map({ $0.wrapped })
print(colors2)
The error is when a single value is used in the encoder
let w = WrapperOfNSCoding(colors[0])
let jsonData = try! JSONEncoder().encode(w)
error is
Fatal error: 'try!' expression unexpectedly raised an error:
Swift.EncodingError.invalidValue(WrapperOfNSCoding #1...
This succeeds
let w = WrapperOfNSCoding([colors[0]])
let jsonData = try! JSONEncoder().encode(w)
Why would that be
JSONEncoder need valid JSON context on the top level, which could be either [:] (Dictionary) or [] (Array), inside you can place an element like in this example string.
When you save any NSCoding object, the compiler treats that object as the string. In the case of JSONEncoder().encode(w), you are trying to encode an NSCoding object which acts as a string object instead of regular JSON object.
In the case of JSONEncoder().encode([w]), the object has created an array and each element has a string which is NSCoding in your case.
In another way w data is a string and [w] is an array with each index is having a string, therefore JSONEncoder is not giving you an error with [w] and giving you an error for w.

Using Decodable with inheritance raises an exception

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.