ahrefs/atd

Extracting strings from variants includes the escaped quotes

rbjorklin opened this issue · 4 comments

This might be expected behaviour but I nonetheless was surprised by it. See the following example:

my_type.atd:

type side = [
    | Buy <json name="B">
    | Sell <json name="S">
]

type my_type = {
    my_key : side;
    my_string : string;
}
let t = My_type_j.my_type_of_string {| {"my_key": "B", "my_string": "hey"} |}

let () =
  Printf.printf "%s" (My_type_j.string_of_my_type t);
  (* Prints: {"my_key":"B", "my_string": "hey"} *)

  My_type_j.string_of_side t.my_key;
  (* Evaluates to: "\"B\"" in utop *)

  Printf.printf "%s" (My_type_j.string_of_side t.my_key);
  (* Prints: "B"
  Would have expected: B without quotes *)

  Printf.printf "%s" t.my_string;
  (* Prints: hey
  without quotes as expected *)

Yes, the string_of_ converters produce JSON data. A JSON string is always double-quoted, so that's what you get. There's no function to give you directly the unquoted string. Your options include:

  • call My_type_j.string_of_side and then parse it as JSON with Yojson.Safe.from_string which will give you `String "B" from which you can extract the unquoted string.
  • or use another tool like ppx_show to derive printers that will print B Buy instead of "B" in a more OCaml-like syntax. For this, you have to annotate your ATD type definitions so that the OCaml definitions inherit the [@@deriving show]. See https://atd.readthedocs.io/en/latest/atdgen-reference.html?highlight=ppx#field-attr for an example.

Thanks @mjambon for the prompt and informative response! I've found atd very useful and appreciate all the time and effort you and the other maintainers have put into the project!

Armed with this information I'm going to mark this issue as closed.

Another comment if someone finds their way here in the future. Unfortunately the [@@ deriving show] route doesn't work either as that also prints extra characters.

My_type_j.show_side `Buy;;
(* Note the backtick in the string: "`Buy" *)

EDIT: Trying to use <ocaml repr="classic"> will only make it worse as then the module name is also included in the returned string.

EDIT2: I found this to be the easiest solution:

side.atd:

type order = {
  side : string wrap <ocaml t="My_type.t" wrap="My_type.of_string" unwrap="My_type.to_string">;
}

my_type.ml:

type t =
  | Buy
  | Sell

let to_string = function
  | Buy -> "BUY"
  | Sell -> "SELL"

let of_string = function
  | "BUY" -> Buy
  | "SELL" -> Sell
[@@ocaml.warning "-partial-match"]

EDIT: Trying to use will only make it worse as then the module name is also included in the returned string.

ppx_show has an option to not print the module name:

[@@deriving show { with_path = false }]