22

I have a struct that parse JSON using Codable.

struct Student: Codable {
    let name: String?
    let amount: Double?
    let adress: String?
}

Now if the amount value is coming as null the JSON parsing is failing.

So should I manually handle the null cases for all the Int and Double that are present in the Student struct?

The String values coming as null is automatically handled.

Paulo Mattos
  • 18,845
  • 10
  • 77
  • 85
Ekra
  • 3,241
  • 10
  • 41
  • 61
  • 1
    https://stackoverflow.com/questions/46292325/what-is-difference-between-optional-and-decodeifpresent-when-using-decodable-for ? By overriding `init(from decoder:)`. On accepted answer, there seems to be a "Int" value as "Null". – Larme Apr 18 '18 at 08:00
  • 3
    A `null` value (no string) is treated as `nil` by default so the decoding is supposed to succeed if the property is optional. By the way: You can omit the CodingKeys. – vadian Apr 18 '18 at 08:06
  • @vadian - you mean I don't need to handle it separately ? As others have suggested – Ekra Apr 18 '18 at 08:14
  • If the name of the properties are the same as the keys you don't need explicit `CodingsKeys`. Name the property as (correctly spelled) `address` and delete the entire enum. – vadian Apr 18 '18 at 08:16
  • @vadian - u said -- A null value for Int / Double is treated as nil by default so the decoding is supposed to succeed if the property is optional. So there is no need for extra conding to handle the cases. OR I have to write decodeIfPresent for all the Int/Double – Ekra Apr 18 '18 at 08:18
  • 4
    It's not necessary to write a custom initializer. Declaring the properties as optional (`?`) is sufficient. That's a part of the magic of `Codable`. However if the `null` value is a string `"null"` you have to write a custom initializer and then please blame the owner of the service for sending this awful JSON. – vadian Apr 18 '18 at 08:28
  • I can't reproduce this. Can you show the JSON that doesn't decode for you? Adding `"amount": null` to my JSON works fine. – Rob Napier Apr 18 '18 at 13:06

2 Answers2

28

Let me do this Playground for you since an example shows you more than a hundred words:

import Cocoa

struct Student: Codable {
    let name: String?
    let amount: Double?
    let adress: String?
}

let okData = """
{
   "name": "here",
 "amount": 100.0,
 "adress": "woodpecker avenue 1"
}
""".data(using: .utf8)!

let decoder = JSONDecoder()
let okStudent = try decoder.decode(Student.self, from:okData)
print(okStudent)

let nullData = """
{
   "name": "there",
 "amount": null,
"adress": "grassland 2"
}
""".data(using: .utf8)!

let nullStudent = try decoder.decode(Student.self, from:nullData)
print(nullStudent)

null is handled just fine if you define your structs using optionals. I would however advise against it if you can avoid it. Swift provides the best support I know to help me not forgetting to handle nil cases wherever they may occur, but they are still a pain in the ass.

Patru
  • 4,481
  • 2
  • 32
  • 42
  • when i'm trying to code a null int and send it to my web api, the int disrepair from the json, and i need to send a null int to the server, any way to fix that? – Ben Shabat Jan 09 '19 at 07:26
  • @BenShabat You should post your code as a question and show the JSON you would like to send. I am not sure if JSONEncoder will send fields with null, but you should provide more context than you can reasonably post in a comment. You can post a link to your question as a comment if you like. – Patru Jan 11 '19 at 07:38
25

Was browsing through Codable and got this issue.

So to be very clear here it is, If the JSON/response would contain null as the value, its interpreted as nil. And hence for that reason one of the model's property which might contain null should be marked as optional.

For Example, consider the below JSON response,

{
"name": "Steve",
"amount": null,
"address": "India"
}

The model should be as below, cos amount is returning null.

struct Student: Codable {
    let name: String
    let amount: Double?
    let address: String
}

Suggestion: In case, if you would write a init(from decoder: Decoder) throws, always use something like below, for optional properties.

init(from decoder: Decoder) throws {
        let values = try decoder.container(keyedBy: CodingKeys.self)
        amount = try values.decodeIfPresent(String.self, forKey: .amount)
        //so on...
    }

Even if you add do-catch block with try? decoder.... it can be captured if fails. Hope thats clear!! Its simple but very difficult to find the issue even if the model is containing 5 or more properties with some containing null values

Lal Krishna
  • 15,485
  • 6
  • 64
  • 84
Vinayak Hejib
  • 721
  • 7
  • 17