plugins/ocaml-dev/skills/jsont/SKILL.md
JSON type-safe encoding and decoding using the OCaml jsont library. Use when Claude needs to: define typed JSON codecs for OCaml record types, parse JSON strings to OCaml values, or serialize OCaml values to JSON, or work with nested JSON structures
npx skillsauth add avsm/ocaml-claude-marketplace jsontInstall this skill globally with one command. Works with Claude Code, Cursor, and Windsurf.
3 of 9 scanners reported clean
Some scanners were skipped, did not run, or reported a non-clean status. Review each row below.
(libraries jsont jsont.bytesrw)
Map a JSON object to an OCaml record using Jsont.Object.map with mem for required fields:
type header = {
message_id : string;
method_ : string;
timestamp : int;
}
let header_codec =
Jsont.Object.map ~kind:"header"
(fun message_id method_ timestamp -> { message_id; method_; timestamp })
|> Jsont.Object.mem "messageId" Jsont.string ~enc:(fun h -> h.message_id)
|> Jsont.Object.mem "method" Jsont.string ~enc:(fun h -> h.method_)
|> Jsont.Object.mem "timestamp" Jsont.int ~enc:(fun h -> h.timestamp)
|> Jsont.Object.finish
Use opt_mem for optional JSON fields. The constructor receives 'a option:
type config = {
name : string;
timeout : int; (* default if missing *)
}
let config_codec =
Jsont.Object.map ~kind:"config"
(fun name timeout_opt ->
{ name; timeout = Option.value ~default:30 timeout_opt })
|> Jsont.Object.mem "name" Jsont.string ~enc:(fun c -> c.name)
|> Jsont.Object.opt_mem "timeout" Jsont.int ~enc:(fun c -> Some c.timeout)
|> Jsont.Object.finish
Use skip_unknown before finish to ignore extra JSON fields (tolerant parsing):
let tolerant_codec =
Jsont.Object.map ~kind:"data" (fun id -> { id })
|> Jsont.Object.mem "id" Jsont.string ~enc:(fun d -> d.id)
|> Jsont.Object.skip_unknown (* ignore extra fields *)
|> Jsont.Object.finish
Compose codecs for nested structures:
type request = { header : header; payload : payload }
let request_codec payload_codec =
Jsont.Object.map ~kind:"request" (fun header payload -> { header; payload })
|> Jsont.Object.mem "header" header_codec ~enc:(fun r -> r.header)
|> Jsont.Object.mem "payload" payload_codec ~enc:(fun r -> r.payload)
|> Jsont.Object.finish
Use Jsont.list for JSON arrays:
type response = { items : item list }
let response_codec =
Jsont.Object.map ~kind:"response" (fun items -> { items })
|> Jsont.Object.mem "items" (Jsont.list item_codec) ~enc:(fun r -> r.items)
|> Jsont.Object.finish
Use Jsont.Object.as_string_map for objects with dynamic keys:
module String_map = Map.Make(String)
(* JSON: {"key1": "value1", "key2": "value2"} *)
let string_map_codec = Jsont.Object.as_string_map Jsont.string
(* JSON: {"group1": [...], "group2": [...]} *)
let groups_codec = Jsont.Object.as_string_map (Jsont.list item_codec)
For payloads that don't carry data:
let empty_payload_codec : unit Jsont.t =
Jsont.Object.map ~kind:"empty" ()
|> Jsont.Object.skip_unknown
|> Jsont.Object.finish
Use Jsont.map to transform between types:
type device_type = Sonos | Meross | Other
let device_from_string =
Jsont.map ~kind:"device_type"
~dec:(function "sonos" -> Sonos | "meross" -> Meross | _ -> Other)
~enc:(function Sonos -> "sonos" | Meross -> "meross" | Other -> "other")
Jsont.string
anyHandle multiple JSON shapes for backwards compatibility:
(* Device can be string (old format) or object (new format) *)
let device_compat_codec =
Jsont.any ~kind:"device"
~dec_string:device_from_string_codec (* handles "192.168.1.1" *)
~dec_object:device_object_codec (* handles {"ip": "...", "type": "..."} *)
~enc:(fun _ -> device_object_codec) (* always encode as object *)
()
Use Jsont.null for endpoints returning null:
(* For DELETE endpoints that return null on success *)
match delete http ~sw token endpoint (Jsont.null ()) with
| Ok () -> ...
Use Jsont.json to preserve arbitrary JSON:
type characteristic = {
iid : int;
value : Jsont.json option; (* preserve any JSON value *)
}
let char_codec =
Jsont.Object.map ~kind:"char" (fun iid value -> { iid; value })
|> Jsont.Object.mem "iid" Jsont.int ~enc:(fun c -> c.iid)
|> Jsont.Object.opt_mem "value" Jsont.json ~enc:(fun c -> c.value)
|> Jsont.Object.finish
Use Jsont_bytesrw for string-based encoding/decoding:
(* Decode JSON string to OCaml value *)
let decode codec s = Jsont_bytesrw.decode_string codec s
(* Returns: ('a, Jsont.Error.t) result *)
(* Encode OCaml value to JSON string *)
let encode codec v =
match Jsont_bytesrw.encode_string codec v with
| Ok s -> s
| Error _ -> "{}" (* fallback for encoding errors *)
(* Usage *)
match Jsont_bytesrw.decode_string config_codec json_string with
| Ok config -> (* use config *)
| Error e -> (* handle error *)
match Jsont_bytesrw.encode_string config_codec config with
| Ok json_str -> (* send json_str *)
| Error _ -> (* handle error *)
Define module-level helpers for cleaner code:
let decode codec s = Jsont_bytesrw.decode_string codec s
let encode codec v =
match Jsont_bytesrw.encode_string codec v with
| Ok s -> s
| Error _ -> ""
| 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 |
~kindBad: No kind makes error messages unhelpful
let config_codec =
Jsont.Object.map (fun name -> { name }) (* No ~kind *)
|> Jsont.Object.mem "name" Jsont.string ~enc:(fun c -> c.name)
|> Jsont.Object.finish
(* Error: "expected string" - but where? *)
Good: Descriptive kind for clear errors
let config_codec =
Jsont.Object.map ~kind:"config" (fun name -> { name })
|> Jsont.Object.mem "name" Jsont.string ~enc:(fun c -> c.name)
|> Jsont.Object.finish
(* Error: "config: expected string for member 'name'" *)
Bad: Strict parsing breaks when API adds fields
(* API adds "created_at" field, your code breaks *)
let user_codec =
Jsont.Object.map ~kind:"user" (fun id name -> { id; name })
|> Jsont.Object.mem "id" Jsont.int ~enc:(fun u -> u.id)
|> Jsont.Object.mem "name" Jsont.string ~enc:(fun u -> u.name)
|> Jsont.Object.finish (* No skip_unknown! *)
Good: Tolerant parsing ignores unknown fields
let user_codec =
Jsont.Object.map ~kind:"user" (fun id name -> { id; name })
|> Jsont.Object.mem "id" Jsont.int ~enc:(fun u -> u.id)
|> Jsont.Object.mem "name" Jsont.string ~enc:(fun u -> u.name)
|> Jsont.Object.skip_unknown (* Ignore extra fields *)
|> Jsont.Object.finish
Bad: Nullable field without default causes runtime errors
(* Field missing → decode fails *)
let config_codec =
Jsont.Object.map ~kind:"config" (fun timeout -> { timeout })
|> Jsont.Object.mem "timeout" Jsont.int ~enc:(fun c -> c.timeout)
|> Jsont.Object.finish
Good: Use opt_mem with sensible default
let config_codec =
Jsont.Object.map ~kind:"config"
(fun timeout_opt -> { timeout = Option.value ~default:30 timeout_opt })
|> Jsont.Object.opt_mem "timeout" Jsont.int ~enc:(fun c -> Some c.timeout)
|> Jsont.Object.finish
Bad: Monolithic codec with duplicated patterns
let request_codec =
Jsont.Object.map ~kind:"request"
(fun msg_id method_ ts payload_id payload_data ->
{ header = { message_id = msg_id; method_; timestamp = ts };
payload = { id = payload_id; data = payload_data } })
|> Jsont.Object.mem "messageId" Jsont.string ~enc:(fun r -> r.header.message_id)
|> Jsont.Object.mem "method" Jsont.string ~enc:(fun r -> r.header.method_)
(* ... lots more fields mixed together *)
Good: Compose small, reusable codecs
let header_codec =
Jsont.Object.map ~kind:"header"
(fun message_id method_ timestamp -> { message_id; method_; timestamp })
|> Jsont.Object.mem "messageId" Jsont.string ~enc:(fun h -> h.message_id)
|> Jsont.Object.mem "method" Jsont.string ~enc:(fun h -> h.method_)
|> Jsont.Object.mem "timestamp" Jsont.int ~enc:(fun h -> h.timestamp)
|> Jsont.Object.finish
let request_codec =
Jsont.Object.map ~kind:"request" (fun header payload -> { header; payload })
|> Jsont.Object.mem "header" header_codec ~enc:(fun r -> r.header)
|> Jsont.Object.mem "payload" payload_codec ~enc:(fun r -> r.payload)
|> Jsont.Object.finish
Bad: Silently returning empty object on error
let encode codec v =
match Jsont_bytesrw.encode_string codec v with
| Ok s -> s
| Error _ -> "{}" (* Hides the error! *)
Good: Propagate or log encoding errors
let encode codec v =
match Jsont_bytesrw.encode_string codec v with
| Ok s -> Ok s
| Error e ->
Log.err (fun m -> m "JSON encode failed: %a" Jsont.Error.pp e);
Error (`Encode_error e)
~kind: Provide descriptive kind names for better error messagesskip_unknown for external APIs: Be tolerant of extra fields from third-party servicesopt_mem with defaults: Handle missing fields gracefully with Option.value ~default:decode/encode helpers at module level for cleaner usagetools
Working with the OxCaml extensions to OCaml. Use when the oxcaml compiler is available and you need high-performance, unboxing, stack allocation, data-race-free parallelism
development
Creating OCaml library tutorials using .mld documentation format with MDX executable examples. Use when discussing tutorials, documentation, .mld files, MDX, or interactive documentation.
development
Testing strategies for OCaml libraries. Use when discussing tests, alcotest, eio mocks, test structure, or test-driven development in OCaml projects.
development
Security hardening for OCaml libraries through systematic vulnerability research. Use when Claude needs to: (1) Research CVEs in similar implementations (C, Rust, Go, Python) and add regression tests, (2) Add fuzz tests for parsers and encoders, (3) Audit integer handling and buffer operations, (4) Test boundary conditions and malformed input, (5) Review cryptographic usage, (6) Add defensive checks against common vulnerability classes