package ppx_deriving_jsont
Install
dune-project
Dependency
Authors
Maintainers
Sources
sha256=40d4069e3db7705c0a0b2ad326e05174d7e4f675e6beb813f8ec82c3104a9dc6
sha512=73acea0ed7baf2e589ab189be77c601620c34f1472cc3780251e523e624565a30a569407436b653f2e0b176ae61079148e4d5c878f8b2ef2108e6c6a4d44fa9e
doc/README.html
[@@deriving jsont]
ppx_deriving_jsont is a PPX deriver that generates Jsont descriptions of OCaml types. Note that Jsont allows for a lot more flexibility and precision when writing mappings between OCaml values and JSON. This PPX does not purposes to be a completely automatic replacement for manual bindings but rather a tool to help generate tedious parts of the bindings that can be mix-and-matched with carefully user-written descriptions when that is necessary.
Please take some time to read about all of Jsont's intricacies:
- In the official documentation and cookbook
- In Daniel's introductory post here on discuss.
🚧🚧 Work in progress
This an early take on writing a deriver for Jsont. It can already be used to skip tedious mechanical work like describing large records and lists of variants, but it does not do justice to Jsont's fine control and flexibily over the resulting mappings (like choosing the way integers are mapped).
Any kind of contribution (bug-report, PR, suggestions) is welcomed! I am in no way a PPX expert, so there might be a lot of non-idiomatic things here that I'd be happy to improve.
Todo / Roadmap / Wishlist
- Variants without type parameters (enum)
- Variants with one type parameter
- Tuples
- Variants with more than one type parameter (using tuples)
- Inline records
- Records-as-objects
- Types with parameters
- Recursive types
- Mutually recursive types
- Support for all meaningful base types
- Options (in the form of attributes) - [x] to pass custom Jsont values - [ ] for finer support of integers - [ ] for finer settings - [x] to provide
doccomments - [ ] for other kinds of objects mappings (as sets for example) - [ ] for other kinds of variants mappings (as arrays for example) - Also generate objects' Paths (lenses ?)
- Ppx sanitisation
- Ensure locations make sense
- Correctly raise errors
- Comprehensive test-suite
Installation
ppx_deriving_jsont is still experimental and has not been released to Opam yet. Given how incomplete it is right now you might want to vendor it and eventually contribute your improvements upstream. Alternatively, the development version can be installed in a switch using Opam's pin command:
opam pin https://github.com/voodoos/ppx_deriving_jsont.gitConfiguration
Setup depends of your build system of choice. To enable the deriver in a Dune library (or executable), one should add a dependency to jsont and use the ppx_deriving_jsont preprocessor:
(library
...
(libraries jsont ...)
(preprocess (pps ppx_deriving_jsont)))Usage
Generation is enabled by adding the [@@deriving jsont] attribute to type declarations.
Generation can be tuned with the use of attributes like [@key "Key"] that are often compatible with other derivers such as ppx_yojson_conv. These can also be prefixed [@jsont.key ...] when that compatibility isn't desired.
The deriver follows the usual naming conventions. Types whose name is t generates a value named jsont. Otherwise that value bears the name of the type suffixed by _jsont.
Declaration attributes
All type declarations can be annotated with the [@@kind "Some kind"] and [@@doc "Some doc"] attributes to improve error messages. This has no effect when used on base types.
The kind value usually defaults to the name of the type, the doc value to None.
Core types attributes
Users can overide any core type deriving byt providing their own Jsont.t description using the [@jsont <value>] attribute.
Basic types (with parameters)
Example
type 'a t_with_param = 'a [@@deriving jsont]
type u = int list t_with_param [@@deriving jsont]See generated code
let t_with_param_jsont jsont_type_var__a = jsont_type_var__a
let u_jsont = t_with_param_jsont (Jsont.list Jsont.int)Json output:
# Jsont_bytesrw.encode_string u_jsont [3; 6; 4; 2];;[3,6,4,2]Enumerations
⚠️ Only variants whose constructors have no type parameters are translated as enumerations.
Type declaration attributes
@@kind <string>and@@doc <string>
Constructor attributes
@key <string>specifies the JSON name (otherwise the same as the constructor itself)
Example
type sort = A | X [@key "B"] | C
[@@doc "A doc of sorts"] [@@deriving jsont]See generated code
let sort_jsont =
Jsont.enum ~doc:"A doc of sorts" ~kind:"Sort"
[("A", A); ("B", X); ("C", C)]Json output:
# Jsont_bytesrw.encode_string (Jsont.list u_jsont) [ A; X; C ];;["A","B","C"]Tuples
Tuples are encoded as json arrays.
Type declaration attributes
@@kind <string>and@@doc <string>
Example
type tup = int * string t_with_param [@@doc "Tup doc"] [@@deriving jsont]See generated code
let tup_jsont =
let get_or_raise = function
| Ok r -> r
| Error err -> raise (Jsont.Error err)
in
let enc f acc (e0, e1) =
let e0 = Jsont.Json.encode' Jsont.int e0 |> get_or_raise in
let e1 =
Jsont.Json.encode' (t_with_param_jsont Jsont.string) e1 |> get_or_raise
in
[ (0, e0); (1, e1) ] |> List.fold_left (fun acc (i, e) -> f acc i e) acc
in
let dec_empty () = (None, None) in
let dec_add i elt (e0, e1) =
match i with
| 0 ->
let e = Jsont.Json.decode' Jsont.int elt |> get_or_raise in
(Some e, e1)
| 1 ->
let e =
Jsont.Json.decode' (t_with_param_jsont Jsont.string) elt
|> get_or_raise
in
(e0, Some e)
| _ -> Jsont.Error.msgf Jsont.Meta.none "Too many elements for tuple."
in
let dec_finish meta _ (e0, e1) =
let get_or_raise i o =
match o with
| Some v -> v
| None -> Jsont.Error.msgf meta "Missing tuple member #%i" i
in
(get_or_raise 0 e0, get_or_raise 1 e1)
in
Jsont.Array.map ~kind:"Tup" ~doc:"Tup doc" ~enc:{ enc } ~dec_empty ~dec_add
~dec_finish Jsont.json
|> Jsont.Array.arrayJson output:
# Jsont_bytesrw.encode_string tup_jsont (42, "quarante-deux");;[42,"quarante-deux"]Records
Records are mapped using the "objects-as-records" technique.
Type declaration attributes
@@kind <string>and@@doc <string>
Field attributes
@key <string>specifies the JSON key (otherwise the same as the field)@doc <string>to document fields@absent <expr>/@default <expr>specifies the value to use when decoding if the field is absent (see the cookbook)@omit <expr: unit -> bool>specifies when a value should be ommitted during encoding the cookbook@optionis a shorcut for@absent Noneand@omit Option.is_none
Example
type t = {
name : string; [@doc "Object name"]
maybe_parent : t option; [@option]
ids : string list; [@default []] [@omit List.is_empty]
sort : sort; [@key "Sort"]
}
[@@doc "A t object"] [@@deriving jsont]See generated code
let jsont =
let rec jsont =
lazy
(let make name maybe_parent ids sort =
{ name; maybe_parent; ids; sort }
in
Jsont.Object.map ~doc:"A t object" ~kind:"T" make
|> Jsont.Object.mem "name" ~doc:"Object name" Jsont.string ~enc:(fun t ->
t.name)
|> Jsont.Object.mem "maybe_parent"
(Jsont.option (Jsont.rec' jsont))
~enc:(fun t -> t.maybe_parent)
~dec_absent:None ~enc_omit:Option.is_none
|> Jsont.Object.mem "ids" (Jsont.list Jsont.string)
~enc:(fun t -> t.ids)
~dec_absent:[] ~enc_omit:List.is_empty
|> Jsont.Object.mem "Sort" sort_jsont ~enc:(fun t -> t.sort)
|> Jsont.Object.finish)
in
Lazy.force jsontJson output:
# Jsont_bytesrw.encode_string (Jsont.list u_jsont)
{
name = "Alice";
maybe_parent = Some {
name = "Bob";
maybe_parent = None;
ids = [ "X" ];
sort = X };
ids = [];
sort = A;
};;{
"name":"Alice",
"maybe_parent":
{"name":"Bob", "ids":["X"], "Sort":"B"},
"Sort":"A"
}Variants
Variants are encoded using "object types" as described in the cookbook.
The default type_key is "type". Values that are not inlined-records are wrapped with the key v.
In the future we plan to also support the more traditional encoding variant as arrays.
Polymorphic variant behaves similarly.
Type declaration attributes
@@kind <string>and@@doc <string>@@type_key <string>specifies the name of the JSON field used to distinguish cases. This should not bevwhich is used as a wrapper for constructor arguments, or any of the member of an inlined record. Defaults totype.@@wrap_key <string>specifies the name of the JSON field used to wrap values other than inlined records or records with thenowrapattribute. Defaults tov.
Constructors attributes
@key <string>specifies the JSON name (otherwise the same as the constructor itself)@doc <string>to document constructors@kind <string>to specify the kind of inlined-records@nowrapcan be used on constructors whose only argument is the type of a record
For inlined record
Example
type v =
| A of int [@key "Id"] [@kind "One of A kind"]
| S of sort [@doc "Doc for S"]
| R of { name : string [@doc "Doc for R.name"] }
[@kind "Kind for R"] [@doc "Doc for R"]
[@@doc "Doc for v"] [@@type_key "t"] [@@deriving jsont]See generated code
let v_jsont =
let jsont__R =
Jsont.Object.Case.map "R"
(let make name = R { name } in
Jsont.Object.map ~doc:"Doc for R" ~kind:"Kind for R" make
|> Jsont.Object.mem "name" ~doc:"Doc for R.name" Jsont.string
~enc:((fun (R t) -> t.name) [@ocaml.warning "-8"])
|> Jsont.Object.finish)
~dec:Fun.id
and jsont__S =
Jsont.Object.Case.map "S"
(Jsont.Object.map ~kind:"S" ~doc:"Doc for S" Fun.id
|> Jsont.Object.mem "v" ~doc:"Wrapper for S" sort_jsont ~enc:Fun.id
|> Jsont.Object.finish)
~dec:(fun arg -> S arg)
and jsont__A =
Jsont.Object.Case.map "Id"
(Jsont.Object.map ~kind:"One of A kind" Fun.id
|> Jsont.Object.mem "v" ~doc:"Wrapper for A" Jsont.int ~enc:Fun.id
|> Jsont.Object.finish)
~dec:(fun arg -> A arg)
in
Jsont.Object.map ~kind:"V" ~doc:"Doc for v" Fun.id
|> Jsont.Object.case_mem "t" ~doc:"Cases for V" Jsont.string ~enc:Fun.id
~enc_case:(function
| R t -> Jsont.Object.Case.value jsont__R (R t)
| S t -> Jsont.Object.Case.value jsont__S t
| A t -> Jsont.Object.Case.value jsont__A t)
[
Jsont.Object.Case.make jsont__R;
Jsont.Object.Case.make jsont__S;
Jsont.Object.Case.make jsont__A;
]
|> Jsont.Object.finishJson output:
# Jsont_bytesrw.encode_string (Jsont.list v_jsont) [ S X; A 42 ];;[{"type":"S","v":"B"},{"type":"Id","v":42}]