Sara is a serialization code generator for Gleam.
With Sara, using //@json_encode() and //@json_decode(), you can generate type-safe JSON encoding and decoding functions for your Gleam custom types at build time.
No need to keep the decoder and encoder up to date 😉
First you'll need to add Sara to your project as a dev dependency:
gleam add sara --devThen add //@json_encode() and/or //@json_decode() attributes to the types you want to serialize:
//@json_encode()
//@json_decode()
pub type Comment {
Comment(id: Int, message: String)
}
//@json_encode()
//@json_decode()
pub type Post {
PostWithoutComment(
id: Int,
title: String,
)
PostWithComment(
id: Int,
title: String,
comments: List(Comment),
)
}Then you can generate the code by running the sara module:
gleam run -m saraAnd that's it! Each time you execute this command, Sara will look for all *.gleam files inside the src directory and will generate a new _json.gleam module for each type annotated with //@json_encode() and //@json_decode().
The only downside is that since Sara will never edit one of your files, your annotated types need to be public and not opaque, otherwise the _json module can't access and create them
Sara is capable of understanding all of the default Gleam types and more!
The types that are currently supported are:
| Type | Example | Description |
|---|---|---|
Bool |
Bool |
True / False |
Int |
Int |
Integer numbers |
Float |
Float |
Coerce numbers into floating point numbers |
String |
String |
Text strings |
List |
List(Int) |
Lists of any supported type |
Tuple |
#(Int, Float) |
Tuples of any supported types |
option.Option |
Option(Bool) |
stdlib Option of any supported type |
dict.Dict |
Dict(Int, Float) |
stdlib Dict of any supported type values |
Type Aliases |
type Alias = String |
Type aliases are resolved to their underlying type |
Custom Types |
type Node { Leaf(Int) | Branch(Node, Node) } |
Custom types with multiple variants |
Recursive Types |
type Node { Node(left: Node, right: Node) | Leaf(Int) } |
Types that reference themselves |
Complex Nested Types |
List(#(String, #(Bool, Int))) |
Arbitrary nesting of supported types |
When Sara encounters a custom type that is not annotated with //@json_encode() or //@json_decode(), it doesn't attempt to generate nested encoders/decoders automatically. Instead, it adds function parameters to the generated functions, allowing you to provide custom encoders, decoders, or default values.
For example, if you have:
//@json_encode()
//@json_decode()
pub type Post {
Post(id: Int, metadata: CustomMetadata)
}
pub type CustomMetadata {
CustomMetadata(data: String)
}Sara will generate functions like:
pub fn post_to_json(
post: Post,
custom_metadata_to_json: fn(CustomMetadata) -> json.Json
) -> json.Json
pub fn post_json_decoder(
custom_metadata_json_decoder: fn() -> decode.Decoder(CustomMetadata),
custom_metadata_zero_value: CustomMetadata
) -> decode.Decoder(Post)This allows you to provide your own serialization logic for types that don't have Sara annotations.
This project would not be possible without squirrel inspiring me to create a code generator. But also the Gleam LSP code action for giving me the idea