Custom rule for decoding JSON input with Jason

I am working with an API that provides currency conversion rates in JSON like this:

  "fx_rate": 1.01

When reading from that API, I get a map %{"fx_rate" => 1.01} and as far as I understand I immediately lose precision because I convert the literal representation of “1.01” to float 1.01 which might not be the same internally.

I’d like to make sure that Jason does not parse this 1.01 to float but instead returns it as string %{"fx_rate" => "1.01"}. Then I have exact representation and can use to make further computations.

Is there a way to advise Jason to treat that field as string?

I was thinking about making a plug that changes the request body still represented as a string. It would find the "fx_rate": 1.01, and change it to "fx_rate": "1.01" with a regular expression, so that Jason can later do its job.

But I figured I will ask here first, if you have less hacky ideas :slight_smile:

1 Like

If you want precision in decimals, you can use fixed point arithmetic.

Simple idea - represent everything in integers - multiply everything by 10 n where n is the number of precision digits .

# i want 2 digits precision so i will multiply with 100
0 * 100 = 0
0.5 * 100 = 50
3.1415 * 100 = 314
  "fx_rate": 101 # 1.01 * 100

This technique is used in finance, gaming, graphics, etc

Have you seen the floats: :decimals option to Jason.decode/2? That might be what you’re looking for.

uses to parse the binary into a Decimal struct with arbitrary precision.

Edit: For Plug, that looks like this:

  plug Plug.Parsers,
    parsers: [:json],
    json_decoder: {Jason, :decode!, [[floats: :decimals]]}

There is the floats: decimal option, but out of curiosity, would it ever really be a problem if you just use Float.to_string.

> Float.to_string(0.1)
> Float.to_string(0.01)

While “The closest representable number to 0.1 is 0.1000000014” it is still converted to 0.1

Thank you guys! I missed the floats: :decimal!

This plug example has now been added to the docs.

1 Like