opam-version: "2.0"
synopsis: "Elm-inspired decoders for Ocaml"
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:

1. Get your data from somewhere. Now you have a `string`.
2. *Parse* the `string` as JSON (or YAML). Now you have a `Yojson.Basic.json`, or maybe an `Ezjsonm.value`, or perhaps a `Ocaml.yaml`.
3. *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.

```ocaml
utop # module D = Decoders_yojson.Basic.Decode;;
module D = Decoders_yojson.Basic.Decode
```

Let's set our sights high and decode an integer.

```ocaml
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.

```ocaml
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.

```ocaml
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.

```ocaml
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?

```ocaml
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.

```ocaml
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.

```ocaml
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.

```ocaml
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:

```ocaml
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
```"""
maintainer: "Matt Bray <matt@aestheticintegration.com>"
authors: "Matt Bray <matt@aestheticintegration.com>"
license: "ISC"
homepage: "https://github.com/mattjbray/ocaml-decoders"
doc: "https://mattjbray.github.io/ocaml-decoders/decoders"
bug-reports: "https://github.com/mattjbray/ocaml-decoders/issues"
depends: [
  "ocaml"
  "jbuilder"
  "cppo" {build}
  "containers"
]
build: ["jbuilder" "build" "-p" name "-j" jobs]
conflicts: [ "dune" {>="1.7.0"} ]
dev-repo: "git+ssh://git@github.com/mattjbray/ocaml-decoders.git"
url {
  src:
    "https://github.com/mattjbray/ocaml-decoders/releases/download/0.1.0/decoders-0.1.0.tbz"
  checksum: "md5=a97f370f3f7ff68d085ac01e05fab493"
}
