janestreet / ppx_yojson_conv

[@@deriving] plugin to generate Yojson conversion functions
MIT License
58 stars 8 forks source link

Support for custom conversion functions #10

Open hcarty opened 2 years ago

hcarty commented 2 years ago

Most libraries don't come with of_yojson and yojson_of functions. It would be very useful to be able to provide a custom module with (de)serialization functions or direct references to the functions.

An example of what direct references to custom functions might look like:

open Core_kernel

let time_of_yojson json =
  match json with
  | `String s -> Time_ns.of_string s
  | _ -> failwith "Invalid time"

let yojson_of_time time = `String (Time.to_string time)

type t = {
  label : string;
  timestamp: Time_ns.t; [@yojson_of time_of_yojson] [@of_yojson yojson_of_time]
}
[@@deriving yojson]

Those two functions can be wrapped in a module easily enough. However, that wrapping would make the type t definition more difficult to understand because we'd no longer see directly in the type that timestamp is a Time_ns.t.

An example with a custom module:

open Core_kernel

module Json = struct
  module Time_ns = struct
    type t = Time_ns.t

    let t_of_yojson json =
      match json with
      | `String s -> Time_ns.of_string s
      | _ -> failwith "Invalid time"
    ;;

    let yojson_of_t time = `String (Time.to_string time)
  end
end

type t = {
  label : string;
  timestamp : Time_ns.t; [@yojson.module Json.Time_ns]
}
[@@deriving yojson]

In this case we could directly replace timestamp : Time_ns.t with timestamp : Json.Time_ns.t but that requires a person reading the code to check and see what the definition of Json.Time_ns.t is. Specifying a custom module avoids placing that burden on reader.

hcarty commented 2 years ago

This would also help when using other ppx derivers alongside ppx_yojson_conv. For example, when using ppx_compare and [@@deriving equal, yojson] the following example would need an additional [@@deriving equal] on the type t = Time_ns.t alias as the deriver currently exists. With support for a syntax like the [@yojson.module Json.Time_ns] example above that extra annotation wouldn't be needed.

module Json = struct
  module Time_ns = struct
    type t = Time_ns.t

    let t_of_yojson json =
      match json with
      | `String s -> Time_ns.of_string s
      | _ -> failwith "Invalid time"
    ;;

    let yojson_of_t time = `String (Time.to_string time)
  end
end

type t = {
  label : string;
  timestamp : Json.Time_ns.t;
}
[@@deriving equal, yojson]
(* This will give an error because [Json.Time_ns.equal] does not exist *)