0

I am using Swift 4 and JSONDecoder. I have the following structure:

struct Customer: Codable {
    var id: Int!
    var cnum: String!
    var cname: String!
}

Note: the fields cannot be made optional.

Now I have a JSON string:

[
    {
        "id": 1,
        "cnum": "200",
        "cname": "Bob Smith"
    },
    {
        "id": 2,
        "cnum": "201",
        "cname": null
    }
]

And to decode it, I use the following:

let decoder = JSONDecoder()
let customers = try decoder.decode([Customer].self, from: json)

Everything works fine except the null data gets converted to nil. My question is, what would be the easiest way to convert incoming nil to an empty string ("")?

I would like to do this with the minimum amount of code but I'm not sure about the correct approach and at what point can the nil be converted to an empty string. Thank you beforehand.

4
  • 1
    If you don't want optionals why do you declare all variables as optional?? And NEVER declare properties as implicit unwrapped optional which are initialized with an init method anyway. Commented Feb 1, 2018 at 13:52
  • Vadian thank you and sorry for the confusion. When I meant optional, I mean it cannot be: var cname: String? Commented Feb 1, 2018 at 14:07
  • 1
    If it could be nil declare it as regular optional var cname: String?. If not, as non-optional: var cname: String Commented Feb 1, 2018 at 14:09
  • Thank you Vadian, I had also tried that but that again would break objective-c calls. Commented Feb 1, 2018 at 14:12

5 Answers 5

5

You can use backing ivars:

struct Customer: Codable {
    var id: Int
    var cnum: String {
        get { _cnum ?? "" }
        set { _cnum = newValue }
    }
    var cname: String {
        get { _cname ?? "" }
        set { _cname = newValue }
    }
    
    private var _cnum: String?
    private var _cname: String?
    private enum CodingKeys: String, CodingKey {
        case id, _cnum = "cnum", _cname = "cname"
    }
}

Due to the custom CodingKeys, the JSON decoder will actually decode to _cnum and _cname, which are optional strings. We convert nil to empty string in the property getters.

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

Comments

3

You can use decodeIfPresent method.

struct Source : Codable {

    let id : String?


    enum CodingKeys: String, CodingKey {
        case id = "id"

    }

    init(from decoder: Decoder) throws {
        let values = try decoder.container(keyedBy: CodingKeys.self)
        id = try values.decodeIfPresent(String.self, forKey: .id) ?? "Default value pass"

    }
}

Comments

1

You should make a computed variable that will have the original value if it's not nil and an empty string if it is.

var cnameNotNil: String {
    return cname ?? ""
}

3 Comments

the4kman thanks for the editing! Thanks for the answer also but, I need the name to remain as cname though! That would create an additional variable. This would also break alot of other code. Thanks.
Awesome update. I just tried it and for some reason didSet is not running? (placed breakpoint and code never ran inside didSet) Did you test it using JSONDecoder?
@RobertSmith I reverted my answer to the previous state because it did not work. If you want to use cname for the name, provide a different coding key for the original variable and rename it to something like cnameOriginal.
0

The usual way is to write an initializer which handles the custom behavior. The null value is caught in an extra do - catch block.

struct Customer: Codable {
    var id: Int
    var cnum: String
    var cname: String

    init(from decoder: Decoder) throws {
        let container = try decoder.container(keyedBy: CodingKeys.self)
        id = try container.decode(Int.self, forKey: .id)
        cnum = try container.decode(String.self, forKey: .cnum)
        do { cname = try container.decode(String.self, forKey: .cname) }
        catch { cname = "" }
    }
}

2 Comments

Vadian, thank you and I did try that. But unfortunately when I put in an init, it breaks my calls to objective-c code. The closest answer was something in the lines of "didSet" but that would not work neither.
You should have mentioned in the question how the code interacts with Objective-C.
0

Use decodeIfPresent if the value from response might be null

cnum = try container.decode(String.self, forKey: .cnum)
cname = try container.decodeIfPresent(String.self, forKey: .cname)

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.