bignerdranch / Freddy

A reusable framework for parsing JSON in Swift.
MIT License
1.09k stars 120 forks source link

How to Serialize Optional Vars #242

Closed emmavray closed 7 years ago

emmavray commented 7 years ago

My model objects often contain many optional fields - particularly when building a data-oriented application. Indicating that no value is present (missing key) is usually preferable to passing some empty value (key="").

This presents a problem when serializing the data model, using the example provided in the docs. What's the best way to deal with this scenario? For example:

var phoneNumber: String?

public func toJSON() -> JSON {
     return JSON.dictionary( [
          "firstName": .string(firstName),
          "lastName": .string(lastName),
          "phoneNumber": .string(phoneNumber)  // Value of 'phoneNumber' not unwrapped
     ])
}

My models contain many optional keys (20-25 items). Ideally there would be a mechanism for skipping keys entirely if the data is not present, while maintaining the clean function structure above.

What is the recommendation?

davidahouse commented 7 years ago

@cdstamper see PR #244.

Currently you could do the following with your example above:

var phoneNumber: String?

public func toJSON() -> JSON {
     return JSON.dictionary( [
          "firstName": .string(firstName),
          "lastName": .string(lastName),
          "phoneNumber": phoneNumber != nil ? .string(phoneNumber!) : .null 
     ])
}

That would at least give you .null values in your JSON. But first of all that is pretty wordy, especially if you have a bunch of these. Secondly, when you serialize this you seem to prefer to not include the key at all if the value is .null. So the PR above adds a method for creating a JSON entry for optionals, turning your example into:

var phoneNumber: String?

public func toJSON() -> JSON {
     return JSON.dictionary( [
          "firstName": .string(firstName),
          "lastName": .string(lastName),
          "phoneNumber": JSON(phoneNumber) // becomes either .string or .null
     ])
}

Then the options: .nullSkipsKey parameter on serialize wouldn't include the key if the value is .null.

jeremy-w commented 7 years ago

The PR change yields a solution that is :1st_place_medal:.

But if you ever find yourself going, "Darn, I wish I could use if let/else as an expression rather than needing to use a temporary variable! Ah well, lemme just bang that x!", I recommend picking up Optional.map, which would let you turn this:

"phoneNumber": phoneNumber != nil ? .string(phoneNumber!) : .null,

into this:

"phoneNumber": phoneNumber.map({ .string($0) }) ?? .null,

And if all else fails, you can inline the whole temporary variable mess using an IIFE:

"phoneNumber": ({ () -> JSON in if let n = phoneNumber { return .string(n) } else { return .null } })(),
emmavray commented 7 years ago

Excellent. The option .nullSkipsKey will resolve this issue for me very nicely.

As far as actually packaging the json, I really like JSON Optional support. Optional.map is another fine solution, but I think it isn't immediately obvious why you'd call .map on a string.

PR #244 will be a great solution, thanks!