json-tc Introduction ============ json-tc is a syntax extension of OCaml that can make the use of JSON data easier. From a special type declaration, the camlp4 preprocessor generates the code that converts between a JSON "abstract syntax tree" and specialized OCaml data structures such as objects, polymorphic variants, lists, arrays, tuples, etc. It will at the same time check that the structure of the JSON document is correct and produce OCaml data which is statically typed. For example, the following declaration defines the type of a point object: type point = < x: float; y: float > with json This automatically makes two functions available, with the following signature: val json_of_point : point -> Json_type.t val point_of_json : Json_type.t -> point Json_type.t is the type of parsed JSON data, which is provided by the json-wheel library. Function json_of_point would convert an OCaml object of type point into a JSON object. point_of_json works the other way around, and fails by raising the Json_type.Json_error exception if the input JSON data doesn't have the right format. Installation ============ Installation: make make install Uninstallation: make uninstall Usage ===== Basically, you must preprocess your OCaml file(s) with camlp4o pa_json_tc.cmo. Once installed using the standard procedure (ocamlfind), you can compile a file using these commands: # compile ocamlfind ocamlopt -c yourfile.ml -syntax camlp4o -package json-tc.syntax # link ocamlfind ocamlopt -o yourprog yourfile.cmx -linkpkg -package json-tc.syntax Build tools like OCamlMakefile take care of this nicely. Syntax ====== You must write a special type declaration that describes the expected format of the JSON data. There is a predefined mapping from OCaml types to JSON: OCaml type JSON type Properties of JSON data ---------- --------- ----------------------- string String float Number not an int int Number an int number* Number a float or an int bool Boolean list Array homogenous array Array homogenous tuple Array fixed length (string * 'a) assoc** Object an object read as an associative list (string, 'a) Hashtbl.t Object object or record Object additional methods/fields are ignored option any null means None polymorphic variants String or Array a String for constructors without an argument, or an Array of length 2 where the first element is a String that represents the constructor and the second element is the argument. classic variants String or Array a String for contructors without an argument, or an Array where the first element is the String that represents the constructor and the rest are the arguments. Unlike polymorphic variants, there may be several arguments (just don't use parentheses around them in the type definition). X.t*** defined by X.of_json and X.to_json --- *: the number type is an alias for float, but accepts JSON ints and converts them to OCaml floats. **: the assoc type is an alias for list, but converts from a JSON object. ***: X can be any simple module name, but module fields t, of_json and to_json are mandatory. A type definition is done like regular type definitions, but the keyword "with json" is placed after the type definition, as: type t = int * float with json ^^^^^^^^^ The type cannot be polymorphic, i.e. it doesn't support type parameters. A small set of basic types are supported (see table above). Other type names can be used only if they are part of the same definition. This works: type a = b and b = int list with json But the following doesn't work: type b = int list with json type a = b with json (* b is unknown to the preprocessor *) Example 1 ========= The following definition is correct: type point = < x: number; y: number > and coords = point array with json It can successfully load the following JSON data: [ { "x": 1, "y": 0.5 }, { "x": 0, "y": 0.3333333 } ] Full example: (* File example1.ml *) type point = < x: number; y: number > and coords = point array with json let json_string = " [ { \"x\": 1, \"y\": 0.5 }, { \"x\": 0, \"y\": 0.3333333 } ] " let json_tree = Json_io.json_of_string json_string let my_coords = coords_of_json json_tree let _ = Array.iter (fun p -> Printf.printf "(%g, %g)\n" p#x p#y) my_coords (* EOF *) Save the example as "example1.ml", compile it and run it: $ ocamlfind ocamlopt -o example1 -linkpkg -package json-tc -syntax camlp4o example1.ml $ ./example1 (1, 0.5) (0, 0.333333) Example 2 ========= This example shows you the representation that we chose for sum types in JSON: (* File example2.ml *) type colors = [ `Black | `White | `Rgb of (float * float * float) | `Any "*" ] list with json let my_colors = [ `Black; `White; `Any; `Rgb (1., 0., 0.); `Rgb (0., 1., 0.); `Rgb (0., 0., 1.) ] let _ = print_endline (Json_io.string_of_json (json_of_colors my_colors)) (* EOF *) $ ocamlfind ocamlopt -o example2 -linkpkg -package json-tc -syntax camlp4o example2.ml $ ./example2 [ "Black", "White", "*", [ "Rgb", [ 1.0, 0.0, 0.0 ] ], [ "Rgb", [ 0.0, 1.0, 0.0 ] ], [ "Rgb", [ 0.0, 0.0, 1.0 ] ] ] Note how we specified that `Any translates into "*" rather than "Any". The same technique is available to rename object methods, and it is crucial when some existing JSON format uses method names that are not valid OCaml identifiers. Credits ======= json-tc is based on the json-static/json-wheel libraries by Martin Jambon, and converted to use the type-conv library so that it can be used alongside other camlp4 syntax extensions.