Jsont JSON Encoding/Decoding
Dependencies
dune
1(libraries jsont jsont.bytesrw)
Core Patterns
Simple Object Codec
Map a JSON object to an OCaml record using Jsont.Object.map with mem for required fields:
ocaml
1type header = {
2 message_id : string;
3 method_ : string;
4 timestamp : int;
5}
6
7let header_codec =
8 Jsont.Object.map ~kind:"header"
9 (fun message_id method_ timestamp -> { message_id; method_; timestamp })
10 |> Jsont.Object.mem "messageId" Jsont.string ~enc:(fun h -> h.message_id)
11 |> Jsont.Object.mem "method" Jsont.string ~enc:(fun h -> h.method_)
12 |> Jsont.Object.mem "timestamp" Jsont.int ~enc:(fun h -> h.timestamp)
13 |> Jsont.Object.finish
Optional Fields
Use opt_mem for optional JSON fields. The constructor receives 'a option:
ocaml
1type config = {
2 name : string;
3 timeout : int; (* default if missing *)
4}
5
6let config_codec =
7 Jsont.Object.map ~kind:"config"
8 (fun name timeout_opt ->
9 { name; timeout = Option.value ~default:30 timeout_opt })
10 |> Jsont.Object.mem "name" Jsont.string ~enc:(fun c -> c.name)
11 |> Jsont.Object.opt_mem "timeout" Jsont.int ~enc:(fun c -> Some c.timeout)
12 |> Jsont.Object.finish
Skip Unknown Fields
Use skip_unknown before finish to ignore extra JSON fields (tolerant parsing):
ocaml
1let tolerant_codec =
2 Jsont.Object.map ~kind:"data" (fun id -> { id })
3 |> Jsont.Object.mem "id" Jsont.string ~enc:(fun d -> d.id)
4 |> Jsont.Object.skip_unknown (* ignore extra fields *)
5 |> Jsont.Object.finish
Nested Objects
Compose codecs for nested structures:
ocaml
1type request = { header : header; payload : payload }
2
3let request_codec payload_codec =
4 Jsont.Object.map ~kind:"request" (fun header payload -> { header; payload })
5 |> Jsont.Object.mem "header" header_codec ~enc:(fun r -> r.header)
6 |> Jsont.Object.mem "payload" payload_codec ~enc:(fun r -> r.payload)
7 |> Jsont.Object.finish
Lists
Use Jsont.list for JSON arrays:
ocaml
1type response = { items : item list }
2
3let response_codec =
4 Jsont.Object.map ~kind:"response" (fun items -> { items })
5 |> Jsont.Object.mem "items" (Jsont.list item_codec) ~enc:(fun r -> r.items)
6 |> Jsont.Object.finish
String Maps
Use Jsont.Object.as_string_map for objects with dynamic keys:
ocaml
1module String_map = Map.Make(String)
2
3(* JSON: {"key1": "value1", "key2": "value2"} *)
4let string_map_codec = Jsont.Object.as_string_map Jsont.string
5
6(* JSON: {"group1": [...], "group2": [...]} *)
7let groups_codec = Jsont.Object.as_string_map (Jsont.list item_codec)
Empty Object
For payloads that don't carry data:
ocaml
1let empty_payload_codec : unit Jsont.t =
2 Jsont.Object.map ~kind:"empty" ()
3 |> Jsont.Object.skip_unknown
4 |> Jsont.Object.finish
Custom Value Mapping
Use Jsont.map to transform between types:
ocaml
1type device_type = Sonos | Meross | Other
2
3let device_from_string =
4 Jsont.map ~kind:"device_type"
5 ~dec:(function "sonos" -> Sonos | "meross" -> Meross | _ -> Other)
6 ~enc:(function Sonos -> "sonos" | Meross -> "meross" | Other -> "other")
7 Jsont.string
Polymorphic Decoding with any
Handle multiple JSON shapes for backwards compatibility:
ocaml
1(* Device can be string (old format) or object (new format) *)
2let device_compat_codec =
3 Jsont.any ~kind:"device"
4 ~dec_string:device_from_string_codec (* handles "192.168.1.1" *)
5 ~dec_object:device_object_codec (* handles {"ip": "...", "type": "..."} *)
6 ~enc:(fun _ -> device_object_codec) (* always encode as object *)
7 ()
Null Values
Use Jsont.null for endpoints returning null:
ocaml
1(* For DELETE endpoints that return null on success *)
2match delete http ~sw token endpoint (Jsont.null ()) with
3| Ok () -> ...
Generic JSON
Use Jsont.json to preserve arbitrary JSON:
ocaml
1type characteristic = {
2 iid : int;
3 value : Jsont.json option; (* preserve any JSON value *)
4}
5
6let char_codec =
7 Jsont.Object.map ~kind:"char" (fun iid value -> { iid; value })
8 |> Jsont.Object.mem "iid" Jsont.int ~enc:(fun c -> c.iid)
9 |> Jsont.Object.opt_mem "value" Jsont.json ~enc:(fun c -> c.value)
10 |> Jsont.Object.finish
Encoding and Decoding
Use Jsont_bytesrw for string-based encoding/decoding:
ocaml
1(* Decode JSON string to OCaml value *)
2let decode codec s = Jsont_bytesrw.decode_string codec s
3(* Returns: ('a, Jsont.Error.t) result *)
4
5(* Encode OCaml value to JSON string *)
6let encode codec v =
7 match Jsont_bytesrw.encode_string codec v with
8 | Ok s -> s
9 | Error _ -> "{}" (* fallback for encoding errors *)
10
11(* Usage *)
12match Jsont_bytesrw.decode_string config_codec json_string with
13| Ok config -> (* use config *)
14| Error e -> (* handle error *)
15
16match Jsont_bytesrw.encode_string config_codec config with
17| Ok json_str -> (* send json_str *)
18| Error _ -> (* handle error *)
Common Helpers
Define module-level helpers for cleaner code:
ocaml
1let decode codec s = Jsont_bytesrw.decode_string codec s
2
3let encode codec v =
4 match Jsont_bytesrw.encode_string codec v with
5 | Ok s -> s
6 | Error _ -> ""
Base Types Reference
| OCaml Type | Jsont Codec | JSON Type |
|---|
string | Jsont.string | string |
int | Jsont.int | number |
float | Jsont.number | number |
bool | Jsont.bool | boolean |
'a list | Jsont.list codec | array |
'a option | Jsont.option codec | value or null |
unit | Jsont.null () | null |
| generic | Jsont.json | any JSON |
Best Practices
- Always use
~kind: Provide descriptive kind names for better error messages
- Use
skip_unknown for external APIs: Be tolerant of extra fields from third-party services
- Prefer
opt_mem with defaults: Handle missing fields gracefully with Option.value ~default:
- Compose small codecs: Build complex structures from simple, reusable codecs
- Define helper functions: Create
decode/encode helpers at module level for cleaner usage