Reputation: 303371
Consider the following code that is intended to round numbers to the nearest one-hundredth and serialize the result to JSON:
require 'json'
def round_to_nearest( value, precision )
(value/precision).round * precision
end
a = [1.391332, 0.689993, 4.84678]
puts a.map{ |n| round_to_nearest(n,0.01) }.to_json
#=> [1.3900000000000001,0.6900000000000001,4.8500000000000005]
Is there a way to use JSON to serialize all numbers with a specific level of precision?
a.map{ ... }.to_json( numeric_decimals:2 )
#=> [1.39,0.69,4.85]
This could be either with the Ruby built-in JSON library or another JSON gem.
Edit: As noted in comments to answers below, I'm looking for a general solution to all JSON serialization for arbitrary data that includes numbers, not specifically a flat array of numbers.
Note that the above problem can be fixed in this specific case by rewriting the method:
def round_to_nearest( value, precision )
factor = 1/precision
(value*factor).round.to_f / factor
end
...but this does not solve the general desire to force a precision level during serialization.
Upvotes: 5
Views: 4736
Reputation: 303371
Since the built-in JSON library does not call #as_json
or #to_json
on Numerics (presumably for speed) we can use the ActiveSupport library from Rails (without needing Rails).
We do our monkey-patch delicately, so that it only takes effect when a user-specified option is passed when calling to_json
:
require 'active_support/json' # gem install activesupport
class Float
def as_json(options={})
if options[:decimals]
value = round(options[:decimals])
(i=value.to_i) == value ? i : value
else
super
end
end
end
data = { key: [ [ 2.991134, 2.998531 ], ['s', 34.127876] ] }
puts data.to_json #=> {"key":[[2.991134,2.998531],["s",34.127876]]}
puts data.to_json(decimals:2) #=> {"key":[[2.99,3],["s",34.13]]}
As shown in the last example, there's a little extra code used to convert integer-valued-floats to pure integers, just so that serialization doesn't waste bytes outputting 3.00
and can instead put just 3
(the same value in JSON and JS).
Upvotes: 3
Reputation: 34206
I'd just pre-round it using ruby's built-in round method: http://www.ruby-doc.org/core-1.9.3/Float.html#method-i-round
a.map{ |n| n.round(2) }.to_json
That looks clean to me instead of getting all types of custom libraries and passing in arguments.
Edit for comment:
I know you can do that with activesupport.
# If not using rails
require 'active_support/json'
class Float
def as_json(options={})
self.round(2)
end
end
{ 'key' => [ [ 3.2342 ], ['hello', 34.1232983] ] }.to_json
# => {"key":[[3.23],["hello",34.12]]}
More exact solution: better monkey-patch
Upvotes: 3
Reputation: 160571
Because you are dealing with floating-point, I'm inclined to say to convert to strings using format strings, and pass those. They'd allow you to set the precision nicely:
a = [1.391332, 0.689993, 4.84678]
a.map{ |f| '%.2f' % f }
=> ["1.39", "0.69", "4.85"]
If you want true floats, convert them back:
a.map{ |f| ('%.2f' % f).to_f }
=> [1.39, 0.69, 4.85]
From that point you could write something to override JSON's default Array serializer, or the Float serializer, however JSON does it, with one that accepts the precision, or format-string, you want.
Just to clarify, the use of to_f
would occur before serializing, which would not bloat the resulting JSON output or force any sort of "magic" on the receiving side. Floats would transfer and be deserialized as floats:
irb(main):001:0> require 'json'
true
irb(main):002:0> a = [1.391332, 0.689993, 4.84678]
[
[0] 1.391332,
[1] 0.689993,
[2] 4.84678
]
irb(main):003:0> a.map{ |f| ('%.2f' % f).to_f }.to_json
"[1.39,0.69,4.85]"
irb(main):004:0> JSON.parse(a.map{ |f| ('%.2f' % f).to_f }.to_json)
[
[0] 1.39,
[1] 0.69,
[2] 4.85
]
Start with something like:
class Float
def to_json
('%.2f'%self).to_f
end
end
(355.0/113).to_json # => 3.14
and figure out how to apply it to an array of Floats. You'll probably need to use the pure Ruby JSON though.
Upvotes: 0
Reputation: 1519
Upvotes: -2