Maldazar
Maldazar

Reputation: 43

Swift - Is there a way to differentiate between a field not being present or a field being nil/null when decoding an optional Codable value

The Necessary Functionality

I'm in the process of modifying a system to save a queue of currently unsent API requests to UserDefaults to be re-sent when the user's connection allows.

As some patch requests require the ability to send an actual NULL value to the API (and not just ignore out the field if its a nil optional), this means I need the ability to encode and decode nil/NULL values from defaults for certain fields.

The Issue

I have the encoding side down, and can happily encode requests to either send NULL fields to the server or encode them to Defaults. However, my issue is that when it comes to decoding saved unsent requests, I can't find a way to differentiate between an actual Nil value and the field just not being there.

I am currently using decodeIfPresent to decode my fields (all of the fields for these requests are optional), which returns nil if the field is empty OR if the field is set to Nil/NULL. Obviously this doesn't work for my fields that can explicitly be set to Nil, as there is no way for me to differentiate between the two cases.

Question

Is there any decode methodology I could implement that would allow for differentiating between a field not being there and a field actually being set to nil?

Upvotes: 4

Views: 400

Answers (1)

Shehata Gamal
Shehata Gamal

Reputation: 100503

There is no way , but you can add another info to know that

struct Root : Codable {

    let code : Int?
    let codeExists:Bool?

    init(from decoder: Decoder) throws {
        let values = try decoder.container(keyedBy: CodingKeys.self) 
        code = try values.decodeIfPresent(Int.self, forKey: .code)
        codeExists =  values.contains(.code)

    }
}

According to docs decodeIfPresent

This method returns nil if the container does not have a value associated with key, or if the value is null. The difference between these states can be distinguished with a contains(_:) call.

So decoding

let str = """
{
  "code" : 12
}
"""

gives

Root(code: Optional(12), codeExists: Optional(true))

&&

This

let str = """
{
  "code" : null
}
"""

gives

Root(code: nil, codeExists: Optional(true))

and this

let str = """
{

}
"""

gives

Root(code: nil, codeExists: Optional(false))

Upvotes: 4

Related Questions