NathanReb / ppx_yojson

OCaml PPX extension for JSON literals and patterns
BSD 2-Clause "Simplified" License
42 stars 5 forks source link
ezjsonm json ocaml ppx-extension ppx-rewriter yojson

ppx_yojson

PPX extension for JSON literals and patterns. It supports yojson and ezjsonm.

Based on an original idea by @emillon.

Build Status

Overview

ppx_yojson lets you write Yojson or Ezjsonm expressions and patterns using ocaml syntax to make your code more concise and readable.

It rewrites %yojson and %ezjsonm extension points based on the content of the payload.

For example you can turn:

let json =
  `List
    [ `Assoc
        [ ("name", `String "Anne")
        ; ("grades", `List [`String "A"; `String "B-"; `String "B+"]
        ]
    ; `Assoc
        [ ("name", `String "Bernard")
        ; ("grades", `List [`String "B+"; `String "A"; `String "B-"]
        ]
    ]

into:

let json =
  [%yojson
    [ {name = "Anne"; grades = ["A"; "B-"; "B+"]}
    ; {name = "Bernard"; grades = ["B+"; "A"; "B-"]}
    ]
  ]

Installation and usage

You can install ppx_yojson using opam:

$ opam install ppx_yojson

If you're building your library or app with dune, add the following field to your library, executable or test stanza:

(preprocess (pps ppx_yojson))

You can now use the %yojson or %ezjsonm extensions in your code. See the expressions and patterns sections for the detailed syntax.

Syntax

Expressions

The expression rewriter supports the following Yojson and Ezjsonm values:

The resulting expression are not constrained, meaning it works with Yojson.Safe or Yojson.Basic regardless.

Anti-quotation

You can escape regular Yojson or Ezjsonm expressions within a payload using [%aq json_expr]. You can use this to insert variables in the payload. For example:

let a = `String "a"
let json = [%yojson { a = [%aq a]; b = "b"}]

is rewritten as:

let a = `String "a"
let json = `Assoc [("a", a); (b, `String "b")]

Note that the payload in a %aq extension should always subtype one of the Yojson types.

Patterns

Note that the pattern extension expects a pattern payload and must thus be invoked as [%yojson? pattern] or [%ezjsonm? pattern].

The pattern rewriter supports the following:

Record patterns

Json objects fields order doesn't matter so you'd expect the {a = 1; b = true} pattern to match regardless of the parsed json being {"a": 1, "b": true} or {"b": true, "a": 1}.

Since json objects are represented as lists, the order of the fields in the rewritten pattern does matter.

To allow you to write such patterns concisely and without having to care for the order of the fields, the record pattern is expanded to an or-pattern that matches every permutation of the (string * json) list. This is the reason of the limitations mentioned in the above list. Also note that there is no limitation on nesting such patterns but you probably want to avoid doing that too much.

This is provided mostly for convenience. If you want efficient code and/or to handle complex json objects I recommend that you use ppx_deriving_yojson or ppx_yojson_conv instead.

To clarify, the following code:

let f = function
  | [%yojson? {a = 1; b = true}] -> (1, true)

is expanded into:

let f = function
  | ( `Assoc [("a", `Int 1); ("b", `Bool true)]
    | `Assoc [("b", `Bool true); ("a", `Int 1)]
    ) -> (1, true)

Anti-quotation

You can also escape regular Ezjsonm or Yojson patterns in ppx_yojson pattern extensions' payload using [%aq? json_pat]. You can use it to further deconstruct a JSON value. For example:

let f = function
  | [%yojson? {a = [%aq? `Int i]} -> i + 1

is expanded into:

let f = function
  | `Assoc [("a", `Int i)] -> i + 1

Advanced use

Object keys

Some valid JSON object key's name are not valid OCaml record fields. Any capitalized word or OCaml keyword such as type or object.

You can still assemble such JSON literals with ppx_yojson using a leading underscore in the record field name. It will strip exactly one leading underscore.

For example, the following:

let json = [%yojson { _type = "a"; __object = "b"}]

will be expanded to:

let json = `Assoc [("type", `String "a"); ("_object", `String "b")]

Alternatively, you can attach an [@as "key_name"] attribute to the relevant field to provide the key to use in the JSON object literal.

The following:

let json = [%yojson {dummy = "a" [@as "type"]; some_key = "b"}

will be expanded to:

let json = `Assoc [("type", `String "a"); ("some_key", `String "b")]