package ppx_deriving_jsonschema

  1. Overview
  2. Docs
Jsonschema generator for ppx_deriving

Install

Dune Dependency

Authors

Maintainers

Sources

ppx_deriving_jsonschema-0.0.4.tbz
sha256=88e339b69c827673b85276068ea9b4f05f2ff1181bd18f5b9faa303bf0d5993f
sha512=e37e2c6aa839311a9a0bbdc313349ffbb0531adca6118775c9bc408c6bd0bfd474c979ae12f497bd30da8e1c36afb4bab22bb44531a2dc265bdcd82854259b83

Description

ppx_deriving_jsonschema is a ppx rewriter that generates jsonschema from ocaml types

Tags

jsonschema org:ahrefs syntax

Published: 02 May 2025

README

ppx_deriving_jsonschema

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

The conversion aims to be compatible with the existing json derivers:

  • https://github.com/melange-community/melange-json
  • https://github.com/ocaml-ppx/ppx_deriving_yojson
  • https://github.com/janestreet/ppx_yojson_conv

Installation

opam install ppx_deriving_jsonschema

[@@deriving jsonschema]

type address = {
  street: string;
  city: string;
  zip: string;
} [@@deriving jsonschema]

type t = {
  name: string;
  age: int;
  email: string option;
  address: address;
} [@@deriving jsonschema]

let schema = Ppx_deriving_jsonschema_runtime.json_schema t_jsonschema

Such a type will be turned into a JSON schema like this:

{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "type": "object",
  "properties": {
    "address": {
      "type": "object",
      "properties": {
        "zip": { "type": "string" },
        "city": { "type": "string" },
        "street": { "type": "string" }
      },
      "required": [ "zip", "city", "street" ]
    },
    "email": { "type": "string" },
    "age": { "type": "integer" },
    "name": { "type": "string" }
  },
  "required": [ "address", "age", "name" ]
}

Conversion rules

Basic types

Types int, int32, int64, nativeint, string, bytes, float, bool are converted to their JSON equivalents.

Type char is converted to { "type": "string", "minLength": 1, "maxLength": 1}.

Type 'a ref is treated as 'a.

Type unit is converted to { "type": "null" }.

List and arrays

OCaml lists and arrays are converted to { "type": "array", "items": { "type": "..." } }.

Tuples

Tuples are converted to { "type": "array", "prefixItems": [...] }.

type t = int * string [@@deriving jsonschema]
{
  "type": "array",
  "prefixItems": [ { "type": "integer" }, { "type": "string" } ],
  "unevaluatedItems": false,
  "minItems": 2,
  "maxItems": 2
}
Variants and polymorphic variants

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:

type t =
| Typ
| Class of string
[@@deriving jsonschema]
{
  "anyOf": [
    {
      "type": "array",
      "prefixItems": [ { "const": "Typ" } ],
      "unevaluatedItems": false,
      "minItems": 1,
      "maxItems": 1
    },
    {
      "type": "array",
      "prefixItems": [ { "const": "Class" }, { "type": "string" } ],
      "unevaluatedItems": false,
      "minItems": 2,
      "maxItems": 2
    }
  ]
}

Note that the implicit tuple in a polymorphic variant is flattened. This can be disabled using the ~polymorphic_variant_tuple flag.

type a = [ `A of int * string * bool ] [@@deriving jsonschema]
{
  "anyOf": [
    {
      "type": "array",
      "prefixItems": [
        { "const": "A" },
        { "type": "integer" },
        { "type": "string" },
        { "type": "boolean" }
      ],
      "unevaluatedItems": false,
      "minItems": 4,
      "maxItems": 4
    }
  ]
}
type b = [ `B of int * string * bool ] [@@deriving jsonschema ~polymorphic_variant_tuple]
{
  "anyOf": [
    {
      "type": "array",
      "prefixItems": [
        { "const": "B" },
        {
          "type": "array",
          "prefixItems": [
            { "type": "integer" },
            { "type": "string" },
            { "type": "boolean" }
          ],
          "unevaluatedItems": false,
          "minItems": 3,
          "maxItems": 3
        }
      ],
      "unevaluatedItems": false,
      "minItems": 2,
      "maxItems": 2
    }
  ]
}

A ~variant_as_string flag is exposed to obtain a more natural representation "anyOf": [{ "const": "..." }, ...]. This representation does not support payloads. For example:

