meta_conv /

Filename Size Date modified Message
110 B
189 B
2.7 KB
2.0 KB
246 B
81 B
24.1 KB
11.9 KB
589 B
170.7 KB


Using Meta_conv

This is for whom uses already prepared conv(...) encoders and decoders.

Module open

The following modules should be open in the module where you want to use conv(xyz) things:

  • Meta_conv.Open: for types with special conversion like mc_option, mc_leftovers.
  • Module for primitive decoders and encoders, such as xyz_of_int, int_of_xyz, etc.. By convention, it is Xyz_conv, but you can use your own primitive decoders.

Other Meta_conv modules like Meta_conv.Internal are only for decoder/encoder implementers. Normally you do not need it. Unless you implement a new conv(...), you do not need to open or use this module.

with conv(...) extension

For example:

open Meta_conv.Open
open Json_conv

type t = {
     foo: int;
     bar: float;
} with conv(json)

type t = Foo of int
       | Bar of float
with conv(json)


conv(xyz) generates codes for encoders and decoders between the target type Xyz.t and host types whose definitions attached with conv(xyz). It requires the following types and values are defined and accessible in the context:

  • Xyz.t, the target type
  • Xyz_conv module which defines generic decoders and encoders, and decoding error exception. See Meta_conv.Internal.MinimumCoders for details.
  • <xyz>_of_<prims> and <prims>_of_<xyz>, for primitive types <prim> such as int, bool, float, etc.. Usually they are defined in Xyz_conv module.

conv(xyz) is actually a short cut of conv(xyz, Xyz.t, Xyz_conv). This three ary form of conv(...) can control of names of the target type and decoder/encoder module.


Complex types such as open polymorphic variants, open object types are not supported.

For GADT, encoding is supported:

