ahrefs / atd

Static types for JSON APIs
Other
308 stars 53 forks source link

Should it be possible to round-trip json? #368

Open rbjorklin opened 8 months ago

rbjorklin commented 8 months ago

I was caught by surprise when trying to round-trip some json following this example in the documentation. Is round-tripping supposed to work?


let json = "{\"ID\":12345678,\"username\":\"kimforever\",\"background_color\":\"black\"}";;

let profile = Profile_j.profile_of_string json;;

let round_tripped_json = Profile_j.string_of_profile profile;;

(* "{\"ID\":12345678,\"username\":\"kimforever\",\"background_color\":<\"black\">}" *)

Notice how "black" comes back as <\"black\">.

The same behaviour is also present when using <ocaml repr="classic">.

cyberhuman commented 8 months ago

Can you try compiling the atd with -j-std? (IMO it should be the default)

rbjorklin commented 8 months ago

Yeah that fixed it! Thanks for the quick response @cyberhuman! This feels like it might be worth a mention somewhere close to where variants are shown.

EDIT: Now that I know what I'm looking for I found the relevant documentation. Unfortunately I have no good suggestion for how to make this more discoverable.

mjambon commented 8 months ago

Can you try compiling the atd with -j-std? (IMO it should be the default)

Yes. The only issue is backward compatibility. We might want to release this as part of a major version release. I started a list here.

rbjorklin commented 8 months ago

On the topic of round-tripping is there a way to work-around this behaviour?

my_type.atd:

type my_type = {
    my_number : float;
    my_key : string;
}

my_type_test.ml:

let t = My_type_t.{ my_number = 65.26; my_key = "ABC" }

let () = 
   Printf.printf "%f" t.my_number;
    (* Prints: "65.260000" *)

    Printf.printf "%s" (My_type_j.string_of_my_type t)
    (* Prints: "{\"my_number\":65.26000000000001,\"my_key\":\"ABC\"}" *)

I would be okay parsing my_number into a string as long as I can round-trip it to a json number while maintaining the number of significant figures.

mjambon commented 8 months ago

It's best to open a new issue for each new question.

Printing 65.26 instead of 65.26000000000001 while maintaining correctness is difficult or computationally expensive. Atdgen prints enough decimals to ensure that parsing the string representing the float will result in the original float. In this case, you can see that it works:

utop # 65.26 = 65.26000000000001;;
- : bool = true

The internal representation of OCaml floats or C doubles (IEEE-754 binary-64) is binary rather than decimal and provides no clue that there's a short decimal representation for the number. Printing a sufficiently large number of decimal digits (17 digits or so) ensures that parsing the string in decimal notation will recover the original float.

Maybe there are tricks we could use to print 65.26 as 65.26 correctly and cheaply but I'm not an expert in this field and I'm not aware of such tricks.