type t =
| Typ
| Class of string
[@@deriving jsonschema ~variant_as_string]
{ "anyOf": [ { "const": "Typ" }, { "const": "Class" } ] }

If the JSON variant names differ from OCaml conventions, it is possible to specify the corresponding JSON string explicitly using [@name "constr"], for example:

type t =
| Typ   [@name "type"]
| Class of string [@name "class"]
[@@deriving jsonschema ~variant_as_string]
{ "anyOf": [ { "const": "type" }, { "const": "class" } ] }
Records

Records are converted to { "type": "object", "properties": {...}, "required": [...], "additionalProperties": false }.

The fields of type option are not included in the required list.

By default, additionalProperties are not allowed in objects. To allow additionalProperties, use the allow_extra_fields attribute:

type company = {
  name : string;
  employees : int;
}
[@@deriving jsonschema]
[@@jsonschema.allow_extra_fields]

This annotation will generate a schema with "additionalProperties": true, allowing for additional fields not defined in the record:

{
  "type": "object",
  "properties": {
    "name": { "type": "string" },
    "age": { "type": "integer" }
  },
  "required": [ "name", "age" ],
  "additionalProperties": true
}

When the JSON object keys differ from the ocaml field names, users can specify the corresponding JSON key implicitly using [@key "field"], for example:

type t = {
  typ    : float [@key "type"];
  class_ : float [@key "CLASS"];
}
[@@deriving jsonschema]
Inline Records in Variants

You can use the [@jsonschema.allow_extra_fields] attribute on a constructor with an inline record to allow additional fields in that record:

type inline_record_with_extra_fields =
  | User of { name : string; email : string } [@jsonschema.allow_extra_fields]
  | Guest of { ip : string }
[@@deriving jsonschema]

This will generate a schema that allows additional fields for the User variant's record but not for the Guest variant:

{
  "anyOf": [
    {
      "type": "array",
      "prefixItems": [
        { "const": "User" },
        {
          "type": "object",
          "properties": {
            "email": { "type": "string" },
            "name": { "type": "string" }
          },
          "required": [ "email", "name" ],
          "additionalProperties": true
        }
      ],
      "unevaluatedItems": false,
      "minItems": 2,
      "maxItems": 2
    },
    {
      "type": "array",
      "prefixItems": [
        { "const": "Guest" },
        {
          "type": "object",
          "properties": { "ip": { "type": "string" } },
          "required": [ "ip" ],
          "additionalProperties": false
        }
      ],
      "unevaluatedItems": false,
      "minItems": 2,
      "maxItems": 2
    }
  ]
}
References

Rather than inlining the definition of a type it is possible to use a json schema $ref using the [@ref "name"] attribute. In such a case, the type definition must be passed to Ppx_deriving_jsonschema_runtime.json_schema as a parameter.

type address = {
  street : string;
  city : string;
  zip : string;
}
[@@deriving jsonschema]

type t = {
  name : string;
  age : int;
  email : string option;
  home_address : address; [@ref "shared_address"]
  work_address : address; [@ref "shared_address"]
  retreat_address : address; [@ref "shared_address"]
}
[@@deriving jsonschema]

let schema =
  Ppx_deriving_jsonschema_runtime.json_schema
    ~definitions:[("shared_address", address_jsonschema)]
    t_jsonschema

Would produce the following schema:

{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "$defs": {
    "shared_address": {
      "type": "object",
      "properties": {
        "zip": { "type": "string" },
        "city": { "type": "string" },
        "street": { "type": "string" }
      },
      "required": [ "zip", "city", "street" ]
    }
  },
  "type": "object",
  "properties": {
    "retreat_address": { "$ref": "#/$defs/shared_address" },
    "work_address": { "$ref": "#/$defs/shared_address" },
    "home_address": { "$ref": "#/$defs/shared_address" },
    "email": { "type": "string" },
    "age": { "type": "integer" },
    "name": { "type": "string" }
  },
  "required": [
    "retreat_address", "work_address", "home_address", "age", "name"
  ]
}

Dependencies (3)

  1. ppxlib >= "0.24.0"
  2. dune >= "3.16"
  3. ocaml >= "4.08.0"

Dev Dependencies (7)

  1. odoc with-doc
  2. ocaml-lsp-server with-dev-setup
  3. ocamlformat with-dev-setup
  4. ppx_expect with-test
  5. yojson with-test
  6. melange-json-native with-test
  7. melange-json with-test

Used by

None

Conflicts

None

OCaml

Innovation. Community. Security.