Custom flags decoder in Elm

I’m preparing a talk about Elm and JS interoperability for Prague Elm Meetup and I got to the topic of flags, i.e. how to initialize an Elm app with data passed in from JavaScript. Looking into it, I realized how much I don’t know and had to find out. There was one special question which bothered me from the moment I tried using flags: Elm automatically decodes the data which limits it to elementary types and their combinations.

Classic Elm application without any inputs will probably use Html.program to

This means that the application doesn’t need any data from the outside (e.g. a calculator), all data is fixed and a part of the application (e.g. random text generator with a static vocabulary), or it is willing to show a spinner to the user and fetch all data asynchronously via commands. In most cases, we integrate Elm applications into an existing ecosystem which can provide some bootstrap data immediately.1

To pass data to and Elm application, you have to replace Html.program by Html.programWithFlags. This does not affect update, view, and subscriptions but it changes the type of the init function:

init : (model, Cmd msg)

becomes

init : flags -> (model, Cmd msg)

This means that init takes an argument of type of your choosing and converts it to the initial model and commands.2

Let’s pass locale code into our application. We’ll need locale field in our model:

type alias Model =
  { locale : String
  , meters : List Meter
  -- ...
  }

We’ll define our flags type as an alias for String which represents the locale code:

type alias Flags = String

So we can build the initial model with our flags:

init : Flags -> (Model, Cmd Msg)
init flags =
  { locale = flags
  , meters = [] } ! []

The JS side is simple as well:

var flags = "en";
Elm.Main.fullscreen(flags);

Elm will take care of parsing the argument passed to the fullscreen and converting it to our Flags type. As mentioned at the top of this article, this process is automatic which is great as long as your flags type does not include any union types because Elm doesn’t know how to decode those and never starts the application.

Let’s see this in action! In Enectiva, we work a lot with meters, so it’s common that an Elm app would need a list of meters and sometimes this list might be passed in as part of flags:

var flags = {
  locale: "en",
  meters: [
    {
      id: 1,
      name: "Main electricity meter",
      energy: "el"
    },
    {
      id: 2,
      name: "Main water meter",
      energy: "wa"
    }
  ]
};
Elm.Main.fullscreen(flags);

This works great as long as the meter type is defined something like this:

type alias Meter =
  { id : Int
  , name : String
  , energy : String
  }

With flags:

type alias Flags =
  { locale : String
  , meters : List Meter
  }

Energies are, however, a fixed set of values and it makes sense to define a union type for them:

type Energy = El | Wa | He | Co | Ga

This changes the energy field in Meter type to Energy and suddenly Elm can’t automatically convert the data passed from JS into Flags type. It will give us a nice error message describing our predicament but it won’t help much.

Our old approach was to define an alternative type for Meter which was identical except for having string energy. This would let Elm decode the flags correctly, and we’d then have to go and manually convert the strings into values of Energy. Definitely not an elegant solution an it would get quite hairy with more complex types.

It would be much easier to give Elm our own decoder to parse the incoming data but such mechanism isn’t there, it would be apparent from the types. A dirty hack would be saying that meters field in flags is a String and then parse that in init with the decoder. But the flags would then need to have a JSON string as a value of a field in a JSON object — far from perfect.

Since I’m preparing a talk, I feel I should give my best to be accurate, to be up-to-date, and to present the optimal approach, not hacks. I tried to google for this problem on several occasions and never found anything useful, so I had to dig into Elm’s source code. My question was: how does Elm derive the decoder for flags?

After running through few modules, I reached runHelp in the native portion of JSON decoding library. This is the core of the decoding process: it compares the elementary decoder type to the actual JSON value and returns either an Ok value or an Err which will eventually halt the decoding process. The key component here is the value branch which always succeeds with the value. This clicked with the error message for bad flags mentioning Json.Decode.Value as an acceptable type!

What this means that if you say that any part of the flags type is Json.Decode.Value, Elm won’t try to parse it further and will make it accessible as such. We can then take this value and pass it to Json.Decode.decodeValue alongside our own decoder. Exactly our goal all along!

Here’s a full example:

Flags are a bit mysterious by using Json.Decode.Value and init has to do the extra work of parsing it, but the result is perfect and feels very Elm-ish.


  1. This typically means things like CSRF token, localization data, or lists of customer created objects used in selects. ↩︎

  2. The type of your program changes to include the flags type as well. ↩︎

We're looking for developers to help us save energy

If you're interested in what we do and you would like to help us save energy, drop us a line at jobs@enectiva.cz.

comments powered by Disqus