After being horrified by the decoder pattern in Gleam, I stumbled upon the Zig rewrite blog post, so I came here to see what's available.
The pattern in Gleam:
import gleam/dynamic/decode
import gleam/io
import gleam/json
import gleam/list
import gleam/option.{type Option, None, Some}
import gleam/string
import simplifile
type Greeting {
Greeting(hello: String, extra: Option(String))
}
fn greeting_decoder() -> decode.Decoder(Greeting) {
use hello <- decode.field("hello", decode.string)
use extra <- decode.optional_field(
"extra",
None,
decode.field("sub", decode.optional(decode.string), decode.success),
)
decode.success(Greeting(hello:, extra:))
}
pub fn main() {
case simplifile.read("a.jsonl") {
Ok(contents) -> {
contents
|> string.split("\n")
|> list.filter(fn(line) { string.trim(line) != "" })
|> list.each(fn(line) {
case json.parse(line, greeting_decoder()) {
Ok(g) ->
case g.extra {
Some(extra) ->
io.println("hello: " <> g.hello <> ", extra.sub: " <> extra)
None -> io.println("hello: " <> g.hello)
}
Error(err) ->
io.println(
"bad json: " <> line <> " (" <> string.inspect(err) <> ")",
)
}
})
}
Error(err) -> io.println("could not read a.jsonl: " <> string.inspect(err))
}
}
How is it like in Roc?
app [main!] { pf: platform "https://github.com/roc-lang/basic-cli/releases/download/0.21.0-rc4/FvCh4vdqm3nBY6DWEfZ8RuGCVfjuMY43HA8KSNk9qVDn.tar.zst" }
import pf.OsStr
import pf.Path
import pf.Stdout
Greeting : {
hello : Str,
extra : Try({ sub : Try(Str, [Null]) }, [Missing]),
}
main! : List(OsStr) => Try({}, _)
main! = |_args| {
path : Path
path = "example.jsonl"
contents = path.read_utf8!()?
for line in contents.split_on("\n") {
if !line.trim().is_empty() {
decoded : Try(Greeting, Json.ParseErr)
decoded = Json.parse(line)
match decoded {
Ok(greeting) =>
match greeting.extra {
Ok({ sub: Ok(extra) }) =>
Stdout.line!("hello: ${greeting.hello}, extra.sub: ${extra}")?
Ok({ sub: Err(Null) }) =>
Stdout.line!("hello: ${greeting.hello}")?
Err(Missing) =>
Stdout.line!("hello: ${greeting.hello}")?
}
Err(err) =>
Stdout.line!("bad json: ${line} (${Str.inspect(err)})")?
}
}
}
Ok({})
}
15:06:36 ~/Documents/GitHub/basic-cli main $ cat example.jsonl
{"hello":"world"}
{"hello":"roc","extra":{"sub":"kree"}}
{"hello":"nullable","extra":{"sub":null}}
{"hello":"missing-sub","extra":{}}
not json
15:06:46 ~/Documents/GitHub/basic-cli main $ roc example.roc
hello: world
hello: roc, extra.sub: kree
hello: nullable
bad json: {"hello":"missing-sub","extra":{}} (MissingRequiredField("sub"))
bad json: not json (InvalidJson("Invalid JSON"))
This feels like it's hidding magic. Zig's comptime at least I can look at the source code and understand. How do I understand this?
It's similar to Rust's serde pattern I guess... maybe you could find content on that?
Also in case you haven't seen it, the langref topic on this https://github.com/roc-lang/roc/blob/main/docs/langref/static-dispatch.md#parsing-and-encoding
I'm not tracking anything Roc specific like guides or tutorials yet.. this specific feature is recently implemented -- though the design has been around a while. I think Richard talks about it (or static dispatch more generally) in a video, but I'm not sure which one.
I think Richard talks about it (or static dispatch more generally) in a video, but I'm not sure which one.
This one covered static dispatch pretty extensively, as I recall: Richard Feldman, "New Ways to Roc!"
Last updated: Jul 23 2026 at 13:15 UTC