Using Decodable with Inheritance Raises an Exception

Using Decodable with inheritance raises an exception

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.

how to use inheritance in decodable model

Rather than inheritance and classes use generics and structs because Decodable doesn't support inheritance by default.

For example create a struct JSONParser

struct JSONParser<T : Decodable> {

struct ResponseData<U : Decodable> : Decodable {
let total, count : Int
let results : [U]
}

let code : Int
let status : String
let data : ResponseData<T>

init(data: Data) throws {
let decoder = JSONDecoder()
data = try decoder.decode(ResponseData.self, from: data)
}
}

And use it for the dictionary containing id and title

struct Item {
let id : Int
let title : String
}

do {
let jsonParser = try JSONParser<Item>(data: data)
let results = jsonParser.data.results
} catch { print(error) }

Swift 5 Default Decododable implementation with only one exception

Is there a way to keep Swift's default implementation for a Decodable class with only Decodable objects but one exception

Unfortunately no. To be Decodable all properties must be Decodable. And if you are going to write a custom init you must initialize (and therefore decode) all properties yourself.

Apple knows this is painful and has given some thought to the matter, but right now a custom init for a Decodable is all or nothing.

As has been suggested in a comment you might work around this by splitting your struct into two separate types. That way you could have a type with just one property, you initialize it manually, and you’re all done.

Swift 4 JSON Decodable simplest way to decode type change

Unfortunately, I don't believe such an option exists in the current JSONDecoder API. There only exists an option in order to convert exceptional floating-point values to and from a string representation.

Another possible solution to decoding manually is to define a Codable wrapper type for any LosslessStringConvertible that can encode to and decode from its String representation:

struct StringCodableMap<Decoded : LosslessStringConvertible> : Codable {

var decoded: Decoded

init(_ decoded: Decoded) {
self.decoded = decoded
}

init(from decoder: Decoder) throws {

let container = try decoder.singleValueContainer()
let decodedString = try container.decode(String.self)

guard let decoded = Decoded(decodedString) else {
throw DecodingError.dataCorruptedError(
in: container, debugDescription: """
The string \(decodedString) is not representable as a \(Decoded.self)
"""
)
}

self.decoded = decoded
}

func encode(to encoder: Encoder) throws {
var container = encoder.singleValueContainer()
try container.encode(decoded.description)
}
}

Then you can just have a property of this type and use the auto-generated Codable conformance:

struct Example : Codable {

var name: String
var age: Int
var taxRate: StringCodableMap<Float>

private enum CodingKeys: String, CodingKey {
case name, age
case taxRate = "tax_rate"
}
}

Although unfortunately, now you have to talk in terms of taxRate.decoded in order to interact with the Float value.

However you could always define a simple forwarding computed property in order to alleviate this:

struct Example : Codable {

var name: String
var age: Int

private var _taxRate: StringCodableMap<Float>

var taxRate: Float {
get { return _taxRate.decoded }
set { _taxRate.decoded = newValue }
}

private enum CodingKeys: String, CodingKey {
case name, age
case _taxRate = "tax_rate"
}
}

Although this still isn't as a slick as it really should be – hopefully a later version of the JSONDecoder API will include more custom decoding options, or else have the ability to express type conversions within the Codable API itself.

However one advantage of creating the wrapper type is that it can also be used in order to make manual decoding and encoding simpler. For example, with manual decoding:

struct Example : Decodable {

var name: String
var age: Int
var taxRate: Float

private enum CodingKeys: String, CodingKey {
case name, age
case taxRate = "tax_rate"
}

init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)

self.name = try container.decode(String.self, forKey: .name)
self.age = try container.decode(Int.self, forKey: .age)
self.taxRate = try container.decode(StringCodableMap<Float>.self,
forKey: .taxRate).decoded
}
}

Decode string or int value in structure will return string(1) in Swift 4

If you want to print the value without the enum wrapper, just implement description:

extension QuantumValue: CustomStringConvertible {
public var description: String {
switch self {
case let .string(string):
return string
case let .int(number):
return "\(number)"
}
}
}


Related Topics



Leave a reply



Submit