this repo has no description
1opam-version: "2.0"
2synopsis: "Elm-inspired decoders for Ocaml"
3description: """
4A combinator library for "decoding" JSON-like values into your own Ocaml types, inspired by Elm's `Json.Decode` and `Json.Encode`.
5
6> Eh?
7
8An Ocaml program having a JSON (or YAML) data source usually goes something like this:
9
101. Get your data from somewhere. Now you have a `string`.
112. *Parse* the `string` as JSON (or YAML). Now you have a `Yojson.Basic.json`, or maybe an `Ezjsonm.value`, or perhaps a `Ocyaml.yaml`.
123. *Decode* the JSON value to an Ocaml type that's actually useful for your program's domain.
13
14This library helps with step 3.
15
16## Getting started
17
18Install one of the supported decoder backends:
19
20### For ocaml
21
22```
23opam install decoders-ezjsonm # For Ezjsonm
24opam install decoders-ocyaml # For Ocyaml
25opam install decoders-yojson # For Yojson
26```
27
28### For bucklescript
29
30```
31npm install --save-dev bs-decoders
32```
33
34## Decoding
35
36Now we can start decoding stuff!
37
38First, a module alias to save some keystrokes. In this guide, we'll parse JSON
39using `Yojson`'s `Basic` variant.
40
41```ocaml
42utop # module D = Decoders_yojson.Basic.Decode;;
43module D = Decoders_yojson.Basic.Decode
44```
45
46Let's set our sights high and decode an integer.
47
48```ocaml
49utop # D.decode_value D.int (`Int 1);;
50- : (int, error) result = Ok 1
51```
52
53Nice! We used `decode_value`, which takes a `decoder` and a `value` (in this
54case a `Yojson.Basic.json`) and... decodes the value.
55
56```ocaml
57utop # D.decode_value;;
58- : 'a decoder -> value -> ('a, error) result = <fun>
59```
60
61For convenience we also have `decode_string`, which takes a `string` and calls
62`Yojson`'s parser under the hood.
63
64```ocaml
65utop # D.decode_string D.int "1";;
66- : (int, error) result = Ok 1
67```
68
69What about a `list` of `int`s? Here's where the "combinator" part comes in.
70
71```ocaml
72utop # D.decode_string D.(list int) "[1,2,3]";;
73- : (int list, error) result = Ok [1; 2; 3]
74```
75
76Success!
77
78Ok, so what if we get some unexpected JSON?
79
80```ocaml
81utop # #install_printer D.pp_error;;
82utop # D.decode_string D.(list int) "[1,2,true]";;
83- : (int list, error) result =
84Error while decoding a list: element 2: Expected an int, but got true
85```
86
87## Complicated JSON structure
88
89To decode a JSON object with many fields, we can use the bind operator (`>>=`) from the `Infix` module.
90
91```ocaml
92type my_user =
93 { name : string
94 ; age : int
95 }
96
97let my_user_decoder : my_user decoder =
98 let open D in
99 field "name" string >>= fun name ->
100 field "age" int >>= fun age ->
101 succeed { name; age }
102```
103
104We can also use bind to decode objects with inconsistent structure. Say, for
105example, our JSON is a list of shapes. Squares have a side length, circles have
106a radius, and triangles have a base and a height.
107
108```json
109[{ "shape": "square", "side": 11 },
110 { "shape": "circle", "radius": 5 },
111 { "shape": "triange", "base": 3, "height": 7 }]
112```
113
114We could represent these types in OCaml and decode them like this:
115
116```ocaml
117type shape =
118 | Square of int
119 | Circle of int
120 | Triangle of int * int
121
122let square_decoder : shape decoder =
123 D.(field "side" int >>= fun s -> succeed (Square side))
124
125let circle_decoder : shape decoder =
126 D.(field "radius" int >>= fun r -> succeed (Circle r))
127
128let triangle_decoder : shape decoder =
129 D.(
130 field "base" int >>= fun b ->
131 field "height" int >>= fun h ->
132 succeed (Triangle (b, h))
133 )
134
135let shape_decoder : shape decoder =
136 let open D in
137 field "shape" string >>= function
138 | "square" -> square_decoder
139 | "circle" -> circle_decoder
140 | "triangle" -> triangle_decoder
141 | _ -> fail "Expected a shape"
142
143
144let decode_list (json_string : string) : (shape list, _) result =
145 D.(decode_string (list shape_decoder) json_string)
146```
147
148Now, say that we didn't have the benefit of the `"shape"` field describing the
149type of the shape in our JSON list. We can still decode the shapes by trying
150each decoder in turn using the `one_of` combinator.
151
152`one_of` takes a list of `string * 'a decoder` pairs and tries each decoder in
153turn. The `string` element of each pair is just used to name the decoder in
154error messages.
155
156```ocaml
157let shape_decoder_2 : shape decoder =
158 D.(
159 one_of
160 [ ("a square", sqaure_decoder)
161 ; ("a circle", circle_decoder)
162 ; ("a triangle", triangle_decoder)
163 ]
164 )
165```
166
167## Generic decoders
168
169
170Suppose our program deals with users and roles. We want to decode our JSON input
171into these types.
172
173```ocaml
174type role = Admin | User
175
176type user =
177 { name : string
178 ; roles : role list
179 }
180```
181
182Let's define our decoders. We'll write a module functor so we can re-use the
183same decoders across different JSON libraries, with YAML input, or with
184Bucklescript.
185
186```ocaml
187module My_decoders(D : Decoders.Decode.S) = struct
188 open D
189
190 let role : role decoder =
191 string >>= function
192 | "ADMIN" -> succeed Admin
193 | "USER" -> succeed User
194 | _ -> fail "Expected a role"
195
196 let user : user decoder =
197 field "name" string >>= fun name ->
198 field "roles" (list role) >>= fun roles ->
199 succeed { name; roles }
200end
201
202module My_yojson_decoders = My_decoders(Decoders_yojson.Basic.Decode)
203```
204
205Great! Let's try them out.
206
207```ocaml
208utop # open My_yojson_decoders;;
209utop # D.decode_string role {| "USER" |};;
210- : (role, error) result = Ok User
211
212utop # D.decode_string D.(field "users" (list user))
213 {| {"users": [{"name": "Alice", "roles": ["ADMIN", "USER"]},
214 {"name": "Bob", "roles": ["USER"]}]}
215 |};;
216- : (user list, error) result =
217Ok [{name = "Alice"; roles = [Admin; User]}; {name = "Bob"; roles = [User]}]
218```
219
220Let's introduce an error in the JSON:
221
222```ocaml
223utop # D.decode_string D.(field "users" (list user))
224 {| {"users": [{"name": "Alice", "roles": ["ADMIN", "USER"]},
225 {"name": "Bob", "roles": ["SUPER_USER"]}]}
226 |};;
227- : (user list, error) result =
228Error
229 in field "users":
230 while decoding a list:
231 element 1:
232 in field "roles":
233 while decoding a list:
234 element 0: Expected a role, but got "SUPER_USER"
235```
236
237We get a nice pointer that we forgot to handle the `SUPER_USER` role.
238
239## Release
240
241After updating CHANGES.md:
242
243```
244npm version <newversion>
245dune-release tag
246git push
247dune-release -p decoders,decoders-ezjsonm,decoders-yojson
248```"""
249maintainer: "Matt Bray <matt@aestheticintegration.com>"
250authors: "Matt Bray <matt@aestheticintegration.com>"
251license: "ISC"
252homepage: "https://github.com/mattjbray/ocaml-decoders"
253doc: "https://mattjbray.github.io/ocaml-decoders/decoders-yojson"
254bug-reports: "https://github.com/mattjbray/ocaml-decoders/issues"
255depends: [
256 "ocaml"
257 "dune"
258 "ounit" {with-test}
259 "decoders" {< "0.3.0"}
260 "yojson" {< "2.0.0"}
261]
262build: [
263 ["dune" "build" "-p" name "-j" jobs]
264 ["dune" "runtest" "-p" name "-j" jobs] {with-test}
265]
266dev-repo: "git+ssh://git@github.com/mattjbray/ocaml-decoders.git"
267url {
268 src:
269 "https://github.com/mattjbray/ocaml-decoders/releases/download/0.1.2/decoders-0.1.2.tbz"
270 checksum: [
271 "sha256=70accb21f2792f7a5bd9617d73a95a2a905f62053c39d105f863ee14bb9013b6"
272 "md5=88f1fc39db4ceff663168e4dea1e70b6"
273 ]
274}