Background
I’m building a toy webapp to allow users to create, share and vote on various polls (like strawpoll.me). I’m struggling to decode JSON from my API into a type that can exist in one of two states: Single
or Multi
.
To represent an individual users vote on a given poll I’ve created the following type:
type Selection
= Single (Maybe String)
| Multi (Set.Set String)
Reasoning that:
- If the poll only permits a single vote
- The user has either
- Submitted a Vote (Just selection)
- Not voted at all (Nothing)
- The user has either
- If the poll allows multiple votes
- The user has either voted
{'A', 'B', 'C'}
(for one or more option) - Or not voted at all
{}
- The user has either voted
This custom Selection
type lives within each poll (fields omitted for clarity)
type alias Poll =
{ pid : String
, title : String
, selection : Selection
}
JSON
I’ve configured my backend API to return information for the two different types of poll as follows, with the type specified by the multi
value
{
"multi": true,
"votes": ['A', 'B', 'C'] // Multiple Options
...
}
And
{
"multi": false,
"votes": ['A'] // Single option
}
Ideally I’d like to take this format, and conditionall unmarshal to either Single
or Multi
based on the value associated with multi
.
My Attempt
Here’s what I have so far, loosely based this Stackoverflow answer:
singleDecoder : Decoder Selection
singleDecoder =
Decode.succeed Single
|> required "voted" (nullable string)
multiDecoder : Decoder Selection
multiDecoder =
-- Unmarshal to a JSON array to an Elm Set.Set?
selectionDecoder : Bool -> Decoder Selection
selectionDecoder multi =
if multi then
multiDecoder
else
singleDecoder
pollDecoder : Decoder Poll
pollDecoder =
Decode.succeed Poll
|> required "pid" string
|> required "title" string
|> Json.Decode.Pipeline.custom (field "multi" bool |> Decode.andThen selectionDecoder)
I’d hoped that andThen
would be help me to create a new decoder based on the multi
field.
But I get the following compilation error:
|> Json.Decode.Pipeline.custom (field "multi" bool |> Decode.andThen selectionDecoder)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
The argument is:
Decoder (Selection -> b)
But (|>) is piping it a function that expects:
Decoder (Bool -> Poll)
I’ve clearly misundersood the behaviour of the andThen
function. Does this mean I have to duplicate my Poll decoder datastructure, to handle Single
and Multi
in order to correctly use andThen
or is there something I’m missing?
Are there any examples/resources people can point to that achieve this conditional unmarhsalling behaviour