ppx_deriving_encoding

Ppx deriver for json-encoding
README

ppx_deriving_encoding is a json-encoding ppx deriver.

type x = {
  a : string;
  b : int list;
  c : Ezjsonm.value option;
} [@@deriving encoding]

will produce the encoding:

let x_enc : x encoding =
  conv
    (fun {a; b; c} -> (a, b, c))
    (fun (a, b, c) -> {a; b; c})
    (obj3
      (req "a" string)
      (req "b" (list int))
      (opt "c" any_ezjson_value))

Most of regular json types are handled.
GADT and variant inheritance are not handled yet.

Field Options

  • [@dft expr]
    Default field instead

    a : x [@dft expr]
    

    will produce a field :

    (dft "a" x_enc expr)
    
  • [@opt]
    Optional field (undefined)

    a : x [@opt]
    

    will produce a field :

    (opt "a" x_enc)
    
  • [@ddft expr]
    Default field always constructed

    a : x [@ddft expr]
    

    will produce a field :

    (dft ~construct:true "a" x_enc expr)
    
  • [@req] (for an option type)
    Nullable field instead of dft "..." (option enc) None

    a : x option [@req]
    

    will produce a field :

    (req "a" (option x_enc))
    
  • [@key "name"]
    Specify the name of the field

    a : x [@key "name"]
    

    will produce a field :

    (req "name" x_enc)
    
  • [@title expr]
    Specify the title of the field

    a : x [@title expr]
    

    will produce a field :

    (req ~title:expr "a" x_enc)
    
  • [@description expr]
    Specify the description of the field

    a : x [@description expr]
    

    will produce a field :

    (req ~description:expr "a" x_enc)
    
  • [@exclude expr]
    Exclude a field from an encoding

    {
      a : x;
      b : y; [@exclude y_default]
      c : z;
    }
    

    will produce :

    conv
      (fun {a; _; c} -> (a, c))
      (fun (a, c) -> {a; b = y_default; c})
        (obj2
          (req "a" x_enc)
          (req "c" z_enc))
    
  • [@merge]
    Merge an field object instead of creating another field

    {
      a : x; [@merge]
      b : z;
    }
    

    will produce :

    conv
      (fun {a; _; c} -> (a, c))
      (fun (a, c) -> {a; b = y_default; c})
      (merge_objs x_enc (obj1 (req "c" z_enc)))
    
  • [@camel] or [@snake]
    format the field in snake or camel case

  • [@set <encoding>], [@map <key_encoding>]
    create an encoding from Set or Map modules

    module SMap = Map.Make(String)
    type x = { a : int SMap.t [@map string] } [@@deriving encoding]
    

    will produce :

    let x_enc =
      conv (fun { a } -> a) (fun a -> { a }) @@
      (obj1
         (req "a"
            (conv
              SMap.bindings
              (fun l ->
                 List.fold_left (fun acc -> fun (k, v) -> SMap.add k v acc) SMap.empty l)
               (list (tup2 string int)))))
    

General options

  • [@assoc]

    Create an assoc encoding

    (string * x) list [@assoc]
    

    will produce :

    assoc x_enc
    
  • [@enum]

    Create an string enum encoding

    [ `A | `B | `C ]
    

    will produce :

    string_enum [ "a", `A ; "b" `B; "c", `C ]
    

    For normal type constructor, you need to use the flag attribute:

    type t = A | B | C [@@deriving encoding {enum}]
    
  • [@encoding expr]

    Assign a generic encoding

  • [@obj1 "name"] / [@wrap "name"]

    Wrap an encoding inside a obj1 encoding

Tuple options

  • [@object]

    Create an object encoding from a tuple

    ( w, x [@exclude x_default], y [@key "name"], z ) [@object]
    

    will produce:

    conv
      (fun (w, _, y, z) -> (w, y, z))
      (fun (w, y, z) -> (w, x_default, y, z))
        (obj3
          (req "0" w_enc)
          (req "name" y_enc)
          (req "3" z_enc))
    

Variant options

If it is not a string enumeration, any constructor or polymorphic variant will produce a union encoding.
Any case of the union can receive [@title expr], [@description expr], [@kind "kind"] attributes.

[@kind "kind_name"] will add the encoding

(obj1 (req "kind" (constant "kind_name")))

to allow several constructor with the same type to be well desctructed.

If the string literal is omitted, the kind name will be derived from the constructor name.
The label can also be changed from "kind" using [@kind_label "kind_label_name"].

For an empty constructor, the default behaviour will use the constant encoding with the name derived from the name of the constructor.
It can be set to empty with the [@empty] attribute.

type t =
  | A of x [@kind "a"]
  | B of y
  | C of x [@kind]
  | D of z [@kind "bla"] [@kind_label "category"]
  | E
  | F [@empty]
[@@deriving encoding]

will produce :

