2

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)
1
  • 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 Commented Feb 4, 2020 at 16:08

3 Answers 3

4

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

Sign up to request clarification or add additional context in comments.

1 Comment

Just a note: KeyedDecodingContainer.Key inside a KeyedDecodingContainer extension is redundant. Just use Key
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.

2 Comments

Thanks for the suggestion. I have tried it, but not working. Body is empty, it's not nil. :)
There is another issue with OP's code. OP has responseCode property in Header structure but the json returns code
0

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

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.