Skip to content

Commit 03dd18b

Browse files
committed
ppx: make variant_as_array the default
1 parent 9da632e commit 03dd18b

File tree

6 files changed

+2070
-665
lines changed

6 files changed

+2070
-665
lines changed

README.md

Lines changed: 19 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,10 @@
22

33
`ppx_deriving_jsonschema` is a PPX syntax extension that generates JSON schema from OCaml types.
44

5-
The conversion aims to be compatible with the existing json derivers.
5+
The conversion aims to be compatible with the existing json derivers:
6+
- https://github.com/melange-community/melange-json
7+
- https://github.com/ocaml-ppx/ppx_deriving_yojson
8+
- https://github.com/janestreet/ppx_yojson_conv
69

710
## Installation
811

@@ -88,20 +91,7 @@ type t = int * string [@@deriving jsonschema]
8891

8992
#### Variants and polymorphic variants
9093

91-
By default, variants are converted to `"anyOf": [{ "const": "..." }, ...]`. This means that while the constructor names are represented as strings, any associated payload is not included.
92-
93-
```ocaml
94-
type t =
95-
| Typ
96-
| Class of string
97-
[@@deriving jsonschema]
98-
```
99-
100-
```json
101-
{ "anyOf": [ { "const": "Typ" }, { "const": "Class" } ] }
102-
```
103-
104-
To include the payload in the encoding, the `~variant_as_array` flag should be used. This flag also ensures compatibility with [ppx_deriving_json] and [ppx_yojson_conv]. In this case each constructor is represented like a tuple.
94+
By default, constructors in variants are represented as a list with one string, which is the name of the contructor. Constructors with arguments are represented as lists, the first element being the constructor name, the rest being its arguments. It reproduces the representation of `ppx_deriving_yojson` and `ppx_yojson_conv`. For example:
10595

10696
```ocaml
10797
type t =
@@ -131,13 +121,26 @@ type t =
131121
}
132122
```
133123

124+
A `~variant_as_string` flag is exposed to obtain a more natural representation `"anyOf": [{ "const": "..." }, ...]`. This representation does _not_ support payloads. It reproduces the representation of `melange-json` for [enumeration like variants](https://github.com/melange-community/melange-json?tab=readme-ov-file#enumeration-like-variants). For example:
125+
126+
```ocaml
127+
type t =
128+
| Typ
129+
| Class of string
130+
[@@deriving jsonschema ~variant_as_string]
131+
```
132+
133+
```json
134+
{ "anyOf": [ { "const": "Typ" }, { "const": "Class" } ] }
135+
```
136+
134137
If the JSON variant names differ from OCaml conventions, it is possible to specify the corresponding JSON string explicitly using `[@name "constr"]`, for example:
135138

136139
```ocaml
137140
type t =
138141
| Typ [@name "type"]
139142
| Class of string [@name "class"]
140-
[@@deriving jsonschema]
143+
[@@deriving jsonschema ~variant_as_string]
141144
```
142145

143146
```json

src/ppx_deriving_jsonschema.ml

Lines changed: 24 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -2,32 +2,32 @@ open Ppxlib
22
open Ast_builder.Default
33

44
type config = {
5-
variant_as_array : bool;
6-
(** Encode variants as arrays of string enum instead of a string enum.
7-
Provides compatibility with the encoding used by the [ppx_deriving_json]
8-
and [ppx_yojson_conv] extensions. *)
5+
variant_as_string : bool;
6+
(** Encode variants as string instead of string array.
7+
This option breaks compatibility with yojson derivers and
8+
doesn't support constructors with a payload. *)
99
}
1010

1111
let deriver_name = "jsonschema"
1212

1313
let jsonschema_key =
1414
Attribute.declare "jsonschema.key" Attribute.Context.label_declaration
15-
Ast_pattern.(pstr (pstr_eval (estring __) nil ^:: nil))
15+
Ast_pattern.(single_expr_payload (estring __'))
1616
(fun x -> x)
1717

1818
let jsonschema_ref =
1919
Attribute.declare "jsonschema.ref" Attribute.Context.label_declaration
20-
Ast_pattern.(pstr (pstr_eval (estring __) nil ^:: nil))
20+
Ast_pattern.(single_expr_payload (estring __'))
2121
(fun x -> x)
2222

2323
let jsonschema_variant_name =
2424
Attribute.declare "jsonschema.name" Attribute.Context.constructor_declaration
25-
Ast_pattern.(pstr (pstr_eval (estring __) nil ^:: nil))
25+
Ast_pattern.(single_expr_payload (estring __'))
2626
(fun x -> x)
2727

2828
let jsonschema_polymorphic_variant_name =
2929
Attribute.declare "jsonschema.name" Attribute.Context.rtag
30-
Ast_pattern.(pstr (pstr_eval (estring __) nil ^:: nil))
30+
Ast_pattern.(single_expr_payload (estring __'))
3131
(fun x -> x)
3232

3333
let attributes =
@@ -39,7 +39,7 @@ let attributes =
3939
]
4040

4141
(* let args () = Deriving.Args.(empty) *)
42-
let args () = Deriving.Args.(empty +> flag "variant_as_array")
42+
let args () = Deriving.Args.(empty +> flag "variant_as_string")
4343

4444
let deps = []
4545

@@ -123,9 +123,9 @@ let variant ~loc ~config constrs =
123123
(function
124124
| `Inherit typ -> typ
125125
| `Tag (name, typs) ->
126-
match config.variant_as_array with
127-
| true -> Schema.tuple ~loc (Schema.const ~loc name :: typs)
128-
| false -> Schema.const ~loc name)
126+
match config.variant_as_string with
127+
| true -> Schema.const ~loc name
128+
| false -> Schema.tuple ~loc (Schema.const ~loc name :: typs))
129129
constrs)
130130

131131
let value_name_pattern ~loc type_name = ppat_var ~loc { txt = type_name ^ "_jsonschema"; loc }
@@ -168,7 +168,7 @@ let rec type_of_core ~config core_type =
168168
| { prf_desc = Rtag (name, _, typs); _ } ->
169169
let name =
170170
match Attribute.get jsonschema_polymorphic_variant_name row_field with
171-
| Some name -> name
171+
| Some name -> name.txt
172172
| None -> name.txt
173173
in
174174
let typs = List.map (type_of_core ~config) typs in
@@ -180,9 +180,9 @@ let rec type_of_core ~config core_type =
180180
in
181181
(* todo: raise an error if encoding is as string and constructor has a payload *)
182182
let v =
183-
match config.variant_as_array with
184-
| true -> variant_as_array ~loc constrs
185-
| false -> variant_as_string ~loc constrs
183+
match config.variant_as_string with
184+
| true -> variant_as_string ~loc constrs
185+
| false -> variant_as_array ~loc constrs
186186
in
187187
v
188188
| _ ->
@@ -195,12 +195,12 @@ let object_ ~loc ~config fields =
195195
(fun (fields, required) ({ pld_name; pld_type; pld_loc = _loc; _ } as field) ->
196196
let name =
197197
match Attribute.get jsonschema_key field with
198-
| Some name -> name
198+
| Some name -> name.txt
199199
| None -> pld_name.txt
200200
in
201201
let type_def =
202202
match Attribute.get jsonschema_ref field with
203-
| Some def -> Schema.type_ref ~loc def
203+
| Some def -> Schema.type_ref ~loc def.txt
204204
| None -> type_of_core ~config pld_type
205205
in
206206
( [%expr [%e estring ~loc name], [%e type_def]] :: fields,
@@ -216,17 +216,17 @@ let object_ ~loc ~config fields =
216216
"required", `List [%e elist ~loc required];
217217
]]
218218