let enc =
  union [
    case
      (conv (fun x -> (), x) (fun ((), x) -> x)
        (merge_objs (obj1 (req "kind" (constant "a"))) x_enc))
      (function A x -> Some x | _ -> None)
      (fun x -> A x);
    case
      y_enc
      (function B x -> Some x | _ -> None)
      (fun x -> B x);
    case
      (conv (fun x -> (), x) (fun ((), x) -> x)
        (merge_objs (obj1 (req "kind" (constant "c"))) x_enc))
      (function C x -> Some x | _ -> None)
      (fun x -> C x);
    case
      (conv (fun x -> (), x) (fun ((), x) -> x)
        (merge_objs (obj1 (req "category" (constant "bla"))) z_enc))
      (function D x -> Some x | _ -> None)
      (fun x -> D x);
    case
      (constant "e")
      (function E -> Some () | _ -> None)
      (fun () -> E);
    case
      empty
      (function F -> Some () | _ -> None)
      (fun () -> F);
  ]

Top type options

  • ignore
    wrap an object encoding to ignore other fields

    type t = {
      a : x;
      b : y;
    } [@@deriving encoding {ignore}]
    

    will produce :

    let enc =
      conv
        (fun x -> (), x)
        (fun ((), x) -> x)
        (merge_objs
          unit
          (conv
            (fun {a; b} -> (a, b))
            (fun (a, b) -> {a; b})
              (obj2
                (req "a" x_enc)
                (req "b" y_enc))))
    

    It can also be used as an attribute for records in constructor:

    type x =
      | A of { a : y } [@ignore]
      | B of t
    
  • remove_prefix

    Remove prefixes of record

    type x = {
      xyza : a;
      xyzb : b;
    } [@@deriving encoding {remove_prefix = "xy"}] (* or {remove_prefix = 2} *)
    

    will produce :

    let x_enc =
      conv
        (fun {x_a; x_b} -> (x_a, x_b))
        (fun (x_a, x_b) -> {x_a; x_b})
        (obj2
          (req "za" a_enc)
          (req "zb" b_enc))
    

    By default, the ppx will try to remove the longest common chain.
    It the example above, if remove_prefix wasn't mentionned, the ppx would have removed xyz.
    You can also remove this behaviour with [@@deriving encoding {remove_prefix=false}].

  • recursive

    Wrap an encoding in a recursive construction

    type x =
      | A of x
      | B [@@deriving encoding {recursive}]
    

    will produce :

    let x_enc =
      mu "x"
        (fun enc -> union [
          case enc (function A x -> Some x | _ -> None) (fun x -> A x);
          case empty (function B -> Some () | _ -> None) (fun () -> B)
        ])
    
  • title, description

    Wrap en encoding to add some description

    type x = y [@@deriving encoding {title = "title"; description = "descr"}]
    

    will produce :

    let x_enc =
      def "x" ~title:"title" ~description:"descr" y_enc
    
  • schema

    Wrap an encoding to add a schema

    type x = y [@@deriving encoding {schema = sch}]
    

    will produce :

    let x_enc =
      conv (fun x -> x) (fun x -> x)
        ~schema:sch y_enc
    
  • option

    By default an option field will be dealt as dft "field_name" (option enc) None to be able to catch undefined and null values, but it is sometime not the wanted behaviour and in some cases even not allowed (for example if the encoding dealing with the type is nullable: dft "name" (option any_ezjsonm) None will break the execution).
    You can change the default behaviour:

    type x = {
      a : string option
    } [@@deriving encoding {option = "req"}]
    type y = {
      a : string option
    } [@@deriving encoding {option = "opt"}]
    type z = {
      a : string option
    } [@@deriving encoding {option = "dft"}]
    

    will produce :

    let x_enc = obj1 (req "a" (option string))
    let y_enc = obj1 (opt "a" string)
    let z_enc = obj1 (dft "a" (option string) None)
    
  • debug

    Force the printing of the produced encoding during compilation

  • name

    By default the name of the encoding produced is <typename>_enc,
    Using this name option, you can choose to change it.

  • module_name

    This ppx deriver can be used with other module than Json_encoding if they follow the same interface than Json_encoding (or rather a sub part of it that you can get in src/utils.ml).
    This can be done by mentionning the module_name option.
    As a dummy example:

    open Json_encoding
    
    type x = int [@@deriving encoding {module_name=""}]
    

    will work fine.

  • camel or snake
    Flag to format all fields in camel or snake case

  • wrap
    Wrap the type in an obj1

Compilation environnement variables

Here are some environnement variables that can be useful:

  • PPX_ENCODING_DEBUG:

    Can be set to true, false or some verbose level. It will print all the expression produced by the ppx.

  • PPX_ENCODING_MODULE:

    Set the module name for all expression derived using the environnement.

  • PPX_ENCODING_FAKE:

    Will not produce any encoding. It can be useful if you just wish to copy some file using this deriver but you don't have access to json-data-encoding at this time.

Install
Published
27 Apr 2022
Sources
ppx_deriving_encoding-0.3.0.tar.gz
md5=3e928d75f5b165a0ad511d806cab11e5
sha512=97ecaca0f2fad0ad8c5e82d910f665f381796995ee1133f26032f9caa036bcf2a9249c4020e90e935946aafff7e7adedac1bcf817391c35ca00bb97dcffe677b
Dependencies
odoc
with-doc
ppxlib
>= "0.18.0"
ocaml
>= "4.08"
dune
>= "2.8"
Reverse Dependencies