Using Elm’s Json.Decode to Safely Parse External Data
Learn how Elm’s Json.Decode module lets you declaratively parse JSON safely, turning runtime errors into handleable Results.
08 Sept 2025, 15:08 UTC

Problem: Unsafe JSON Parsing in Frontend Apps
When an Elm application fetches data from a REST endpoint, the raw response is a JSON string. If the payload does not match the expected shape—perhaps a field is missing or has the wrong type—naïve parsing can raise a runtime exception, crashing the user interface.
Thesis: Declarative Decoders Guarantee Safe Parsing
Elm’s Json.Decode module lets you describe, as a pure function, exactly how a JSON value should be turned into an Elm record. The decoder either succeeds with a value of the correct type or fails with a detailed Error that you can handle gracefully.
Worked Example: Decoding a Simple User Record
module JsonExample exposing (..)
import Json.Decode as Decode exposing (Decoder, field, map2, string, int, decodeString)
type alias User =
{ name : String
, age : Int
}
userDecoder : Decoder User
userDecoder =
map2 User
(field "name" string)
(field "age" int)
To use the decoder with an HTTP request:
import Http
import Json.Decode as Decode
fetchUser : Cmd Msg
fetchUser =
Http.get
{ url = "/api/user"
, expect = Http.expectJson fetchUserResult userDecoder
}
type Msg
= FetchUserResult (Result Http.Error User)
fetchUserResult : Result Http.Error User -> Msg
fetchUserResult result =
case result of
Ok user ->
SetUser user
Err _ ->
SetDecodeError
Because the decoder is pure, you can unit‑test it in isolation:
Trade‑off: Verbosity for Nested or Heterogeneous JSON
Describing deeply nested structures requires chaining many field, at, or mapN calls, which can feel boilerplate‑heavy. For heterogeneous arrays you may need oneOf with several branch decoders. The Elm community mitigates this with helper packages such as json-decode-pipeline or by defining reusable sub‑decoders for repeated parts of the schema.
You can check whether the verbosity is affecting readability by extracting a sub‑decoder and verifying that the parent decoder still type‑checks:
addressDecoder : Decoder Address
addressDecoder =
map3 Address
(field "street" string)
(field "city" string)
(field "zipCode" int)
userDecoder : Decoder User
userDecoder =
map4 User
(field "name" string)
(field "age" int)
(field "address" addressDecoder)
(field "active" bool)
If the compiler accepts the definition, the decomposition is safe and does not change runtime behavior.
Actionable Closing
Start by identifying the JSON shapes your app consumes. Write a small decoder for each shape in a dedicated module, test it in elm-repl or with elm-test, and then plug the decoder into your Http.expectJson calls. When you encounter a decode failure, pattern‑match on the Result to show a user‑friendly message or fallback data, keeping the UI stable.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.