How to generate JSON from a data structure?

I’ve been using Haskell to parse HTML and produce well formed JSON. (Repo) Is there a way to get this kind of strongly typed, locked-down JSON creation? E.g., in Haskell I define a data type for each kind of thing I want to generate:

-- The top-level organizational unit in the Nevada Revised Statutes
data Title =
  Title {
    name     ∷ Text,
    number   ∷ Integer,
    chapters ∷ [Chapter]
}

I can then generate the entire JSON tree representing this data type. Does OCaml have any libraries that support something like this? Thanks!

I believe using Yojson and ppx_deriving_yojson will do want you want. I’ve never used the latter before, though, so I can’t really speak to how strongly-typed, etc. the serialization/deserialization functions you get are.

2 Likes

Yes, use atdgen. It generates plain .mli and .ml files which are helpful to see what functions are available and how they are implemented should anything go wrong.

Another aspect of the design is that the ATD file can serve as strict documentation for your API. It is also possible to support code generation for other languages than OCaml. We have the equivalent of atdgen for Java (atdj) and it would be great to have some support for other languages as well.

For your example, you’d write a file statute.atd like this:

type title = {
  name: string;
  number: int;
  chapters: chapter list;
}

type chapter = string

Try the following and take look at the files that are generated:

$ atdgen -t statute.atd
$ atdgen -j -j-std statute.atd
$ atdgen -v statute.atd

(t = types, j = json, v = validators and record creators)

Also commonly used are optional fields with or without defaults:

type preferences = {
  id: string; (* required field *)
  ?img_url: string option; (* defaults to None *)
  ~pets: pet list; (* defaults to the empty list *)
}

This allows missing or null json fields. The _v generated module provides a function with the following signature:

val create_preferences : id:string -> ?img_url:string -> ?pets:pet list -> unit -> preferences

If you have abstract types, say a user ID that must follow a special syntax, you can use the wrap feature to get an OCaml type that maps to a basic type supported by json, like a string or an int. So instead of having type id = string, you can use your special id type:

type id = string wrap <ocaml module="ID">

where the ID module that you provide contains the following:

type t (* type of a well-formed ID *)
val wrap : string -> t (* would raise an exception on malformed input *)
val unwrap : t -> string
2 Likes

Depyt is pretty useful too.

1 Like