5

I need convert a Ruby hash which contains some BigDecimal fields into JSON.

I need the BigDecimal to be converted into Float/Integer, but the 'json' lib always convert it into the scientific notation.

require 'json'
require 'bigdecimal'

obj = {}
obj['created_at'] = BigDecimal('0.12345')

puts "JSON.dump(obj) = #{JSON.dump(obj)}"
puts "JSON.generate(obj) = #{JSON.generate(obj)}"
puts "JSON.fast_generate(obj) = #{JSON.fast_generate(obj)}"
puts "JSON.pretty_generate(obj) = #{JSON.pretty_generate(obj)}"

Results:

JSON.dump(obj) = {"created_at":"0.12345e0"}
JSON.generate(obj) = {"created_at":"0.12345e0"}
JSON.fast_generate(obj) = {"created_at":"0.12345e0"}
JSON.pretty_generate(obj) = {
  "created_at": "0.12345e0"
}

Is there any JSON lib that can specify the number format so when i parsing an object, the BigDecimal field won't be converted into the scientific notation?

obj['created_at'] = BigDecimal.new('0.12345')
JSON.parse(obj) = { "created_at": "0.12345" } # not 0.12345e0

I'm using ruby 2.4.1p111

wduan
  • 53
  • 5
  • What happens when you do something like `a=BigDecimal('0.12345')` and `a.to_f` – Abhinay Jul 31 '17 at 16:46
  • i know that, but what i want know is how to let JSON.parse(a) = 0.12345 – wduan Jul 31 '17 at 17:55
  • updated my answer – Abhinay Jul 31 '17 at 18:02
  • What problem are you trying to solve? `0.12345` and `0.12345e0` are both [valid JSON numbers](https://stackoverflow.com/questions/19554972/json-standard-floating-point-numbers) that represent the exact same value, and any standard JSON parser will correctly parse them as the same value. Are you getting an error somewhere? *Why* do you need the number in a different format? – Jordan Running Jul 31 '17 at 18:10

2 Answers2

2
BigDecimal('0.12345').to_f

Should give you the desired result.

Update:

JSON.parse(BigDecimal('0.12345').to_s) # => 0.12345

JSON.parse(BigDecimal('0.12345').to_s).to_s # => "0.12345"
Abhinay
  • 1,796
  • 4
  • 28
  • 52
  • Thanks. I guess convert the BigDecimal into the right format before invoking the JSON.parse is the only way. – wduan Jul 31 '17 at 19:00
1

The JSON library requires objects to have a string representation that can be used to reconstruct that object. If it is obvious what it is (int or float for example) those strings are interpreted back to those types:

> JSON.dump(1234)
=> "1234"
> JSON.dump(1234.456)
=> "1234.456"
> JSON.dump(1.2e22)
=> "1.2e+22"

Auto reconstruction:

> JSON.parse(JSON.dump(1234))
=> 1234
> JSON.parse(JSON.dump(1.2e22))
=> 1.2e+22

BigDecimal uses a specific representation that you are seeing:

> tgt=BigDecimal('0.12345')
=> 0.12345e0
> tgt.inspect
=> "0.12345e0"

While that is a legit format for a float string, it does not automatically trigger the JSON decoder to reconstruct a BigDecimal or float object if in that format:

> JSON.parse(JSON.dump(tgt))
=> "0.12345e0"

You can call .to_f prior to encoding to JSON (and potentially loose the extra precision of BigDecimal):

> JSON.parse(JSON.dump(tgt.to_f))
=> 0.12345

To get an automatic float.

Or, know what is a BigDecimal and reencode on entry:

> BigDecimal(JSON.parse(JSON.dump(tgt)))
=> 0.12345e0

Or, you can use BigDecimal's format options to convert to a string float representation (without loosing the BigDecimal internal representation):

> tgt.to_s('F')
=> "0.12345"

But, again, you may loose accuracy going from a BigDecimal=>BigDecimal.to_s('F')=>float when loaded as JSON because the decoder will automatically convert that representation to a normal float.

dawg
  • 98,345
  • 23
  • 131
  • 206
  • I see, thanks. I think I have to convert BigDecimal first instead of relying on the JSON.parse function. – wduan Jul 31 '17 at 19:01
  • You might consider that tremendous accurancy of just a normal float or normal integer and use those with JSON. Easier that way unless you have a specific reason to use BigDecimal. – dawg Jul 31 '17 at 19:09