elm-review is an excellent static code analysis tool made by Jeroen Engels. I hope it’s well known in the Elm community by now. Perhaps less known is the fact that in addition to analysing code, it’s also able to generate code.
This is actually a fundamental part of the design of elm-review
as when you run it with the --fix
flag,
it will not only find issues but also offer to fix them when possible by replacing problematic code with an improved version.
It’s then only a small leap to go from replacing problematic code to replacing arbitrary code.
elm-review
is a highly customisable system. It works by applying a set of rules to your code, with each rule
(or technically a set of rules) defined in the form of an Elm package.
Some time ago, Dillon Kearns published a rule
that replaces a string of HTML given to Debug.todo
with elm/html
code that would result in that HTML.
So you could write something like this:
import Html exposing (Html)
import Html.Attributes as Attr
navbarView : Html msg
navbarView =
nav
[]
[ Debug.todo """@html <ul class="flex"><li><a href="/">Home</a></li></ul>""" ]
Then you’d run elm-review --fix
, the rule would spot the argument to Debug.todo
with the special
@html
prefix (you wouldn’t want it to act on all Debug.todo
s!), and it would replace the code with this:
import Html exposing (Html)
import Html.Attributes as Attr
navbarView : Html msg
navbarView =
nav
[]
[ Html.ul
[ Attr.class "flex"
]
[ Html.li []
[ Html.a
[ Attr.href "/"
]
[ Html.text "Home" ]
]
]
]
When I saw this, I realised that I could provide another “user interface” for my json2elm tool – an elm-review
rule! The rule would replace a sample JSON string with a set of decoders and encoders (and relevant types) generated from it.
Today, I’m releasing alexkorban/elm-review-json-to-elm.
Similarly to Dillon’s rule, it looks for Debug.todo
arguments that start with a specific prefix (@json
in this case), and replaces the enclosing definition with a set of types/aliases, decoders and encoders.
For example, suppose you had this in a file:
import Json.Decode
import Json.Encode
person =
Debug.todo """@json{"name": "John", "age": 30}"""
After running elm-review --fix
with my alexkorban/elm-review-json-to-elm
rule configured, you’ll get the following:
import Json.Decode
import Json.Encode
type alias Person =
{ age : Int
, name : String
}
personDecoder : Json.Decode.Decoder Person
personDecoder =
Json.Decode.map2 Person
(Json.Decode.field "age" Json.Decode.int)
(Json.Decode.field "name" Json.Decode.string)
encodedPerson : Person -> Json.Encode.Value
encodedPerson person =
Json.Encode.object
[ ( "age", Json.Encode.int person.age )
, ( "name", Json.Encode.string person.name )
]
Note that the name of the value (person
) was taken to be the name of the top level type.
The rule actually looks at the relevant imports in the file and generates code appropriately.
For example, if you wanted your decoders to use Json.Decode.Pipeline
, just add the import (import Json.Decode.Pipeline
)
and the rule will generate this code instead:
type alias Person =
{ age : Int
, name : String
}
personDecoder : Json.Decode.Decoder Person
personDecoder =
Json.Decode.succeed Person
|> Json.Decode.Pipeline.required "age" Json.Decode.int
|> Json.Decode.Pipeline.required "name" Json.Decode.string
encodedPerson : Person -> Json.Encode.Value
encodedPerson person =
Json.Encode.object
[ ( "age", Json.Encode.int person.age )
, ( "name", Json.Encode.string person.name )
]
Similarly, if you have import Json.Decode.Extra
, you’ll get applicative-style decoders using andMap
from that module.
What’s more, the rule takes into account aliases and the exposing
clause. For example, if your imports look like this:
import Json.Decode as Decode exposing (Decoder, field, int, string)
import Json.Encode as Encode
then the rule will generate the code accordingly:
type alias Person =
{ age : Int
, name : String
}
personDecoder : Decoder Person
personDecoder =
Decode.map2 Person
(field "age" int)
(field "name" string)
encodedPerson : Person -> Encode.Value
encodedPerson person =
Encode.object
[ ( "age", Encode.int person.age )
, ( "name", Encode.string person.name )
]
It doesn’t, however, cross check exposed symbols for clashes at present, which means that you can potentially get
code that doesn’t compile (eg. if you expose string
both from Json.Decode
and Json.Encode
).
While the online json2elm
tool allows you to choose the naming style for decoders and encoders (verbs or nouns),
there is no such option in the elm-review
rule for now (in part because I couldn’t work out how that choice
could be specified).
I hope this rule can help you write JSON decoders and encoders faster - let me know on Twitter if it does!