Decoding a FlashCard
We would like the let the server decide which flash card to show. In order for
that to work, we need to be able to receive JSON data and decode that into a
FlashCard
.
This chapter we will be working on that.
json package
There is a json
package that will help us decode, and encode, JSON
into datatypes that we are interested in. Before we can use it we need to
install it.
elm install elm/json
Json.Decode
We will be using the Json.Decode
module from the json
package. Because we
want to avoid typing Json.Decode
everywhere when we want to refer to a type
from that module, we are importing it with a different name.
Since we will be defining a Decoder
we will expose that type into our
name space.
import Json.Decode as Decode exposing (Decoder)
Decoder
We want a Decoder
for FlashCard
. The type therefore should be
decode : Decoder FlashCard
Decoders work by composing primitive decoders into more elaborate once. Primitive decoders include
Decode.string
that will decode a string value.Decode.int
that will decode an int value.Decode.bool
that will, well you can probably guess what type of value it decodes.
More elaborate decoders are for example Decode.map
or variants. It's signature
is
Decode.map : (one -> other) -> Decoder one -> Decoder other
It takes a function that maps one type into the other, a decoder for one and will produce a decoder for other. Under the cover the one decoder is used and when successful the transformation is applied to the result.
JSON
Below we will give an example of the JSON we expect to receive. It probably does not surprise you that it looks like
{ "front": "Wettervorhersage", "back": "Weather forecast", "face": "Front" }
Decoder FlashCard
We will create a decoder that will ignore the face field of the JSON. When we have a little more experience we will come back and decode that as well. That means that the decoder becomes
decode : Decoder FlashCard
decode =
Decode.map3 FlashCard
(Decode.field "front" Decode.string)
(Decode.field "back" Decode.string)
(Decode.field "face" <| Decode.succeed Front)
We use Decode.map3
and the FlashCard
constructor, which got automatically
created when we introduced the type alias, to map three different values into a
FlashCard
.
We extensively use the Decode.field
decoder. It
decodes a JSON object, requiring a particular field. The object can have other fields. Lots of them! The only thing this decoder cares about is if a field is present and that the value there is an of a certain type.
Lastly we use Decode.succeed
to hard code the face value to be Front.
Verification
We have created a decoder but how do we know that it behaves correctly? We can write a test to see if our decoder can decode some JSON.
In tests/FlashCardTest.elm
write the following code after our other test.
, test "decode a flash card from JSON" <|
\_ ->
let
input = """{ "front": "Wettervorhersage", "back": "Weather forecast", "face": "Back" }"""
expected =
flash_card "Wettervorhersage" "Weather forecast"
|> Ok
actual =
decodeString FlashCard.decode input
in
Expect.equal actual expected
and all the tests should still work when you import
import Json.Decode exposing (decodeString)