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