Skip to content

Loading…

BigDecimal should dump to a String, at least in compat mode #59

Merged
merged 1 commit into from

2 participants

@cgriego

The vast majority of JSON libraries across various languages load JSON numeric data types in using floating point numbers. The only safe way to maintain the precision of BigDecimal languages with 3rd party clients is to encode the data in JSON as a String and allow client code downstream from the JSON decoder use the correct language data type. This behavior is mirrored in the JSON gem, Yajl gem, and ActiveSupport.

I left the other modes alone because I'm not sure what the loss of precision means for people who use these modes. 80.51 is one such number that, at least in Ruby 1.9, won't be equal between a Float and a BigDecimal.

gem 'json'
require 'json'
require 'bigdecimal'
JSON.dump(BigDecimal.new("1.01")) #=> "\"0.101E1\"" 
require 'bigdecimal'
require 'yajl'
Yajl::Encoder.encode(BigDecimal.new("1.01")) #=> "\"0.101E1\""
require 'active_support/json'
BigDecimal.new("1.01").as_json #=> "1.01" 
BigDecimal.new("1.01").to_json #=> "\"1.01\""
@ohler55
Owner

Sadly your fix is probably best for compat mode. I'd rather not change the other modes to accommodate failing on the part of other languages and/or parsers.

Thanks for including a path with your recommendation.

@ohler55 ohler55 merged commit 816f316 into ohler55:master

1 check passed

Details default The Travis build passed
@iNecas iNecas added a commit to iNecas/dynflow that referenced this pull request
@iNecas iNecas Ensure deserialization from json keeps numeric values
After Oj 2.10.0, there happens quite often that the float is
deserialized to BigDecimal which is serialized back to string.

    MultiJson.load(MultiJson.dump(
      MultiJson.load(MultiJson.dump(6.903916489999999))))
    # => "6.903916489999999"

Also, other JSON implementations have similar behavior when it takes
BigDecimal. More details here ohler55/oj#59.

As preserving of the float type is not guaranteed, we need to manually
typecast to_f to avoid

   Value (String) '3.00232023232032203232' is not any of: Numeric; NilClass
3ce72b4
@iNecas iNecas referenced this pull request in Dynflow/dynflow
Merged

Ensure deserialization from json keeps numeric values #127

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Showing with 4 additions and 10 deletions.
  1. +2 −8 ext/oj/dump.c
  2. +2 −2 test/tests.rb
View
10 ext/oj/dump.c
@@ -1074,14 +1074,8 @@ dump_data_comp(VALUE obj, Out out) {
} else {
VALUE rstr;
- if (oj_bigdecimal_class == clas) {
- //rstr = rb_funcall(obj, oj_to_s_id, 1, rb_intern("E"));
- rstr = rb_funcall(obj, oj_to_s_id, 0);
- dump_raw(StringValuePtr(rstr), RSTRING_LEN(rstr), out);
- } else {
- rstr = rb_funcall(obj, oj_to_s_id, 0);
- dump_cstr(StringValuePtr(rstr), RSTRING_LEN(rstr), 0, 0, out);
- }
+ rstr = rb_funcall(obj, oj_to_s_id, 0);
+ dump_cstr(StringValuePtr(rstr), RSTRING_LEN(rstr), 0, 0, out);
}
}
View
4 test/tests.rb
@@ -666,10 +666,10 @@ def test_bigdecimal_null
Oj.default_options = {:mode => mode}
end
def test_bigdecimal_compat
- orig = BigDecimal.new('3.14159265358979323846')
+ orig = BigDecimal.new('80.51')
json = Oj.dump(orig, :mode => :compat)
bg = Oj.load(json, :mode => :compat)
- assert_equal(orig, bg)
+ assert_equal(orig.to_s, bg)
end
def test_bigdecimal_object
mode = Oj.default_options[:mode]
Something went wrong with that request. Please try again.