Module Decoders_sexplib.Decode

Turn S-expressions into Ocaml values via Sexplib.

Following the convention of Sexplib0.Sexp_conv.hashtbl_of_sexp, we consider an S-expression to be an "object" if it is a list of two-element lists. For example:

((field1 value1) (field2 (value2 value3)))

Following `dune` conventions, we also allow as "objects" S-expressions like:

((field1 value1) (field2 value2 value3))

These two S-expressions will be treated in the same way by the object combinators below (e.g. field).

Like YAML, fields of an object are not necessarily atoms. To handle these, look for the primed combinators (e.g. keys').

include Decoders.Decode.S with type value = Sexplib0.Sexp.t
type value = Sexplib0.Sexp.t

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

type error = value Decoders.Error.t
val pp_error : Stdlib.Format.formatter -> error -> unit
val string_of_error : error -> string
val of_string : string -> ( value, error ) Decoders.Util.My_result.t
val of_file : string -> ( value, error ) Decoders.Util.My_result.t
type 'a decoder = ( value, 'a ) Decoders.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 ) Decoders.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 Decoders.Decoder.Infix
val (>>=) : ( 'i -> ( 'a, 'i Decoders.Error.t ) Stdlib.result ) -> ( 'a -> 'i -> ( 'b, 'i Decoders.Error.t ) Stdlib.result ) -> 'i -> ( 'b, 'i Decoders.Error.t ) Stdlib.result
val (>|=) : ( 'i -> ( 'a, 'i Decoders.Error.t ) Stdlib.result ) -> ( 'a -> 'b ) -> 'i -> ( 'b, 'i Decoders.Error.t ) Stdlib.result
val (<*>) : ( 'i -> ( 'a -> 'b, 'i Decoders.Error.t ) Stdlib.result ) -> ( 'i -> ( 'a, 'i Decoders.Error.t ) Stdlib.result ) -> 'i -> ( 'b, 'i Decoders.Error.t ) Stdlib.result
type ('i, 'o) t_let = 'i -> ( 'o, 'i Decoders.Error.t ) Stdlib.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 ) Decoders.Util.My_result.t

Run a decoder on some input.

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

Run a decoder on a string.

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

Run a decoder on a file.

module Pipeline : sig ... end