Open zwilias opened 6 years ago
Thinking about this some more, Option 3 can be combined with either Option 1 or Option 2, too.
-spec optional(Dec) -> {optional, Dec} when Dec :: decoder(T).
-spec to_map(MapSpec) -> decoder(MapResult) when
MapSpec :: #{Key := Dec} | [PropListEntry],
PropListEntry :: {Key, Dec} | {Key, optional, decoder(T)},
Dec :: decoder(T) | {optional, decoder(T)},
MapResult :: #{Key => T}.
This does lead to quite a few equivalent ways of specifying a decoder with an optional field, and I'm not sure if that's a great idea:
dj:to_map(#{foo => dj:optional(dj:field(foo, dj:binary()))}),
dj:to_map(#{foo => {optional, dj:field(foo, dj:binary())}}),
dj:to_map([{foo, dj:optional(dj:field(foo, dj:binary()))}]),
dj:to_map([{foo, optional, dj:field(foo, dj:binary())}]).
-spec to_map(MapSpec) -> decoder(MapResult) when
MapSpec :: #{Key := Dec} | [PropListEntry],
PropListEntry :: {Key, Dec} | {Key, Optionality, Dec},
Optionality :: optional | required,
Dec :: decoder(T),
MapResult :: #{Key => T}.
-spec to_map(MapSpec, OptionalFields) -> decoder(MapResult) when
MapSpec :: #{Key := Dec} | [{Key, Dec}],
Dec :: decoder(T),
OptionalFields :: [Key],
MapResult :: #{Key => T}.
Then again, this still allows expressing the same decoder in three equivalent ways:
dj:to_map(#{foo => dj:field(bar, dj:binary())}, [foo]),
dj:to_map([{foo, dj:field(bar, dj:binary())}], [foo]),
dj:to_map([{foo, optional, dj:field(bar, dj:binary())}]).
Considering all of the above, I'm starting to lean toward only doing Option 2:
Problem
Occasionally, you'll want to decode something into a
map()
where certain fields are optional. Perhaps your decoder doesn't have enough information to provide sane defaults, or you wish to mirror the things your API accepts fairly closely and deal with defaulting in another layer usingmaps:get/3
.Current workarounds
This can currently be worked around in a few ways.
Let's imagine we have a
person/0
type like so:And our JSON is allowed to leave out the
weight
field entirely.One option is providing a default:
This isn't great - both the JSON and our
person/0
type allow leaving out theweight
field, yet we enforce it to be available.Another option is using a "magical" constant and filtering:
Other variations involving filtering are possible. These workarounds are definitely bulky and all but elegant.
Proposal
Some way(s) to allow specifying optional properties would be nice. I see three contenders.
Option 1: Add an
optional/1
helperEssentially it would ignore
missing_field
errors, instead removing the "offending" key from the resulting map.to_map
Option 2: Allow specifying optional fields
Option 3: Remove the ability to take a
map()
in favour of a tuple-listmap()
, but don't want to create a feature-imparity between the different ways to callto_map()
For people who like visually laying things out, we could instead expand the spec to be something like this:
So the following decoder would be equivalent:
CC @kwrooijen @sgillis