1

I want to decode JSON using JSONDecoder. It is working as expected, but for the JSON where inner object is empty JSONDecoder throws an error The data couldn’t be read because it is missing.

Sample JSON on Error:

{
    "header": {
        "code": 1053,
        "message": "Incorrect information."
    },
    "body": {}
}

Sample JSON on Success:

{
    "header": {
        "code": 1053
        "message": "Nice information."
    },
    "body": {
        "client_id": 12345
    }
}

Success JSON, it is decoded easily. But on Error JSON, It's throwing error.

Here is the code I'm using

struct ApiResponse: Decodable {

    let header: Header
    let body: Body

    struct Header: Decodable {
        let responseCode: Int
        let message: String
    }

    struct Body: Decodable {
        let clientId: Int
    }
}

let decoder: JSONDecoder = JSONDecoder()
decoder.keyDecodingStrategy = .convertFromSnakeCase
let decodedResponse = try decoder.decode(ApiResponse.self, from: data)
Dawood Mujib
  • 337
  • 4
  • 14
  • 1
    make optionals all your model properties, especially body since it may bi nil or not. and add test client id as optional in Body and decode using decodeIfPresent func – Yoel Jimenez del valle Feb 04 '20 at 16:08

3 Answers3

3

You can extend KeyedDecodingContainer to treat an empty dictionary as nil with a protocol

public protocol EmptyDictionaryRepresentable {
    associatedtype CodingKeys : RawRepresentable where CodingKeys.RawValue == String
    associatedtype CodingKeyType: CodingKey = Self.CodingKeys
}

extension KeyedDecodingContainer {
    public func decodeIfPresent<T>(_ type: T.Type, forKey key: KeyedDecodingContainer.Key) throws -> T?
        where T : Decodable & EmptyDictionaryRepresentable
    {
        guard contains(key) else { return nil }
        let container = try nestedContainer(keyedBy: type.CodingKeyType.self, forKey: key)
        return container.allKeys.isEmpty ? nil : try decode(T.self, forKey: key)
    }
}

To use it adopt the protocol and declare the affected property as optional

struct ApiResponse: Decodable {
    let header: Header
    let body: Body?
}

struct Body: Decodable, EmptyDictionaryRepresentable {
    enum CodingKeys : String, CodingKey { case clientId = "client_id" }

    let clientId: Int
}

Caveat: This solution does not work with .convertFromSnakeCase strategy

Note: Consider the key - struct member name mismatch in Header

vadian
  • 274,689
  • 30
  • 353
  • 361
  • Just a note: `KeyedDecodingContainer.Key` inside a `KeyedDecodingContainer` extension is redundant. Just use `Key` – Leo Dabus Feb 04 '20 at 17:27
1

You can always implement your own decode function that lets you do anything you want, but the quick way around this is just to mark anything that isn't guaranteed to return as optional.

Keep in mind if the key is returned by the server then it must decode properly. Normally I would suggest making Body optional.. however here it won't work.

In this case you'll want to do:

struct ApiResponse: Decodable {

    let header: Header
    let body: Body // key is being returned so we keep it as part of the response

    struct Header: Decodable {
        let code: Int // match your variables with the JSON being returned
        let message: String
    }

    struct Body: Decodable {
        let clientId: Int? // sometimes nothing comes inside the body dict, so make its internals optional
    }
}

EDIT:

ALSO, as Leo pointed out in the comments, you also made a silly mistake of not actually matching your variables to the response. Note your JSON has code as a key while your Header object is looking for a responseCode I've edited my original response to also make this change.

Community
  • 1
  • 1
gadu
  • 1,816
  • 1
  • 17
  • 31
0

Declare clientId as optional property. Because in your error JSON client_id not exist. For more info read the Article.

Muzahid
  • 5,072
  • 2
  • 24
  • 42