Skip to content

Commit 4838a99

Browse files
Add a feature to allow any json in a variant (#55)
Co-authored-by: Antonio Nuno Monteiro <anmonteiro@gmail.com>
1 parent c903fec commit 4838a99

23 files changed

+500
-194
lines changed

ppx/browser/dune

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@
2828
(run echo "let () = Ppxlib.Driver.standalone ()"))))
2929

3030
(copy_files#
31-
(files ../common/ppx_deriving_json_common.ml))
31+
(files ../native/common/ppx_deriving_json_common.ml))
3232

3333
(copy_files#
34-
(files ../common/ppx_deriving_tools.{ml,mli}))
34+
(files ../native/common/ppx_deriving_tools.{ml,mli}))

ppx/browser/ppx_deriving_json_js.ml

Lines changed: 76 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -42,8 +42,9 @@ module Of_json = struct
4242
[%expr
4343
Ppx_deriving_json_runtime.of_json_error ~json:x
4444
[%e
45-
estring ~loc (sprintf "expected field %S to be present" n.txt)]]]]
46-
)
45+
estring ~loc
46+
(sprintf "expected field %S to be present"
47+
n.txt)]]]] )
4748
in
4849
[%expr
4950
let fs = (Obj.magic [%e x] : [%t build_js_type ~loc fs]) in
@@ -68,12 +69,19 @@ module Of_json = struct
6869
Ppx_deriving_json_runtime.of_json_msg_error
6970
[%e estring ~loc (sprintf "expected a JSON object")]]
7071

71-
let ensure_json_array_len ~loc n len x =
72+
let ensure_json_array_len ~loc ~allow_any_constr ~else_ n len x =
7273
[%expr
7374
if Stdlib.( <> ) [%e len] [%e eint ~loc n] then
74-
Ppx_deriving_json_runtime.of_json_msg_error ~json:[%e x]
75-
[%e
76-
estring ~loc (sprintf "expected a JSON array of length %i" n)]]
75+
[%e
76+
match allow_any_constr with
77+
| Some allow_any_constr -> allow_any_constr x
78+
| None ->
79+
[%expr
80+
Ppx_deriving_json_runtime.of_json_msg_error ~json:[%e x]
81+
[%e
82+
estring ~loc
83+
(sprintf "expected a JSON array of length %i" n)]]]
84+
else [%e else_]]
7785

7886
let derive_of_tuple derive t x =
7987
let loc = t.tpl_loc in
@@ -99,7 +107,7 @@ module Of_json = struct
99107
[%e ensure_json_object ~loc x];
100108
[%e build_record ~loc derive t.rcd_fields x Fun.id]]
101109

