{"id":18046317,"url":"https://github.com/benbellick/ppx_deriving_decoders","last_synced_at":"2025-08-18T15:32:24.918Z","repository":{"id":258592813,"uuid":"850441661","full_name":"benbellick/ppx_deriving_decoders","owner":"benbellick","description":"Auto generate decoders for OCaml","archived":false,"fork":false,"pushed_at":"2024-12-17T03:26:30.000Z","size":140,"stargazers_count":4,"open_issues_count":4,"forks_count":0,"subscribers_count":1,"default_branch":"main","last_synced_at":"2024-12-17T04:24:26.147Z","etag":null,"topics":["decoders","json","make-life-easy","ocaml","ppx"],"latest_commit_sha":null,"homepage":"","language":"OCaml","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/benbellick.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2024-08-31T19:24:00.000Z","updated_at":"2024-12-17T03:26:34.000Z","dependencies_parsed_at":"2024-10-19T18:35:13.237Z","dependency_job_id":"6a6f680c-9cb2-4546-aab6-e597d4ec31f3","html_url":"https://github.com/benbellick/ppx_deriving_decoders","commit_stats":null,"previous_names":["benbellick/ppx_deriving_decoders"],"tags_count":2,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/benbellick%2Fppx_deriving_decoders","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/benbellick%2Fppx_deriving_decoders/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/benbellick%2Fppx_deriving_decoders/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/benbellick%2Fppx_deriving_decoders/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/benbellick","download_url":"https://codeload.github.com/benbellick/ppx_deriving_decoders/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":230245884,"owners_count":18196261,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["decoders","json","make-life-easy","ocaml","ppx"],"created_at":"2024-10-30T19:06:42.349Z","updated_at":"2025-08-18T15:32:24.887Z","avatar_url":"https://github.com/benbellick.png","language":"OCaml","funding_links":[],"categories":[],"sub_categories":[],"readme":"# ppx_deriving_decoders: Automatically write mattjbray/ocaml-decoders\n\nThere are currently two major flavors of handling encoding and decoding data in OCaml.\n\n1. You can use something like [ppx_deriving_yojson](https://github.com/ocaml-ppx/ppx_deriving_yojson) to automatically generate encoders/decoders for your OCaml types, which works great! However, it gives some tough errors and there is limited customization of the decoders.\n2. You can use the a library like [mattjbray/ocaml-decoders](https://github.com/mattjbray/ocaml-decoders) to hand-write your encoders/decoders, which offers great errors and quite expansive customization! However, writing out encoders/decoders for all of your types is a lot of work. \n\nWhat if there was a way to get the best of both worlds?\n\nThis library helps streamline the process of using [mattjbray/ocaml-decoders](https://github.com/mattjbray/ocaml-decoders) by writing your encoders/decoders for you! Now, when you don't care about the implementation details of serializing/deserializing to e.g. JSON, you can just use the ppx to write the functions for you. But if you do care, you can generate a starting implementation and adjust it according to your preferences. \n\nThere are two primary ways in which this library can be of use. (More details of both follows.)\n\n1. \"I want to write a (e.g. JSON) decoder for a particular type but don't care about the details\" --\u003e You can then use this library via `[@@deriving decoders]` applied to your types. \n2. \"I want to write a (e.g. JSON) decoder for a particular type, but I care a lot about how it works and just want a good starting place\" --\u003e You can use this library via `[@@deriving_inline decoders]` applied to your types to generate the implementation in place.\n\n## Getting Started\n\n```\nopam install ppx_deriving_decoders\n```\n\nThe implementation is agnostic to the underlying decoders back-end. The only requirement is the presence of a module with the signature [`Decoders.Decode.S`](https://github.com/mattjbray/ocaml-decoders/blob/59c0dfbe6026af27fce96af82e650a875157385d/src/sig.ml#L8) as specified in [mattjbray/ocaml-decoders](https://github.com/mattjbray/ocaml-decoders), which is aliased to module `D` (for decoders, for encoders you need the corresponding implementation aliased to `E`).\n\nE.g., if you wanted to decode using `yojson`, you could use \n```\nopam install decoders-yojson\n```\n\n## Just generate the decoder for me\n\nSuppose we have the following file: \n\n```ocaml\n(* In file foo.ml *)\n\ntype bar = Int of int | String of string\n```\n\nTo generate a decoder for `bar`, first add the preprocessing directive to the appropriate dune file: \n```lisp\n (preprocess (pps ppx_deriving_decoders))\n```\n\nThen just add an implementer of `Decoders.Decode.S` to the file, aliased to `D`, and add the deriving extension:\n```ocaml\n(* In file foo.ml *)\nmodule D = Decoders_yojson.Safe.Decode\n\ntype bar = Int of int | String of string [@@deriving decoders]\n```\n\nAfter doing this, you will have available in this module a value `bar_decoder` of type `bar D.decoder`. Then you'll be able to use this decoder freely, e.g.:\n```ocaml\nlet () = assert (\n  match D.decode_string bar_decoder {|{\"Int\": 10}|} with\n  | Ok b -\u003e b = Int 10\n  | Error _ -\u003e false\n)\n```\n\n## Only get the decoder started for me\nSuppose we have the same file again:\n```ocaml\n(* In file foo.ml *)\n\ntype bar = Int of int | String of string\n```\nTo generate a decoder for `bar`, we again first add the preprocessing directive to the appropriate dune file: \n```lisp\n (preprocess (pps ppx_deriving_decoders))\n```\nWe change the file to be\n```ocaml\n(* In file foo.ml *)\nmodule D = Decoders_yojson.Safe.Decode\n\ntype bar = Int of int | String of string [@@deriving_inline decoders]\n\n[@@@deriving.end]\n```\n\nThen, after running `dune build --auto-promote`, our file will become (after applying `ocamlformat`):\n```ocaml\n(* In file foo.ml *)\nmodule D = Decoders_yojson.Safe.Decode\n\ntype bar = Int of int | String of string [@@deriving_inline decoders]\nlet _ = fun (_ : bar) -\u003e ()\n\nlet bar_decoder =\n  let open D in\n  single_field (function\n    | \"Int\" -\u003e D.int \u003e|= fun arg -\u003e Int arg\n    | \"String\" -\u003e D.string \u003e|= fun arg -\u003e String arg\n    | any -\u003e D.fail @@ Printf.sprintf \"Unrecognized field: %s\" any)\n\nlet _ = bar_decoder\n[@@@deriving.end]\n```\n\nYou can now freely remove the deriving attributes, and edit the decoder as you see fit!\n\n## More complicated example\nThe following file:\n```ocaml\n(* In file foo.ml *)\nmodule D = Decoders_yojson.Safe.Decode\n\ntype expr = Num of int | BinOp of op * expr * expr\nand op = Add | Sub | Mul | Div [@@deriving_inline decoders]\n\n[@@@deriving.end]\n```\nafter invoking `dune build --auto-promote` (plus `ocamlformat`) will yield:\n```ocaml \n(* In file foo.ml *)\n type expr = Num of int | BinOp of op * expr * expr\n and op = Add | Sub | Mul | Div [@@deriving_inline decoders]\n\nlet _ = fun (_ : expr) -\u003e ()\nlet _ = fun (_ : op) -\u003e ()\n\n[@@@ocaml.warning \"-27\"]\n\nlet expr_decoder op_decoder =\n  D.fix (fun expr_decoder_aux -\u003e\n      let open D in\n      single_field (function\n        | \"Num\" -\u003e D.int \u003e|= fun arg -\u003e Num arg\n        | \"BinOp\" -\u003e\n            let open D in\n            let ( \u003e\u003e=:: ) fst rest = uncons rest fst in\n            op_decoder \u003e\u003e=:: fun arg0 -\u003e\n            expr_decoder_aux \u003e\u003e=:: fun arg1 -\u003e\n            expr_decoder_aux \u003e\u003e=:: fun arg2 -\u003e\n            succeed (BinOp (arg0, arg1, arg2))\n        | any -\u003e D.fail @@ Printf.sprintf \"Unrecognized field: %s\" any))\n\nlet _ = expr_decoder\n\nlet op_decoder op_decoder =\n  let open D in\n  single_field (function\n    | \"Add\" -\u003e succeed Add\n    | \"Sub\" -\u003e succeed Sub\n    | \"Mul\" -\u003e succeed Mul\n    | \"Div\" -\u003e succeed Div\n    | any -\u003e D.fail @@ Printf.sprintf \"Unrecognized field: %s\" any)\n\nlet _ = op_decoder\nlet op_decoder = D.fix op_decoder\nlet _ = op_decoder\nlet expr_decoder = expr_decoder op_decoder\nlet _ = expr_decoder\n\n[@@@ocaml.warning \"+27\"]\n\n[@@@deriving.end]\n```\nNotice that the mutual recursion is handled for you!\n\n## Type vars\nThe `ppx` can also handle types with type variables: \n```ocaml\ntype 'a wrapper = { wrapped : 'a } [@@deriving_inline decoders]\n[@@@deriving.end]\n```\nbecomes (additionally with `ocamlformat`): \n\n```ocaml\ntype 'a record_wrapper = { wrapped : 'a } [@@deriving_inline decoders]\n\nlet _ = fun (_ : 'a record_wrapper) -\u003e ()\n\nlet record_wrapper_decoder a_decoder =\n  let open D in\n  let open D.Infix in\n  let* wrapped = field \"wrapped\" a_decoder in\n  succeed { wrapped }\n\nlet _ = record_wrapper_decoder\n\n[@@@deriving.end]\n```\nNotice that the decoder for the type variable becomes a parameter of the generated decoder!\n\n## Encoders\nAll of the above information also applies to generating encoders. Using the above type as an example: \n```ocaml\ntype 'a wrapper = { wrapped : 'a } [@@deriving_inline decoders]\n[@@@deriving.end]\n```\nbecomes (additionally with `ocamlformat`): \n\n```ocaml\ntype 'a wrapper = { wrapped : 'a } [@@deriving_inline encoders]\n\nlet _ = fun (_ : 'a record_wrapper) -\u003e ()\n\nlet wrapper_encoder a_encoder { wrapped } =\n  E.obj [ (\"wrapped\", a_encoder wrapped) ]\n\nlet _ = record_wrapper_encoder\n\n[@@@deriving.end]\n```\n\nOf course, you can generate both by using `[@@deriving_inline decoders, encoders]` or `[@@deriving decoders, encoders]`. The corresponding pair will be inverses of one another provided that all prior referenced decoder/encoder pairs are inverses!\n\n\n## Example Workflow\n\nSuppose you wanted to start gathering trading data from [Tiingo](https://app.tiingo.com/welcome). So you navigate over to the [End-of-Day Rest API Endpoint](https://www.tiingo.com/documentation/end-of-day). You're going to need to decode this JSON. First what you're going to do is match your type exactly to the expected shape:\n```ocaml\nmodule EndOfDay = struct\n  type t = {\n    date : string;\n    close : float;\n    high : float;\n    low : float;\n    open : float;\n    volume : int;\n    adjClose : int;\n    adjHigh : float;\n    adjLow : float;\n    adjOpen : float;\n    adjVolume : int;\n    divCash : float;\n    splitFactor : float;\n  }\nend\n```\nHowever, `open` is a reserved keyword in OCaml, and the idiomatic solution is to append an underscore. Now you can apply your decoder:\n```ocaml\nmodule EndOfDay = struct\n  type t = {\n    date : string;\n    close : float;\n    high : float;\n    low : float;\n    open_ : float;\n    volume : int;\n    adjClose : int;\n    adjHigh : float;\n    adjLow : float;\n    adjOpen : float;\n    adjVolume : int;\n    divCash : float;\n    splitFactor : float;\n  }\n  [@@deriving decoders]\nend\n```\nBut of course, this is going to generate a decoder which expects a field called `\"open_\"` rather than the intended `\"open\"`! So, you customize your decoder by generating it inline:\n```\nmodule EndOfDay = struct\n  type t = {\n    date : string;\n    close : float;\n    high : float;\n    low : float;\n    open_ : float;\n    volume : int;\n    adjClose : int;\n    adjHigh : float;\n    adjLow : float;\n    adjOpen : float;\n    adjVolume : int;\n    divCash : float;\n    splitFactor : float;\n  }\n  [@@deriving_inline decoders]\n  [@@@deriving.end]\nend\n```\nYou apply `dune build --auto-promote` (followed by `ocamlformat`) and get:\n```ocaml\nmodule EndOfDay = struct\n  type t = {\n    date : string;\n    close : float;\n    high : float;\n    low : float;\n    open_ : float;\n    volume : int;\n    adjClose : int;\n    adjHigh : float;\n    adjLow : float;\n    adjOpen : float;\n    adjVolume : int;\n    divCash : float;\n    splitFactor : float;\n  }\n  [@@deriving_inline decoders]\n\n  let _ = fun (_ : t) -\u003e ()\n\n  let t_decoder =\n    let open D in\n    let open D.Infix in\n    let* date = field \"date\" D.string in\n    let* close = field \"close\" D.float in\n    let* high = field \"high\" D.float in\n    let* low = field \"low\" D.float in\n    let* open_ = field \"open_\" D.float in\n    let* volume = field \"volume\" D.int in\n    let* adjClose = field \"adjClose\" D.int in\n    let* adjHigh = field \"adjHigh\" D.float in\n    let* adjLow = field \"adjLow\" D.float in\n    let* adjOpen = field \"adjOpen\" D.float in\n    let* adjVolume = field \"adjVolume\" D.int in\n    let* divCash = field \"divCash\" D.float in\n    let* splitFactor = field \"splitFactor\" D.float in\n    succeed\n      {\n        date;\n        close;\n        high;\n        low;\n        open_;\n        volume;\n        adjClose;\n        adjHigh;\n        adjLow;\n        adjOpen;\n        adjVolume;\n        divCash;\n        splitFactor;\n      }\n\n  let _ = t_decoder\n\n  [@@@deriving.end]\nend\n```\nAnd now, fixing it is as easy as adjusting the argument to `field` above for the value `open_`!\n```ocaml\nmodule EndOfDay = struct\n  type t = {\n    date : string;\n    close : float;\n    high : float;\n    low : float;\n    open_ : float;\n    volume : int;\n    adjClose : int;\n    adjHigh : float;\n    adjLow : float;\n    adjOpen : float;\n    adjVolume : int;\n    divCash : float;\n    splitFactor : float;\n  }\n\n  let t_decoder =\n    let open D in\n    let open D.Infix in\n    let* date = field \"date\" D.string in\n    let* close = field \"close\" D.float in\n    let* high = field \"high\" D.float in\n    let* low = field \"low\" D.float in\n    let* open_ = field \"open\" D.float in\n    let* volume = field \"volume\" D.int in\n    let* adjClose = field \"adjClose\" D.int in\n    let* adjHigh = field \"adjHigh\" D.float in\n    let* adjLow = field \"adjLow\" D.float in\n    let* adjOpen = field \"adjOpen\" D.float in\n    let* adjVolume = field \"adjVolume\" D.int in\n    let* divCash = field \"divCash\" D.float in\n    let* splitFactor = field \"splitFactor\" D.float in\n    succeed\n      {\n        date;\n        close;\n        high;\n        low;\n        open_;\n        volume;\n        adjClose;\n        adjHigh;\n        adjLow;\n        adjOpen;\n        adjVolume;\n        divCash;\n        splitFactor;\n      }\nend\n```\nAnd now you see, generating the appropriate decoder took no more than 5 seconds once `ppx_deriving_decoders` is installed! \n\n## Limitations\n- Some of the decoders can be quite complicated relative to what you would write by hand\n- There are a lot of rough edges in places like: \n  - Error reporting\n  - Correctly handling `loc`\n- In an ideal world, it would be nice to generate the corresponding decoders/encoders within their own submodule. It remains to be seen how this can be done. \n\n## Future Work\n- [ ] Simplify generated decoders\n- [ ] Generate decoders from a module\n- [ ] How to handle types produced from functors inline\n\n## Contributing\n\nContributions are always welcome. Please create an issue as appropriate, and open a PR into the `main` branch and I'll have a look :) \n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fbenbellick%2Fppx_deriving_decoders","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fbenbellick%2Fppx_deriving_decoders","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fbenbellick%2Fppx_deriving_decoders/lists"}