Most frontend bugs come from data being a different shape than you assumed. A value that should be a string turns out to be null. Someone renames a field on the backend. Your code throws, or worse, the page silently looks broken.
Elm’s decoders turn messy JSON into typed Elm values. If the data doesn’t match, Elm won’t let you use it, so the failure happens at the boundary instead of somewhere deep in your view code.
The example below fetches users from an API and filters them by all, odd, or even IDs.
The user type
First, say exactly what a user looks like:
type alias User =
{ id : Int
, avatar : String
, firstName : String
, lastName : String
}
No maybes and no nulls. Either the data is shaped like this, or Elm never lets it in.
Decoding JSON safely
This is where decoders come in. A decoder checks the shape of the data, field by field:
decodeUser : Decode.Decoder User
decodeUser =
Decode.map4 User
(Decode.field "id" Decode.int)
(Decode.field "avatar" Decode.string)
(Decode.field "first_name" Decode.string)
(Decode.field "last_name" Decode.string)
Decode.field "id" Decode.intmeans “find theidfield, make sure it’s an integer”.Decode.field "avatar" Decode.stringdoes the same for the avatar, and so on.
The whole user is only built if all four fields are present and correct.
When fetching multiple users, we decode a list:
decodeUsers : Decoder (List User)
decodeUsers =
Decode.at [ "data" ] (Decode.list decodeUser)
This looks inside the data field of the API response, and decodes each item as a User.
Zero runtime errors
If the API changes or sends bad data, Elm never runs with broken user objects. You get a clear error instead, like “expected a String but got null”. From there you can show a message or retry.
Fetching the data
Elm puts it together like this:
getUser : Cmd Msg
getUser =
Http.get (apiUrl "/users") decodeUsers
|> RemoteData.sendRequest
|> Cmd.map UsersResponse
- Fetch the users from the API.
- Try to decode them into a list of users.
- If it works, update the model. If it doesn’t, you can handle the error.
Why this helps
You always know the shape of what you’re working with, broken data is caught the moment it arrives, and a bad response gives you a clear message rather than a blank page.
A quick note on filtering
The app also filters users by odd or even ID:
filter : FilterType -> List User -> List User
filter filterType users =
case filterType of
All -> users
Odd -> List.filter (\user -> user.id % 2 /= 0) users
Even -> List.filter (\user -> user.id % 2 == 0) users
This works because the data is guaranteed to be shaped correctly by the time the filter sees it.
With decoders, the compiler catches the bad data for you, and the “undefined is not a function” class of bug disappears from the codebase.
Published on .