decoders

Elm-inspired decoders for Ocaml
Library decoders
Module Decoders . Decode . Make

Parameters

module M : Decodeable

Signature

type value = M.value

The type of values to be decoded (e.g. JSON or Yaml).

type error = value Error.t
val pp_error : Format.formatter -> error -> unit
val string_of_error : error -> string
val of_string : string -> ( value, error ) Util.My_result.t
val of_file : string -> ( value, error ) Util.My_result.t
type 'a decoder = ( M.value, 'a ) Decoder.t

The type of decoders.

Use the functions below to construct decoders for your data types.

To run a decoder, pass it to decode_value.

Primitives

val string : string decoder

Decode a string.

val int : int decoder

Decode an int.

val float : float decoder

Decode a float.

val bool : bool decoder

Decode a bool.

val null : unit decoder

Decode a null.

val value : value decoder

Decode a literal value.

Lists

val list : 'a decoder -> 'a list decoder

Decode a collection into an OCaml list.

val list_filter : 'a option decoder -> 'a list decoder

Decode a collection into an OCaml list, skipping elements for which the decoder returns None.

val list_fold_left : ( 'a -> 'a decoder ) -> 'a -> 'a decoder

Decode a collection with an accumulator.

If we consider that an 'a decoder is basically a type alias for json -> ('a, error) result, the signature of this function is comparable to that of List.fold_left:

val List.fold_left : ('a ->   'b ->                 'a) -> 'a -> 'b list ->                 'a
val list_fold_left : ('a -> json -> ('a, error) result) -> 'a ->    json -> ('a, error) result
val list_fold_left : ('a ->                 'a decoder) -> 'a ->                    'a decoder
val array : 'a decoder -> 'a array decoder

Decode a collection into an OCaml array.

val index : int -> 'a decoder -> 'a decoder

Decode a collection, requiring a particular index.

val uncons : ( 'a -> 'b decoder ) -> 'a decoder -> 'b decoder

fst |> uncons rest decodes the first element of a list using fst, then decodes the remainder of the list using rest.

For example, to decode this s-expression:

(library
  (name decoders))

we can use this decoder:

string |> uncons (function
  | "library" -> field "name" string
  | _ -> fail "Expected a library stanza")

As another example, say you have a JSON array that starts with a string, then a bool, then a list of integers:

["hello", true, 1, 2, 3, 4]

We could decode it like this:

let (>>=::) fst rest = uncons rest fst

let decoder : (string * bool * int list) decoder =
  string >>=:: fun the_string ->
  bool >>=:: fun the_bool ->
  list int >>= fun the_ints ->
  succeed (the_string, the_bool, the_ints)

(If you squint, the uncons operator >>=:: kind of looks like the cons operator ::.)

Object primitives

val field : string -> 'a decoder -> 'a decoder

Decode an object, requiring a particular field.

val field_opt : string -> 'a decoder -> 'a option decoder

Decode an object, where a particular field may or may not be present.

For example, (field_opt "hello" int):

  • when run on {"hello": 123}, will succeed with Some 123
  • when run on {"hello": null}, will fail
  • when run on {"world": 123}, will succeed with None
  • when run on ["a", "list", "of", "strings"], will fail
val field_opt_or : default:'a -> string -> 'a decoder -> 'a decoder

Similar to field_opt but with a default value.

  • since 0.7
