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}