1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
open Js_of_ocaml
type t = < > Js.t
exception Decode_error of string
let decode_error message = raise (Decode_error message)
let _JSON = Js.Unsafe.global##._JSON
let try_parse_opt s =
try Some (_JSON##parse (Js.string s)) with
| _ -> None
let try_parse_exn s =
try _JSON##parse (Js.string s) with
| _ -> decode_error ("Failed to parse JSON string \"" ^ s ^ "\"")
let stringify ?spaces (json : t) =
let js_string =
match spaces with
| None -> _JSON##stringify json
| Some n ->
let spaces = Js.number_of_float (float_of_int n) in
_JSON##stringify json Js.undefined spaces
in
Js.to_string js_string
module Decode = struct
type 'a decoder = t -> 'a
let expected typ (json : t) =
decode_error ("Expected " ^ typ ^ ", got " ^ stringify json)
let expected_length length array =
decode_error
( "Expected array of length "
^ string_of_int length
^ ", got array of length "
^ string_of_int (Array.length array) )
let typeof value = Js.to_string (Js.typeof value)
let is_array value =
let array_constr = Js.Unsafe.global##._Array in
Js.instanceof value array_constr
let id (json : t) = json
let null (json : t) =
if Js.Opt.test (Js.Opt.return json) then
expected "null" json
else
Js.null
let bool (json : t) =
if typeof json = "boolean" then
Js.to_bool (Js.Unsafe.coerce json)
else
expected "boolean" json
let float (json : t) =
if typeof json = "number" then
Js.float_of_number (Js.Unsafe.coerce json)
else
expected "number" json
let int (json : t) =
let f = float json in
if Float.is_finite f && Float.floor f = f then
int_of_float f
else
expected "integer" json
let string (json : t) =
if typeof json = "string" then
Js.to_string (Js.Unsafe.coerce json)
else
expected "string" json
let char (json : t) =
let s = string json in
if String.length s = 1 then
s.[0]
else
expected "single-character string" json
let nullable decode (json : t) =
if Js.Opt.test (Js.Opt.return json) then
Some (decode json)
else
None
let array decode (json : t) =
if is_array json then
let array = Js.to_array (Js.Unsafe.coerce json) in
let convert i (json : t) =
try decode json with
| Decode_error message ->
decode_error (message ^ "\n\tin array at index " ^ string_of_int i)
in
Array.mapi convert array
else
expected "array" json
let list decode (json : t) = Array.to_list (array decode json)
let tuple_element decode array i =
try decode array.(i) with
| Decode_error message ->
decode_error (message ^ "\n\tin array at index " ^ string_of_int i)
let pair decode_a decode_b (json : t) =
let array = array id json in
if Array.length array = 2 then
let a = tuple_element decode_a array 0 in
let b = tuple_element decode_b array 1 in
(a, b)
else
expected_length 2 array
let tuple2 = pair
let tuple3 decode_a decode_b decode_c (json : t) =
let array = array id json in
if Array.length array = 3 then
let a = tuple_element decode_a array 0 in
let b = tuple_element decode_b array 1 in
let c = tuple_element decode_c array 2 in
(a, b, c)
else
expected_length 3 array
let tuple4 decode_a decode_b decode_c decode_d (json : t) =
let array = array id json in
if Array.length array = 4 then
let a = tuple_element decode_a array 0 in
let b = tuple_element decode_b array 1 in
let c = tuple_element decode_c array 2 in
let d = tuple_element decode_d array 3 in
(a, b, c, d)
else
expected_length 4 array
let object_field decode js_object key =
try decode (Js.Unsafe.get js_object key) with
| Decode_error message ->
decode_error
(message ^ "\n\tin object at field '" ^ Js.to_string key ^ "'")
let dict decode (json : t) =
if
typeof json = "object"
&& (not (is_array json))
&& Js.Opt.(test (return json))
then (
let keys = Js.to_array (Js.object_keys json) in
let table = Hashtbl.create (Array.length keys) in
let set key =
let value = object_field decode json key in
Hashtbl.add table (Js.to_string key) value
in
Array.iter set keys;
table
) else
expected "object" json
let field key decode (json : t) =
if
typeof json = "object"
&& (not (is_array json))
&& Js.Opt.(test (return json))
then
let js_key = Js.string key in
if Js.Optdef.(test @@ return (Js.Unsafe.get json js_key)) then
object_field decode json js_key
else
decode_error ("Expected field '" ^ key ^ "'")
else
expected "object" json
let rec at key_path decoder =
match key_path with
| [ key ] -> field key decoder
| first :: rest -> field first (at rest decoder)
| [] ->
invalid_arg "Expected key_path to contain at least one element"
let try_optional decode (json : t) =
try Some (decode json) with
| Decode_error _ -> None
let try_default value decode (json : t) =
try decode json with
| Decode_error _ -> value
let any decoders (json : t) =
let rec inner errors = function
| [] ->
let rev_errors = List.rev errors in
decode_error
( "Value was not able to be decoded with the given decoders. Errors: "
^ String.concat "\n" rev_errors )
| decode :: rest -> (
try decode json with
| Decode_error e -> inner (e :: errors) rest )
in
inner [] decoders
let either a b = any [ a; b ]
let map f decode (json : t) = f (decode json)
let bind b a (json : t) = b (a json) json
end
module Encode = struct
type 'a encoder = 'a -> t
let id (json : t) = json
let null : t = Obj.magic Js.null
let bool b : t = Js.Unsafe.coerce (Js.bool b)
let float f : t = Js.Unsafe.coerce (Js.number_of_float f)
let int i : t = Js.Unsafe.coerce (Js.number_of_float (float_of_int i))
let string s : t = Js.Unsafe.coerce (Js.string s)
let char c : t = string (String.make 1 c)
let nullable encode = function
| None -> null
| Some v -> encode v
let array encode a : t =
let encoded : t array = Array.map encode a in
Js.Unsafe.coerce (Js.array encoded)
let list encode l : t = array encode (Array.of_list l)
let pair encode_a encode_b (a, b) : t =
let encoded : t array = [| encode_a a; encode_b b |] in
Js.Unsafe.coerce (Js.array encoded)
let tuple2 = pair
let tuple3 encode_a encode_b encode_c (a, b, c) : t =
let encoded : t array = [| encode_a a; encode_b b; encode_c c |] in
Js.Unsafe.coerce (Js.array encoded)
let tuple4 encode_a encode_b encode_c encode_d (a, b, c, d) : t =
let encoded : t array =
[| encode_a a; encode_b b; encode_c c; encode_d d |]
in
Js.Unsafe.coerce (Js.array encoded)
let dict encode table : t =
let encode_pair (k, v) = (k, Js.Unsafe.coerce (encode v)) in
table
|> Hashtbl.to_seq
|> Array.of_seq
|> Array.map encode_pair
|> Js.Unsafe.obj
let object_ (props : (string * t) list) : t =
let coerce (k, v) = (k, Js.Unsafe.coerce v) in
Js.Unsafe.obj (Array.map coerce @@ Array.of_list props)
end
let t_of_js : Ojs.t -> t = Obj.magic
let t_to_js : t -> Ojs.t = Obj.magic