219-
let derive_jsonschema ~ctxt ast flag_variant_as_array =
219+
let derive_jsonschema ~ctxt ast flag_variant_as_string =
220220
let loc = Expansion_context.Deriver.derived_item_loc ctxt in
221-
let config = { variant_as_array = flag_variant_as_array } in
221+
let config = { variant_as_string = flag_variant_as_string } in
222222
match ast with
223223
| _, [ { ptype_name = { txt = type_name; _ }; ptype_kind = Ptype_variant variants; _ } ] ->
224224
let variants =
225225
List.map
226226
(fun ({ pcd_args; pcd_name = { txt = name; _ }; _ } as var) ->
227227
let name =
228228
match Attribute.get jsonschema_variant_name var with
229-
| Some name -> name
229+
| Some name -> name.txt
230230
| None -> name
231231
in
232232
match pcd_args with
@@ -240,9 +240,9 @@ let derive_jsonschema ~ctxt ast flag_variant_as_array =
240240
in
241241
let v =
242242
(* todo: raise an error if encoding is as string and constructor has a payload *)
243-
match config.variant_as_array with
244-
| true -> variant_as_array ~loc variants
245-
| false -> variant_as_string ~loc variants
243+
match config.variant_as_string with
244+
| true -> variant_as_string ~loc variants
245+
| false -> variant_as_array ~loc variants
246246
in
247247
let jsonschema_expr = create_value ~loc type_name v in
248248
[ jsonschema_expr ]

test/generate_schemas.ml

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -5,13 +5,13 @@ let schemas =
55
[
66
json_schema with_modules_jsonschema;
77
json_schema kind_jsonschema;
8-
json_schema kind_as_array_jsonschema;
8+
json_schema kind_as_string_jsonschema;
99
json_schema poly_kind_jsonschema;
10-
json_schema poly_kind_as_array_jsonschema;
10+
json_schema poly_kind_as_string_jsonschema;
1111
json_schema poly_kind_with_payload_jsonschema;
12-
json_schema poly_kind_with_payload_as_array_jsonschema;
12+
json_schema poly_kind_with_payload_as_string_jsonschema;
1313
json_schema poly_inherit_jsonschema;
14-
json_schema poly_inherit_as_array_jsonschema;
14+
json_schema poly_inherit_as_string_jsonschema;
1515
json_schema event_jsonschema;
1616
json_schema events_jsonschema;
1717
json_schema eventss_jsonschema;

0 commit comments

Comments
 (0)