8

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!

dor506
  • 5,246
  • 9
  • 44
  • 79

1 Answers1

18

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.

vadian
  • 274,689
  • 30
  • 353
  • 361
  • Thanks! it is working!. Can you elaborate please? how is it different from the similar link I've posted – dor506 Dec 19 '17 at 13:56
  • I doubt that the given JSON structure in the WWDC video is the same as in your question. Nevertheless I can confirm that `superDecoder` does throw an error. – vadian Dec 19 '17 at 14:00
  • i do that but it causes an exception when i try to get the value of child class (as LoginResponse) but no problem in super (as BaseResponse) i search a lot but debug didn't show exception description – Ahmad Labeeb Feb 25 '18 at 13:52
  • @vadian Thank you! It takes hours for me to reach your solution finally, i appreciate it. – Amir Ehsanijam Jul 17 '20 at 18:57