Skip to content

Commit 26477d5

Browse files
andreypoppAndrey Popppedrobslisboa
authored
Support for labeled tuples (#80)
Co-authored-by: Andrey Popp <andrey.popp@ahrefs.com> Co-authored-by: Pedro Lisboa <pedrobslisboa@gmail.com>
1 parent 071e7d0 commit 26477d5

13 files changed

Lines changed: 581 additions & 51 deletions

CHANGES.md

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,11 +33,13 @@
3333
function can be provided directly, e.g. `[@json.drop_default Int.equal]`
3434
([#77](https://github.com/melange-community/melange-json/pull/77))
3535
- Add `Melange_json.equal` for comparing two JSON values, and
36-
`[@drop_default_if_json_equal]` as an alternative to `[@.drop_default]`
36+
`[@drop_default_if_json_equal]` as an alternative to `[@json.drop_default]`
3737
that compares values at the JSON level
3838
([#77](https://github.com/melange-community/melange-json/pull/77))
3939
- PPX: Fix `unused-var` warning with polyvars without own cases
4040
([#38](https://github.com/melange-community/melange-json/pull/38))
41+
- PPX: Add support for labeled tuples, e.g. `[%to_json: a:string * b:int]`,
42+
encoded as JSON objects like records
4143

4244
## 2.0.0 (2025-03-11)
4345

README.md

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -219,6 +219,25 @@ and `of_json` extension points:
219219
let json = [%to_json: int * string] (42, "foo")
220220
```
221221

222+
Labeled tuples (OCaml 5.4+) are supported as well, and serialize to JSON objects
223+
keyed by each label:
224+
225+
```ocaml
226+
let json = [%to_json: x:int * y:int] (~x:1, ~y:2)
227+
(* {"x": 1, "y": 2} *)
228+
229+
let (~x, ~y) =
230+
[%of_json: x:int * y:int] (Melange_json.of_string {|{"x": 1, "y": 2}|})
231+
(* x = 1, y = 2 *)
232+
```
233+
234+
Members without a label are keyed by their position:
235+
236+
```ocaml
237+
let json = [%to_json: x:int * string] (~x:1, "foo")
238+
(* {"x": 1, "1": "foo"} *)
239+
```
240+
222241
#### `[@json.default E]`: default values for records
223242

224243
You can specify default values for record fields using the `[@json.default E]`

dune-project

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@
3535
(>= "3.10.0")
3636
:with-test))
3737
(ppxlib
38-
(>= "0.36.0"))
38+
(>= "0.38.0"))
3939
(opam-check-npm-deps :with-dev-setup)
4040
(ocaml-lsp-server :with-dev-setup)
4141
(ocamlformat
@@ -53,6 +53,6 @@
5353
(ocaml
5454
(>= "4.12"))
5555
(ppxlib
56-
(>= "0.36.0"))
56+
(>= "0.38.0"))
5757
(yojson
5858
(>= "1.6.0"))))

flake.lock

Lines changed: 7 additions & 7 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

melange-json-native.opam

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ bug-reports: "https://github.com/melange-community/melange-json/issues"
1414
depends: [
1515
"dune" {>= "3.16"}
1616
"ocaml" {>= "4.12"}
17-
"ppxlib" {>= "0.36.0"}
17+
"ppxlib" {>= "0.38.0"}
1818
"yojson" {>= "1.6.0"}
1919
"odoc" {with-doc}
2020
]

melange-json.opam

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ depends: [
1717
"melange" {>= "4.0.0"}
1818
"melange-jest" {with-test}
1919
"reason" {>= "3.10.0" & with-test}
20-
"ppxlib" {>= "0.36.0"}
20+
"ppxlib" {>= "0.38.0"}
2121
"opam-check-npm-deps" {with-dev-setup}
2222
"ocaml-lsp-server" {with-dev-setup}
2323
"ocamlformat" {= "0.28.1" & with-dev-setup}

ppx/browser/ppx_deriving_json_js.ml

Lines changed: 30 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@ module Of_json = struct
3030
Option.value ~default:n (ld_attr_json_key ld)
3131
in
3232
let handle_field fs ld =
33-
( map_loc lident ld.pld_name,
33+
( ld.pld_name,
3434
let n = field_key ld in
3535
[%expr
3636
match
@@ -62,10 +62,8 @@ module Of_json = struct
6262
[%expr
6363
let fs = (Obj.magic [%e x] : [%t build_js_type ~loc fs]) in
6464
[%e
65-
make
66-
(pexp_record ~loc
67-
(List.map fs ~f:(handle_field [%expr fs]))
68-
None)]]
65+
let fs = List.map fs ~f:(handle_field [%expr fs]) in
66+
make ~loc fs]]
6967
in
7068
if allow_extra_fields then body
7169
else
@@ -136,10 +134,28 @@ module Of_json = struct
136134
let derive_of_record derive t x =
137135
let loc = t.rcd_loc in
138136
let allow_extra_fields = td_allow_extra_fields t.rcd_ctx in
137+
let make ~loc fs =
138+
let fs = List.map fs ~f:(fun (n, v) -> map_loc lident n, v) in
139+
pexp_record ~loc fs None
140+
in
139141
[%expr
140142
[%e ensure_json_object ~loc x];
141143
[%e
142-
build_record ~allow_extra_fields ~loc derive t.rcd_fields x Fun.id]]
144+
build_record ~allow_extra_fields ~loc derive t.rcd_fields x make]]
145+
146+
let derive_of_labeled_tuple derive t x =
147+
let loc = t.rcd_loc in
148+
let make ~loc fs =
149+
let fs =
150+
List.map fs ~f:(fun (n, v) -> labeled_tuple_arg_label n, v)
151+
in
152+
pexp_labeled_tuple ~loc fs
153+
in
154+
[%expr
155+
[%e ensure_json_object ~loc x];
156+
[%e
157+
build_record ~allow_extra_fields:true ~loc derive t.rcd_fields x
158+
make]]
143159

144160
let derive_of_variant ?(is_compact_variants = false) _derive t
145161
~allow_any_constr body x =
@@ -235,6 +251,10 @@ module Of_json = struct
235251
| Vcs_record (n, r) ->
236252
let loc = n.loc in
237253
let n = Option.value ~default:n (vcs_attr_json_name r.rcd_ctx) in
254+
let make ~loc fs =
255+
let fs = List.map fs ~f:(fun (n, v) -> map_loc lident n, v) in
256+
make (Some (pexp_record ~loc fs None))
257+
in
238258
[%expr
239259
if Stdlib.( = ) [%e tag] [%e estring ~loc:n.loc n.txt] then
240260
[%e
@@ -251,7 +271,7 @@ module Of_json = struct
251271
| Vcs_ctx_polyvariant _ -> true
252272
in
253273
build_record ~allow_extra_fields ~loc derive
254-
r.rcd_fields [%expr fs] (fun e -> make (Some e))]]]
274+
r.rcd_fields [%expr fs] make]]]
255275
else [%e next]]
256276
| Vcs_tuple (n, t) ->
257277
let loc = n.loc in
@@ -284,7 +304,7 @@ module Of_json = struct
284304
deriving_of () ~name:"of_json"
285305
~of_t:(fun ~loc -> [%type: Js.Json.t])
286306
~is_allow_any_constr ~derive_of_tuple ~derive_of_record
287-
~derive_of_variant ~derive_of_variant_case
307+
~derive_of_labeled_tuple ~derive_of_variant ~derive_of_variant_case
288308
end
289309

290310
module To_json = struct
@@ -419,7 +439,8 @@ module To_json = struct
419439
let deriving : Ppx_deriving_tools.deriving =
420440
deriving_to () ~name:"to_json"
421441
~t_to:(fun ~loc -> [%type: Js.Json.t])
422-
~derive_of_tuple ~derive_of_record ~derive_of_variant_case
442+
~derive_of_tuple ~derive_of_labeled_tuple:derive_of_record
443+
~derive_of_record ~derive_of_variant_case
423444
end
424445

425446
let () =

0 commit comments

Comments
 (0)