package decoders
Install
Dune Dependency
Authors
Maintainers
Sources
sha256=291e46eab27e1c7d14b9dca366e37a0b54817ab1801dd60a565e7270fd3abb0a
md5=a97f370f3f7ff68d085ac01e05fab493
Description
A combinator library for "decoding" JSON-like values into your own Ocaml types, inspired by Elm's Json.Decode
and Json.Encode
.
Eh?
An Ocaml program having a JSON (or YAML) data source usually goes something like this:
- Get your data from somewhere. Now you have a
string
. - Parse the
string
as JSON (or YAML). Now you have aYojson.Basic.json
, or maybe anEzjsonm.value
, or perhaps aOcaml.yaml
. - Decode the JSON value to an Ocaml type that's actually useful for your program's domain.
This library helps with step 3.
Getting started
Install one of the supported decoder backends:
opam install decoders-ezjsonm # For Ezjsonm
opam install decoders-ocyaml # For Ocyaml
opam install decoders-yojson # For Yojson
npm install --save-dev bs-decoders # For Bucklescript
Now we can start decoding stuff!
First, a module alias to save some keystrokes. In this guide, we'll parse JSON
using Yojson
's Basic
variant.
utop # module D = Decoders_yojson.Basic.Decode;;
module D = Decoders_yojson.Basic.Decode
Let's set our sights high and decode an integer.
utop # D.decode_value D.int (`Int 1);;
- : (int, error) result = Ok 1
Nice! We used decode_value
, which takes a decoder
and a value
(in this
case a Yojson.Basic.json
) and... decodes the value.
utop # D.decode_value;;
- : 'a decoder -> value -> ('a, error) result = <fun>
For convenience we also have decode_string
, which takes a string
and calls
Yojson
's parser under the hood.
utop # D.decode_string D.int "1";;
- : (int, error) result = Ok 1
What about a list
of int
s? Here's where the "combinator" part comes in.
utop # D.decode_string D.(list int) "[1,2,3]";;
- : (int list, error) result = Ok [1; 2; 3]
Success!
Ok, so what if we get some unexpected JSON?
utop # #install_printer D.pp_error;;
utop # D.decode_string D.(list int) "[1,2,true]";;
- : (int list, error) result =
Error while decoding a list: element 2: Expected an int, but got true
Generic decoders
Suppose our program deals with users and roles. We want to decode our JSON input into these types.
type role = Admin | User
type user =
{ name : string
; roles : role list
}
Let's define our decoders. We'll write a module functor so we can re-use the same decoders across different JSON libraries, with YAML input, or with Bucklescript.
module My_decoders(D : Decoders.Decode.S) = struct
open D
let role : role decoder =
string >>= function
| "ADMIN" -> succeed Admin
| "USER" -> succeed User
| _ -> fail "Expected a role"
let user : user decoder =
field "name" string >>= fun name ->
field "roles" (list role) >>= fun roles ->
succeed { name; roles }
end
module My_yojson_decoders = My_decoders(Decoders_yojson.Basic.Decode)
Great! Let's try them out.
utop # open My_yojson_decoders;;
utop # D.decode_string role {| "USER" |};;
- : (role, error) result = Ok User
utop # D.decode_string D.(field "users" (list user))
{| {"users": [{"name": "Alice", "roles": ["ADMIN", "USER"]},
{"name": "Bob", "roles": ["USER"]}]}
|};;
- : (user list, error) result =
Ok [{name = "Alice"; roles = [Admin; User]}; {name = "Bob"; roles = [User]}]
Let's introduce an error in the JSON:
utop # D.decode_string D.(field "users" (list user))
{| {"users": [{"name": "Alice", "roles": ["ADMIN", "USER"]},
{"name": "Bob", "roles": ["SUPER_USER"]}]}
|};;
- : (user list, error) result =
Error
in field "users":
while decoding a list:
element 1:
in field "roles":
while decoding a list:
element 0: Expected a role, but got "SUPER_USER"
We get a nice pointer that we forgot to handle the SUPER_USER
role.
Release
After updating CHANGES.md:
npm version <newversion>
dune-release tag
dune-release -p decoders,decoders-ezjsonm,decoders-yojson
Published: 28 Sep 2018
README
ocaml-decoders: Elm-inspired decoders for Ocaml
A combinator library for "decoding" JSON-like values into your own Ocaml types, inspired by Elm's Json.Decode
and Json.Encode
.
An Ocaml program having a JSON (or YAML) data source usually goes something like this:
Get your data from somewhere. Now you have a
string
.Parse the
string
as JSON (or YAML). Now you have aYojson.Basic.json
, or maybe anEzjsonm.value
, or perhaps aOcaml.yaml
.Decode the JSON value to an Ocaml type that's actually useful for your program's domain.
This library helps with step 3.
Getting started
Install one of the supported decoder backends:
opam install decoders-ezjsonm # For Ezjsonm
opam install decoders-ocyaml # For Ocyaml
opam install decoders-yojson # For Yojson
npm install --save-dev bs-decoders # For Bucklescript
Now we can start decoding stuff!
First, a module alias to save some keystrokes. In this guide, we'll parse JSON using Yojson
's Basic
variant.
utop # module D = Decoders_yojson.Basic.Decode;;
module D = Decoders_yojson.Basic.Decode
Let's set our sights high and decode an integer.
utop # D.decode_value D.int (`Int 1);;
- : (int, error) result = Ok 1
Nice! We used decode_value
, which takes a decoder
and a value
(in this case a Yojson.Basic.json
) and... decodes the value.
utop # D.decode_value;;
- : 'a decoder -> value -> ('a, error) result = <fun>
For convenience we also have decode_string
, which takes a string
and calls Yojson
's parser under the hood.
utop # D.decode_string D.int "1";;
- : (int, error) result = Ok 1
What about a list
of int
s? Here's where the "combinator" part comes in.
utop # D.decode_string D.(list int) "[1,2,3]";;
- : (int list, error) result = Ok [1; 2; 3]
Success!
Ok, so what if we get some unexpected JSON?
utop # #install_printer D.pp_error;;
utop # D.decode_string D.(list int) "[1,2,true]";;
- : (int list, error) result =
Error while decoding a list: element 2: Expected an int, but got true
Generic decoders
Suppose our program deals with users and roles. We want to decode our JSON input into these types.
type role = Admin | User
type user =
{ name : string
; roles : role list
}
Let's define our decoders. We'll write a module functor so we can re-use the same decoders across different JSON libraries, with YAML input, or with Bucklescript.
module My_decoders(D : Decoders.Decode.S) = struct
open D
let role : role decoder =
string >>= function
| "ADMIN" -> succeed Admin
| "USER" -> succeed User
| _ -> fail "Expected a role"
let user : user decoder =
field "name" string >>= fun name ->
field "roles" (list role) >>= fun roles ->
succeed { name; roles }
end
module My_yojson_decoders = My_decoders(Decoders_yojson.Basic.Decode)
Great! Let's try them out.
utop # open My_yojson_decoders;;
utop # D.decode_string role {| "USER" |};;
- : (role, error) result = Ok User
utop # D.decode_string D.(field "users" (list user))
{| {"users": [{"name": "Alice", "roles": ["ADMIN", "USER"]},
{"name": "Bob", "roles": ["USER"]}]}
|};;
- : (user list, error) result =
Ok [{name = "Alice"; roles = [Admin; User]}; {name = "Bob"; roles = [User]}]
Let's introduce an error in the JSON:
utop # D.decode_string D.(field "users" (list user))
{| {"users": [{"name": "Alice", "roles": ["ADMIN", "USER"]},
{"name": "Bob", "roles": ["SUPER_USER"]}]}
|};;
- : (user list, error) result =
Error
in field "users":
while decoding a list:
element 1:
in field "roles":
while decoding a list:
element 0: Expected a role, but got "SUPER_USER"
We get a nice pointer that we forgot to handle the SUPER_USER
role.
Release
After updating CHANGES.md:
npm version <newversion>
dune-release tag
dune-release -p decoders,decoders-ezjsonm,decoders-yojson
Dependencies (4)
-
containers
>= "2.8"
-
cppo
build
-
jbuilder
>= "1.0+beta9"
- ocaml
Dev Dependencies
None
Used by (3)
-
decoders-ezjsonm
< "0.2.0"
-
decoders-jsonm
< "0.7.0"
-
decoders-yojson
< "0.3.0"
Conflicts (1)
-
dune
>= "1.7.0"