Tune in to the tools and techniques in the Elm ecosystem.
004: JSON Decoders
Basics
elm/json
package docs- Elm Guide section on JSON Decoders
- Validates that data has the expected shape. Similar to the pattern we discussed in episode 002 Intro to Opaque Types.
Ports and Flags
Here's an Ellie example that shows the error when you have an implicit type that your flags decode to and it's incorrect.
Sorry Dillon... Jeroen won the trivia challenge this time 😉 It turns out that ports will throw exceptions when they are given a bad value from JavaScript, but it doesn't bring down your Elm app. Elm continues to run with an unhandled exception. Here's an Ellie example.
Flags and ports will never throw a runtime exception in your Elm app if you always use Json.Decode.Value
s and Json.Encode.Value
s for them and handle unexpected cases. Flags and ports are the one place that Elm lets you make unsafe assumptions about JSON data.
Benefits of Elm's Approach to JSON
- Bad data won't end up deep in your application logic where it's hard to debug (and discover in the first place)
- Decouples serialization format from your Elm data types
- You can do data transformations locally as you build up your decoder, rather than passing your giant data structure through a single transform function
Decoding Into Ideal Types
- Richard Feldman's
elm-iso8601 package
elm/time package
- Programming by intention
Json.Decode.succeed
is helpful for stubbing out data- Dillon's Incremental Type Driven Design talk at elm Europe
Json.Decode.fail
lets you validate data and fail your whole decoder if there’s a problem- Parse, don’t validate blog post by Alexis King
- Dillon's
elm-cli-options-parser
package Json.Decode.maybe
docs
Note about Decode.maybe
. It can be unsafe to use this function because it can cover up failures.
Json.Decode.maybe
will cover up some cases that you may not have intended to. For example, if an API returns a float we would suddenly get Nothing
back, but we probably want a decoding failure here:
import Json.Decode as Decode
""" {"temperatureInF": 86} """ |> Decode.decodeString (Decode.maybe (Decode.field "temperatureInF" Decode.int))
--> Ok (Just 86)
""" {"temperatureInF": 86.14} """ |> Decode.decodeString (Decode.maybe (Decode.field "temperatureInF" Decode.int))
--> Ok Nothing
Json.Decode.Extra.optionalNullableField
might have more intuitive and desirable behavior for these cases.
Thank you to lydell for the tip! See the discussion in this discourse thread.
Learning resource
- Brian Hicks' book The JSON Survival Kit
Joël Quenneville’s Blog Posts About JSON Decoders
Guaranteeing that json is valid before runtime
elm-graphql
package- The basics of GraphQL
- Dillon's Types Without Borders Elm Conf talk
- Elm Radio 001 Getting Started with
elm-pages
elm-pages
StaticHttp
API docs- Kris Jenkins' elm-export tool for Haskell types to Elm.
- Mario Rogic's Evergreen Elm elm Europe talk
Autogenerating json decoders
- json-to-elm tool - generates Elm decoders from raw JSON values
intellij-elm
JSON decoder generator
Organizing your decoders
- Evan Czaplicki's elm Europe keynote The life of a file
- Evan's experience report on implicit decoding in Haskell
Getting Started
- Understand
Json.Decode.map
- Understand record type aliases - the function that comes from defining
type alias Album = { ... }
Submit your question to Elm Radio!