102-
let derive_of_variant _derive t body x =
110+
let derive_of_variant _derive t ~allow_any_constr body x =
103111
let loc = t.vrt_loc in
104112
[%expr
105113
if Js.Array.isArray [%e x] then
@@ -111,52 +119,75 @@ module Of_json = struct
111119
let tag = (Obj.magic tag : string) in
112120
[%e body]
113121
else
114-
Ppx_deriving_json_runtime.of_json_error ~json:[%e x]
115-
"expected a non empty JSON array with element being a \
116-
string"
122+
[%e
123+
match allow_any_constr with
124+
| Some allow_any_constr -> allow_any_constr x
125+
| None ->
126+
[%expr
127+
Ppx_deriving_json_runtime.of_json_error ~json:[%e x]
128+
"expected a non empty JSON array with element \
129+
being a string"]]
117130
else
118-
Ppx_deriving_json_runtime.of_json_error ~json:[%e x]
119-
"expected a non empty JSON array"
131+
[%e
132+
match allow_any_constr with
133+
| Some allow_any_constr -> allow_any_constr x
134+
| None ->
135+
[%expr
136+
Ppx_deriving_json_runtime.of_json_error ~json:[%e x]
137+
"expected a non empty JSON array"]]
120138
else
121-
Ppx_deriving_json_runtime.of_json_error ~json:[%e x]
122-
"expected a non empty JSON array"]
139+
[%e
140+
match allow_any_constr with
141+
| Some allow_any_constr -> allow_any_constr x
142+
| None ->
143+
[%expr
144+
Ppx_deriving_json_runtime.of_json_error ~json:[%e x]
145+
"expected a non empty JSON array"]]]
123146

124-
let derive_of_variant_case derive make c next =
147+
let derive_of_variant_case derive make c ~allow_any_constr next =
125148
match c with
126149
| Vcs_record (n, r) ->
127150
let loc = n.loc in
128151
let n = Option.value ~default:n (vcs_attr_json_name r.rcd_ctx) in
129152
[%expr
130-
if Stdlib.( = ) tag [%e estring ~loc:n.loc n.txt] then (
131-
[%e ensure_json_array_len ~loc 2 [%expr len] [%expr x]];
132-
let fs = Js.Array.unsafe_get array 1 in
133-
[%e ensure_json_object ~loc [%expr fs]];
153+
if Stdlib.( = ) tag [%e estring ~loc:n.loc n.txt] then
134154
[%e
135-
build_record ~loc derive r.rcd_fields [%expr fs] (fun e ->
136-
make (Some e))])
155+
ensure_json_array_len ~loc ~allow_any_constr 2 [%expr len]
156+
[%expr x]
157+
~else_:
158+
[%expr
159+
let fs = Js.Array.unsafe_get array 1 in
160+
[%e ensure_json_object ~loc [%expr fs]];
161+
[%e
162+
build_record ~loc derive r.rcd_fields [%expr fs]
163+
(fun e -> make (Some e))]]]
137164
else [%e next]]
138165
| Vcs_tuple (n, t) ->
139166
let loc = n.loc in
140167
let n = Option.value ~default:n (vcs_attr_json_name t.tpl_ctx) in
141168
let arity = List.length t.tpl_types in
142169
[%expr
143-
if Stdlib.( = ) tag [%e estring ~loc:n.loc n.txt] then (
144-
[%e ensure_json_array_len ~loc (arity + 1) [%expr len] [%expr x]];
170+
if Stdlib.( = ) tag [%e estring ~loc:n.loc n.txt] then
145171
[%e
146-
if Stdlib.( = ) arity 0 then make None
147-
else
148-
make
149-
(Some
150-
(build_tuple ~loc derive 1 t.tpl_types [%expr array]))])
172+
ensure_json_array_len ~loc ~allow_any_constr (arity + 1)
173+
[%expr len] [%expr x]
174+
~else_:
175+
(if Stdlib.( = ) arity 0 then make None
176+
else
177+
make
178+
(Some
179+
(build_tuple ~loc derive 1 t.tpl_types
180+
[%expr array])))]
151181
else [%e next]]
152182

183+
let is_allow_any_constr vcs =
184+
Ppx_deriving_json_common.vcs_attr_json_allow_any vcs
185+
153186
let deriving : Ppx_deriving_tools.deriving =
154187
deriving_of () ~name:"of_json"
155-
~error:(fun ~loc ->
156-
[%expr Ppx_deriving_json_runtime.of_json_msg_error "invalid JSON"])
157188
~of_t:(fun ~loc -> [%type: Js.Json.t])
158-
~derive_of_tuple ~derive_of_record ~derive_of_variant
159-
~derive_of_variant_case
189+
~is_allow_any_constr ~derive_of_tuple ~derive_of_record
190+
~derive_of_variant ~derive_of_variant_case
160191
end
161192

162193
module To_json = struct
@@ -194,13 +225,24 @@ module To_json = struct
194225
| Vcs_record (n, r) ->
195226
let loc = n.loc in
196227
let n = Option.value ~default:n (vcs_attr_json_name r.rcd_ctx) in
197-
let tag = [%expr (Obj.magic [%e estring ~loc:n.loc n.txt]: Js.Json.t)] in
228+
let tag =
229+
[%expr (Obj.magic [%e estring ~loc:n.loc n.txt] : Js.Json.t)]
230+
in
198231
let es = [ derive_of_record derive r es ] in
199232
as_json ~loc (pexp_array ~loc (tag :: es))
233+
| Vcs_tuple (_n, t) when vcs_attr_json_allow_any t.tpl_ctx -> (
234+
match es with
235+
| [ x ] -> x
236+
| es ->
237+
failwith
238+
(sprintf "expected a tuple of length 1, got %i"
239+
(List.length es)))
200240
| Vcs_tuple (n, t) ->
201241
let loc = n.loc in
202242
let n = Option.value ~default:n (vcs_attr_json_name t.tpl_ctx) in
203-
let tag = [%expr (Obj.magic [%e estring ~loc:n.loc n.txt]: Js.Json.t)] in
243+
let tag =
244+
[%expr (Obj.magic [%e estring ~loc:n.loc n.txt] : Js.Json.t)]
245+
in
204246
let es = List.map2 t.tpl_types es ~f:derive in
205247
as_json ~loc (pexp_array ~loc (tag :: es))
206248

