Need help with your JSON?

Try our JSON Formatter tool to automatically identify and fix syntax errors in your JSON. JSON Formatter tool

OCaml Type-Safe JSON Handling Libraries

If you want type-safe JSON in OCaml, the practical answer in 2026 is still to build on Yojson and add code generation on top. Raw JSON AST handling is useful for generic tools, but for application code you usually want generated encoders and decoders that stay aligned with your OCaml types.

The main decision is not whether to use JSON derivation, but which derivation style fits your project: ecosystem-friendly and result-based, Jane Street-oriented with richer PPX options, or schema-first for large external APIs. That is what search users usually need answered first.

Quick Pick

  • Choose `yojson` + `ppx_deriving_yojson` if you want the safest general recommendation for most OCaml projects. It works in plain Dune setups, has broad compiler compatibility, and generated decoders return `('a, string) result`.
  • Choose `yojson` + `ppx_yojson_conv` if you already use Base/Core or other Jane Street PPXs. It gives you excellent naming, defaulting, and field-control attributes, but the current opam release targets OCaml `>= 5.3.0`.
  • Choose `atdgen` if JSON is a long-lived contract between services and you want a schema file that generates serializers, deserializers, and validators.
  • Use bare `yojson` alone only when the JSON shape is intentionally dynamic or user-defined, such as config editors, formatters, or generic transformation tools.

Yojson 3.0.0

Parser, printer, and `Yojson.Safe.t` AST. Essential building block, but not type safety by itself.

ppx_deriving_yojson 3.10.0

The most portable “derive JSON from types” option today. Good default for libraries and mixed ecosystems.

ppx_yojson_conv v0.17.1

Jane Street's current PPX deriver. Strong fit inside Base/Core stacks, with useful field-control attributes and naming helpers.

atdgen 3.0.1

Schema-first code generation for durable external APIs, larger contracts, and multi-service systems.

Version notes above reflect current opam package listings as of March 2026.

Best Starting Point for Most Projects

For a typical Dune project that just wants safe encode/decode logic with clear boundary errors, ppx_deriving_yojson is the cleanest recommendation. It generates `type_to_yojson` and `type_of_yojson`, and the decoder returns a `result` instead of forcing exception-based control flow.

Minimal Dune Setup

`dune`

(library
  (name api_types)
  (libraries yojson ppx_deriving_yojson.runtime)
  (preprocess (pps ppx_deriving_yojson)))

Example: Result-Based Decoding

`types.ml`

type status =
  | Draft
  | Published
[@@deriving yojson]

type article = {
  id : int;
  title : string;
  tags : string list [@default []];
  status : status;
  published_at : string option;
} [@@deriving yojson]

let decode_article raw =
  raw
  |> Yojson.Safe.from_string
  |> article_of_yojson

let () =
  let raw =
    {|{"id":1,"title":"Intro","tags":["ocaml","json"],"status":["Published"],"published_at":null}|}
  in
  match decode_article raw with
  | Ok article ->
      article_to_yojson article
      |> Yojson.Safe.pretty_to_string
      |> print_endline
  | Error path ->
      prerr_endline ("Invalid JSON at " ^ path)

Two practical details matter here. First, the decoder returns a `result`, which makes boundary validation easy to compose. Second, regular variants are encoded as JSON arrays such as `["Published"]`, not as JSON objects. That trips people up when they switch from examples written for other languages.

When `ppx_yojson_conv` Is the Better Fit

ppx_yojson_conv is still an excellent choice, but it is no longer accurate to present it as a universal default. It is part of Jane Street's PPX stack, and the latest package line is aimed at current Base/Core environments.

  • It generates functions named `yojson_of_type` and `type_of_yojson`, which differ from `ppx_deriving_yojson`'s `type_to_yojson` naming.
  • Primitive converters need to be in scope, commonly via `open Ppx_yojson_conv_lib.Yojson_conv.Primitives`.
  • It has strong ergonomics for field naming and omission rules, including `[@key]`, `[@name]`, `[@default]`, `[@yojson.option]`, `[@yojson_drop_default]`, and `[@@yojson.allow_extra_fields]`.
  • If you are on an older compiler line, verify package compatibility first instead of assuming the newest release will install unchanged.

`ppx_yojson_conv` Example

open Ppx_yojson_conv_lib.Yojson_conv.Primitives

type payload = {
  user_id : int [@key "userId"];
  nickname : string option [@yojson.option];
  retries : int [@default 3] [@yojson_drop_default (=)];
} [@@deriving yojson]

let outgoing =
  yojson_of_payload { user_id = 7; nickname = None; retries = 3 }

When `atdgen` Is Worth the Extra Step

atdgen is not the lightest tool for a single internal record, but it is a strong choice when you want the JSON contract reviewed independently from OCaml code.

  • Use it when several services or repositories need to agree on the same payload shapes.
  • Use it when generated validators and durable schema files matter more than the shortest local setup.
  • Skip it for tiny internal-only payloads where `[@@deriving yojson]` on the type is already enough.

Common Mistakes and Caveats

  • Bare `yojson` is not type-safe by itself. It gives you a JSON tree and parsing functions, not schema validation.
  • Unknown field handling is a product decision. `ppx_deriving_yojson` is strict by default but can be configured with { strict = false }. `ppx_yojson_conv` uses `[@@yojson.allow_extra_fields]` when you want to ignore extras.
  • Variant JSON shapes are easy to misremember. Both major derivers encode normal variants as arrays, so a constructor with no payload becomes something like `["Draft"]`.
  • Very large integers need care in cross-runtime systems. If JSON might be consumed in JavaScript or another double-based runtime, prefer explicit string encoding for large `int64` or `nativeint` values.
  • Missing fields and `None` are not always the same thing. Decide whether you want a field to be nullable, to have a default, or to be omitted entirely, and model that behavior explicitly.

Conclusion

For most new OCaml code, start with `yojson` plus `ppx_deriving_yojson`. Move to `ppx_yojson_conv` if your project already lives in the Jane Street ecosystem or you want its richer PPX feature set. Reach for `atdgen` when the JSON contract itself needs to be treated as a first-class artifact. Whichever route you choose, keeping the JSON boundary derived from types is the main step that makes OCaml JSON handling reliable.

Need help with your JSON?

Try our JSON Formatter tool to automatically identify and fix syntax errors in your JSON. JSON Formatter tool