type ('a, 'b) t =
  | A : 'a -> ('a, 'b) t
  | B : int -> (int, 'b) t
  | C of int
  | D of 'b
with conv(json_of)

GADT decoding is impossible, so you must always use with conv(XXX_of) with GADTs. (Think that writing a function of type Json.t -> ('a, 'b) t where 'a is a GADT parameter. It is impossible.)

What are generated

If a type definition of t (or type definitions concatenated with and) is attached with with conv(xyz) notation, CamlP4 module pa_meta_conv generates the following function definitions:

  • xyz_of_t : Encoder of the host type t to the target type xyz
  • t_of_xyz : Decoder of the target type xyz to the host type t, result monad version
  • t_of_xyz_exn : Decoder of the target type xyz to the host type t, exception version

The differece of t_of_xyz and t_of_xyz_exn is the error handling. No exn version reports the decoding error as a value `Error (...), while the exn version raises an exception Xyz_conv.Error.

Meta_conv annotations

Meta_conv annotations (annots) are to control data conversion between OCaml data types and encoded data. Annots can be written at several places in type definitions.

Annots at Data type name

Annots at Data type names can be listed by commas: (: Ignore_unknown_fields, field_check = ... :)

(: Ignore_unknown_fields :) for records and named object types

Ignore excess record fields at decoding:

type t (: Ignore_unknown_fields :) = {
     foo: int;
     bar: float;
  } with conv(json)

Without Ignore_unknown_fields, t_of_json fails if it takes a json with extra fields other than foo or bar. With Ignore_uknown_fields, the extra fields are just discarded.

Ignore_unknown_fields can be overridden by following field_check = <e>.

(: field_check = <e> :) for records and named object types

Use a custom field check <e> at decoding:

type t (: field_check = my_custom_field_checker :) = {
     foo: int;
     bar: float;
  } with conv(json)

The expression <e> must have the same type as Meta_conv.Internal.record_unknown_field_check. <e> is syntactically embeded in the decoder code and executed at each call of decoder function.

field_check = <e> can be overridden by following field_check = <e> or Ignore_unknown_fields.

(: one_of :) for variants and named poly variant types

Decoder of one_of annot tries to decode the target data using the decoders of constructor argument types. It sequentially tries the decoders of the constructors from the top to the bottom, and the first successful result is used:

type t (: one_of :) =
  | Int of int
  | Float of float
  | String of string
with conv(json)

For example, json_of_t of the above first tries to decode the target input as int. If successful, it returns Int <result>. If the decoding as an integer fails, it tries to decode the input as float. If it fails, it tries the last one, the decoder for string.

Note that one_of just tries decoders sequentially. Its efficiency heavily depends on those of sub decoders and their declaration order.

Annots at Variant tag names and Record field names

as "name"

Use as "name" for tag encoding instead of the constructor name itself. Usually it is used to have lowercased variant tag names and uppercase record field names:

type t = Hg as "hg" | Git as "git" with conv(json)

type t = Name as "name" of string with conv(json)

type t = { name as "Name" : string } with conv(json)

type t = { type_ as "type" : string } with conv(json)

You can also write the record tag name annotations in the form of type_conv record field generator parameter conv(name("blah")):

type t = { type_ : string with conv(name("type")) } with conv(json)

Type_conv record field generator parameters

You can also specify record field special handling by type_conv's record field generator parameters, fieldname : type with conv(e1, e2, ...);.


Same as as "blah" record field annotation

type t = { type_ : string with conv(name("type")) } with conv(json)

A good point of conv(name("blah")) is that you can use with custom encoder and decoder specs. (as "blah" cannot live with them.)


Same as conv(name("blah")), but only enabled for conv(json)


Use expr as the custom encoder for json converter.

conv(of_json(custom decoder for json))

Use expr as the custom decoder for json converter.

Here is an example of custom decoder and encoder:

let json_string_of_int = fun x -> json_of_string (string_of_int x)
let int_of_json_string = fun ?trace j -> Meta_conv.Result.fmap int_of_string (string_of_json ?trace j)

type t = {
  x : int with conv(json_of( json_string_of_int ),
                    of_json( int_of_json_string ));
} with conv(json)

let () =
  assert (t_of_json (json_of_t {x = 10}) = `Ok {x = 10})

Here, OCaml int is coded as a string in JSON.

Special type names

Type names whose decoding/encoding work specially

(<host_type>, <target_type>) mc_result

This is equal to (<host_type>, <target_type> Error.t) Result.t.

The decoding and encoding work specially if the target type specified by conv(...) is equal to <targe_type>. Otherwise, its codings are done normally.

If the target type matches with conv(...), the target value is decoded using the decoder of <host_type>. However, the result is wrapped by the result type: any decoding error is reported as a successful result embeded in the result type (<host_type>, <target_type> Error.t) Result.t. Encoding of this result type is done in the opposite way: the encoding of `Error(desc, target, trace) is target.

Type names work specially only when used as record/object field types

These type names work only specially when used as record/object field types: like { label : t mc_option }, but not like { label : t mc_option list }.

t mc_option

t sexp_option

If an OCaml record/object field has type t mc_option, the field is considererd optional in the target record/object. If the target record/object misses these fields, None is used for them in the host value. If the host value has None for these fields, they are omitted in the target record/object.

sexp_option works as same as mc_option, introduced to live with sexplib nicely.

<target_type> mc_leftovers

<target_type> mc_leftovers is equal to (string, <target_type>) list.

The decoding and encoding work specially if the target type specified by conv(...) is equal to <targe_type>. Otherwise, its codings are done normally.

If the target type matches with conv(...), at decoding, the field with mc_leftovers takes the target fields which are not listed in the other OCaml record/boecjt fields, in undecoded format. Encoding of mc_leftovers field uses its raw record/object value ''as is''. The field name of the OCaml record/record does not appear as a tag in the encoded data.

  • The type of the field must be <target_type> mc_leftovers
  • mc_leftovers field can exist at most one for an OCaml record/object type. (CHECK IS NOT YET IMPLEMENTED)
  • You cannot specify with Ignore_unknown_fields type name annotation.

t mc_embeded

Embeded fields. t must have the same kind (record/object) as the declared data type has.

At decoding, the target fields are firstly used to build normal fields, excluding leftovers and embededs. Then the remained target fields are used to fill mc_embeded fields. The decoders for t at t mc_embeded fields consume the remained target fields in the order of their declaration. The finally remained target fields are used for leftovers. Any error during this process is reported as decoding failure of the parent type.

In practice, the type t of t mc_embeded should not have leftover field, since it just consume all the target fields there.

t mc_option_embeded

It is as same as t mc_embeded, but wrapped with option type. Any decoding error at this mc_option_embeded field set the field value to None, and the decoding continues. The target fields are not consumed at failure.

Writing encoders and decoders for a new target type

Modules and values to be accessible

For actual examples, see meta_conv/json or meta_conv/ocaml directory.

Link with meta_conv

meta_conv packed module library must be linked with programs which use meta_conv.

Conversion module

The conversion module for the basic encoders and decoders must be accessible in the name given at with conv(...) in the context. For example, if you write with conv(foobar), then Foobar_conv must exist.

There is an example of a convsersion module, json/ See this file for details.