ppx/common/ppx_deriving_json_common.ml renamed to ppx/native/common/ppx_deriving_json_common.ml

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ let get_of_variant_case ?mark_as_seen ~variant ~polyvariant = function
77
| Vcs_ctx_variant ctx -> Attribute.get ?mark_as_seen variant ctx
88
| Vcs_ctx_polyvariant ctx -> Attribute.get ?mark_as_seen polyvariant ctx
99

10+
1011
let get_of_variant ?mark_as_seen ~variant ~polyvariant = function
1112
| Vrt_ctx_variant ctx -> Attribute.get ?mark_as_seen variant ctx
1213
| Vrt_ctx_polyvariant ctx -> Attribute.get ?mark_as_seen polyvariant ctx
@@ -23,6 +24,20 @@ let vcs_attr_json_name =
2324
let polyvariant = attr_json_name Attribute.Context.rtag in
2425
get_of_variant_case ~variant ~polyvariant
2526

27+
28+
29+
let attr_json_allow_any ctx = Attribute.declare_flag "json.allow_any" ctx
30+
31+
let vcs_attr_json_allow_any =
32+
let variant =
33+
attr_json_allow_any Attribute.Context.constructor_declaration
34+
in
35+
let polyvariant = attr_json_allow_any Attribute.Context.rtag in
36+
fun ?mark_as_seen ctx ->
37+
match get_of_variant_case ~variant ~polyvariant ?mark_as_seen ctx with
38+
| None -> false
39+
| Some () -> true
40+
2641
let ld_attr_json_key =
2742
Attribute.get
2843
(Attribute.declare "json.key" Attribute.Context.label_declaration
Lines changed: 88 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -364,8 +364,8 @@ module Conv = struct
364364

365365
let repr_variant_cases cs = List.rev cs
366366

367-
let deriving_of ~name ~of_t ~error ~derive_of_tuple ~derive_of_record
368-
~derive_of_variant ~derive_of_variant_case () =
367+
let deriving_of ~name ~of_t ~is_allow_any_constr ~derive_of_tuple
368+
~derive_of_record ~derive_of_variant ~derive_of_variant_case () =
369369
(object (self)
370370
inherit Schema.deriving1
371371
method name = name
@@ -384,9 +384,43 @@ module Conv = struct
384384
method! derive_of_variant td cs x =
385385
let loc = td.ptype_loc in
386386
let cs = repr_variant_cases cs in
387+
let allow_any_constr =
388+
cs
389+
|> List.find_opt ~f:(fun cs ->
390+
is_allow_any_constr (Vcs_ctx_variant cs))
391+
|> Option.map (fun cs e -> econstruct cs (Some e))
392+
in
393+
let cs =
394+
List.filter
395+
~f:(fun cs -> not (is_allow_any_constr (Vcs_ctx_variant cs)))
396+
cs
397+
in
387398
let body, cases =
388399
List.fold_left cs
389-
~init:(error ~loc, [])
400+
~init:
401+
(match allow_any_constr with
402+
| Some allow_any_constr -> allow_any_constr x, []
403+
| None ->
404+
let error_message =
405+
Printf.sprintf "expected %s"
406+
(cs
407+
|> List.map ~f:(fun c ->
408+
let name = c.pcd_name in
409+
match c.pcd_args with
410+
| Pcstr_record _fs ->
411+
Printf.sprintf {|["%s", { _ }]|}
412+
name.txt
413+
| Pcstr_tuple li ->
414+
Printf.sprintf {|["%s"%s]|} name.txt
415+
(li
416+
|> List.map ~f:(fun _ -> ", _")
417+
|> String.concat ~sep:""))
418+
|> String.concat ~sep:" or ")
419+
in
420+
( [%expr
421+
Ppx_deriving_json_errors.of_json_error ~json:[%e x]
422+
[%e estring ~loc error_message]],
423+
[] ))
390424
~f:(fun (next, cases) c ->
391425
let make (n : label loc) arg =
392426
pexp_construct (map_loc lident n) ~loc:n.loc arg
@@ -403,7 +437,7 @@ module Conv = struct
403437
in
404438
let next =
405439
derive_of_variant_case self#derive_of_core_type
406-
(make n) t next
440+
(make n) t ~allow_any_constr next
407441
in
408442
next, t :: cases
409443
| Pcstr_tuple ts ->
@@ -415,7 +449,7 @@ module Conv = struct
415449
in
416450
let next =
417451
derive_of_variant_case self#derive_of_core_type
418-
(make n) case next
452+
(make n) case ~allow_any_constr next
419453
in
420454
next, case :: cases)
421455
in
@@ -426,20 +460,41 @@ module Conv = struct
426460
vrt_ctx = Vrt_ctx_variant td;
427461
}
428462
in
429-
derive_of_variant self#derive_of_core_type t body x
463+
derive_of_variant self#derive_of_core_type t ~allow_any_constr
464+
body x
430465

431466
method! derive_of_polyvariant t (cs : row_field list) x =
432467
let loc = t.ptyp_loc in
468+
let allow_any_constr =
469+
cs
470+
|> List.find_opt ~f:(fun cs ->
471+
is_allow_any_constr (Vcs_ctx_polyvariant cs))
472+
|> Option.map (fun cs ->
473+
match cs.prf_desc with
474+
| Rinherit _ ->
475+
failwith "[@allow_any] placed on inherit clause"
476+
| Rtag (n, _, _) ->
477+
fun e -> pexp_variant ~loc:n.loc n.txt (Some e))
478+
in
479+
let cs =
480+
List.filter
481+
~f:(fun cs ->
482+
not (is_allow_any_constr (Vcs_ctx_polyvariant cs)))
483+
cs
484+
in
433485
let cases = repr_polyvariant_cases cs in
434486
let body, cases =
435487
List.fold_left cases
436488
~init:
437-
( [%expr
438-
raise
439-
(Ppx_deriving_json_runtime.Of_json_error
440-
(Ppx_deriving_json_runtime.Unexpected_variant
441-
"unexpected variant"))],
442-
[] )
489+
(match allow_any_constr with
490+
| Some allow_any_constr -> allow_any_constr x, []
491+
| None ->
492+
( [%expr
493+
raise
494+
(Ppx_deriving_json_runtime.Of_json_error
495+
(Ppx_deriving_json_runtime.Unexpected_variant
496+
"unexpected variant"))],
497+
[] ))
443498
~f:(fun (next, cases) (c, r) ->
444499
let ctx = Vcs_ctx_polyvariant c in
445500
match r with
@@ -453,7 +508,7 @@ module Conv = struct
453508
in
454509
let next =
455510
derive_of_variant_case self#derive_of_core_type make
456-
case next
511+
case ~allow_any_constr next
457512
in
458513
next, case :: cases
459514
| `Rinherit (n, ts) ->
@@ -480,12 +535,13 @@ module Conv = struct
480535
vrt_ctx = Vrt_ctx_polyvariant t;
481536
}
482537
in
483-
derive_of_variant self#derive_of_core_type t body x
538+
derive_of_variant self#derive_of_core_type t ~allow_any_constr
539+
body x
484540
end
485541
:> deriving)
486542

487-
let deriving_of_match ~name ~of_t ~derive_of_tuple ~derive_of_record
488-
~derive_of_variant_case () =
543+
let deriving_of_match ~name ~of_t ~cmp_sort_vcs ~derive_of_tuple
544+
~derive_of_record ~derive_of_variant_case () =
489545
(object (self)
490546
inherit Schema.deriving1
491547
method name = name
@@ -519,6 +575,14 @@ module Conv = struct
519575
|> String.concat ~sep:" or ")
520576
in
521577
let cs = repr_variant_cases cs in
578+
let cs =
579+
List.stable_sort
580+
~cmp:(fun cs1 cs2 ->
581+
let vcs1 = Vcs_ctx_variant cs1
582+
and vcs2 = Vcs_ctx_variant cs2 in
583+
cmp_sort_vcs vcs1 vcs2)
584+
cs
585+
in
522586
let cases =
523587
List.fold_left cs
524588
~init:
@@ -561,6 +625,14 @@ module Conv = struct
561625
method! derive_of_polyvariant t (cs : row_field list) x =
562626
let loc = t.ptyp_loc in
563627
let cases = repr_polyvariant_cases cs in
628+
let cases =
629+
List.stable_sort
630+
~cmp:(fun (cs1, _) (cs2, _) ->
631+
let vcs1 = Vcs_ctx_polyvariant cs1
632+
and vcs2 = Vcs_ctx_polyvariant cs2 in
633+
cmp_sort_vcs vcs1 vcs2)
634+
cases
635+
in
564636
let ctors, inherits =
565637
List.partition_map cases ~f:(fun (c, r) ->
566638
let ctx = Vcs_ctx_polyvariant c in

0 commit comments

Comments
 (0)