val single_field : ( string -> 'a decoder ) -> 'a decoder

Decode an object, requiring exactly one field.

val at : string list -> 'a decoder -> 'a decoder

Decode a nested object, requiring certain fields.

Inconsistent structure

val maybe : 'a decoder -> 'a option decoder

maybe d is a decoder that always succeeds. If d succeeds with x, then maybe d succeeds with Some x, otherwise if d fails, then maybe d succeeds with None.

For example, maybe (field "hello" int):

  • when run on {"hello": 123}, will succeed with Some 123
  • when run on {"hello": null}, will succeed with None
  • when run on {"world": 123}, will succeed with None
  • when run on ["a", "list", "of", "strings"], will succeed with None
val nullable : 'a decoder -> 'a option decoder

nullable d will succeed with None if the JSON value is null. If the JSON value is non-null, it wraps the result of running x in a Some.

For example, field "hello" (nullable int):

  • when run on {"hello": 123}, will succeed with Some 123
  • when run on {"hello": null}, will succeed with None
  • when run on {"world": 123}, will fail
  • when run on ["a", "list", "of", "strings"], will fail
val one_of : (string * 'a decoder) list -> 'a decoder

Try a sequence of different decoders.

val pick : (string * 'a decoder decoder) list -> 'a decoder

pick choices picks a single choice, like one_of. However, each element of choices can look at the value, decide if it applies (e.g. based on the value of a single field, like a "kind" or "type" field), and if it does, returns a decoder for the rest of the value.

If a choice is made, even if the returned sub-decoder fails, the error message will totally ignore the rest of the choices and only be about the choice that was initially made.

  • since 0.7
val decode_sub : value -> 'a decoder -> 'a decoder

decode_sub value sub_dec uses sub_dec to decode value. This is useful when one has a value on hand.

  • since 0.7

Mapping

val map : ( 'a -> 'b ) -> 'a decoder -> 'b decoder

Map over the result of a decoder.

val apply : ( 'a -> 'b ) decoder -> 'a decoder -> 'b decoder

Try two decoders and then combine the result. We can use this to decode objects with many fields (but it's preferable to use Infix.(>>=) - see the README).

Working with object keys

val keys : string list decoder

Decode all of the keys of an object to a list of strings.

val key_value_pairs : 'v decoder -> (string * 'v) list decoder

Decode an object into a list of key-value pairs.

val key_value_pairs_seq : ( string -> 'v decoder ) -> 'v list decoder

Decode an object into a list of values, where the value decoder depends on the key.

val keys' : 'k decoder -> 'k list decoder

keys' is for when your keys might not be strings - probably only likely for Yaml.

val key_value_pairs' : 'k decoder -> 'v decoder -> ('k * 'v) list decoder
val key_value_pairs_seq' : 'k decoder -> ( 'k -> 'v decoder ) -> 'v list decoder

Fancy decoding

val succeed : 'a -> 'a decoder

A decoder that always succeeds with the argument, ignoring the input.

val fail : string -> 'a decoder

A decoder that always fails with the given message, ignoring the input.

val fail_with : error -> 'a decoder
val from_result : ( 'a, error ) Util.My_result.t -> 'a decoder
val and_then : ( 'a -> 'b decoder ) -> 'a decoder -> 'b decoder

Create decoders that depend on previous results.

val fix : ( 'a decoder -> 'a decoder ) -> 'a decoder

Recursive decoders.

let my_decoder = fix (fun my_decoder -> ...) allows you to define my_decoder in terms of itself.

val of_of_string : msg:string -> ( string -> 'a option ) -> 'a decoder

Create a decoder from a function of_string : string -> 'a option

module Infix : sig ... end
include module type of Infix
include module type of Decoder.Infix
val (>>=) : ( 'i -> ( 'a, 'i Error.t ) result ) -> ( 'a -> 'i -> ( 'b, 'i Error.t ) result ) -> 'i -> ( 'b, 'i Error.t ) result
val (>|=) : ( 'i -> ( 'a, 'i Error.t ) result ) -> ( 'a -> 'b ) -> 'i -> ( 'b, 'i Error.t ) result
val (<*>) : ( 'i -> ( 'a -> 'b, 'i Error.t ) result ) -> ( 'i -> ( 'a, 'i Error.t ) result ) -> 'i -> ( 'b, 'i Error.t ) result
type ('i, 'o) t_let = 'i -> ( 'o, 'i Error.t ) result
val let+ : ( 'i, 'a ) t_let -> ( 'a -> 'b ) -> ( 'i, 'b ) t_let
val and+ : ( 'i, 'a ) t_let -> ( 'i, 'b ) t_let -> ( 'i, 'a * 'b ) t_let
val let* : ( 'i, 'a ) t_let -> ( 'a -> ( 'i, 'b ) t_let ) -> ( 'i, 'b ) t_let
val and* : ( 'i, 'a ) t_let -> ( 'i, 'b ) t_let -> ( 'i, 'a * 'b ) t_let
val (<$>) : ( 'a -> 'b ) -> 'a decoder -> 'b decoder

Running decoders

val decode_value : 'a decoder -> value -> ( 'a, error ) Util.My_result.t

Run a decoder on some input.

val decode_string : 'a decoder -> string -> ( 'a, error ) Util.My_result.t

Run a decoder on a string.

val decode_file : 'a decoder -> string -> ( 'a, error ) Util.My_result.t

Run a decoder on a file.

module Pipeline : sig ... end