Skip to content

Commit

Permalink
Decimal class (#629)
Browse files Browse the repository at this point in the history
* Support decimal_class option

* Allow bigdecimal_load in default on JSON.parse

* Fix invalid encoding detection issue

* Remove string from encoding error

* Set version
  • Loading branch information
ohler55 committed Dec 16, 2020
1 parent 127e337 commit 4a39fab
Show file tree
Hide file tree
Showing 10 changed files with 79 additions and 14 deletions.
8 changes: 8 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,13 @@
# CHANGELOG

## 3.10.17 - 2020-12-15

- The undocumented JSON gem option of `:decimal_class` is now
supported and the default option of `:bigdecimal_load` is also
honored in JSON.parse() and in compat mode.

- Invalid encoding detection bug fixed for rails.

## 3.10.16 - 2020-11-10

- Allow escaping any character in :compat mode to match the json gem behavior.
Expand Down
31 changes: 22 additions & 9 deletions ext/oj/dump.c
Original file line number Diff line number Diff line change
Expand Up @@ -174,15 +174,20 @@ hixss_friendly_size(const uint8_t *str, size_t len) {
return size - len * (size_t)'0' + check;
}

inline static size_t
inline static long
rails_xss_friendly_size(const uint8_t *str, size_t len) {
size_t size = 0;
long size = 0;
size_t i = len;
uint8_t hi = 0;

for (; 0 < i; str++, i--) {
size += rails_xss_friendly_chars[*str];
hi |= *str & 0x80;
}
return size - len * (size_t)'0';
if (0 == hi) {
return size - len * (size_t)'0';
}
return -(size - len * (size_t)'0');
}

inline static size_t
Expand Down Expand Up @@ -249,7 +254,6 @@ dump_hex(uint8_t c, Out out) {

static void
raise_invalid_unicode(const char *str, int len, int pos) {
char buf[len + 1];
char c;
char code[32];
char *cp = code;
Expand All @@ -268,8 +272,7 @@ raise_invalid_unicode(const char *str, int len, int pos) {
cp--;
*cp++ = ']';
*cp = '\0';
strncpy(buf, str, len);
rb_raise(oj_json_generator_error_class, "Invalid Unicode %s at %d in '%s'", code, pos, buf);
rb_raise(oj_json_generator_error_class, "Invalid Unicode %s at %d", code, pos);
}

static const char*
Expand Down Expand Up @@ -767,6 +770,7 @@ oj_dump_cstr(const char *str, size_t cnt, bool is_sym, bool escape1, Out out) {
size_t size;
char *cmap;
const char *orig = str;
bool has_hi = false;

switch (out->opts->escape_mode) {
case NLEsc:
Expand All @@ -785,10 +789,19 @@ oj_dump_cstr(const char *str, size_t cnt, bool is_sym, bool escape1, Out out) {
cmap = hixss_friendly_chars;
size = hixss_friendly_size((uint8_t*)str, cnt);
break;
case RailsXEsc:
case RailsXEsc: {
long sz;

cmap = rails_xss_friendly_chars;
size = rails_xss_friendly_size((uint8_t*)str, cnt);
sz = rails_xss_friendly_size((uint8_t*)str, cnt);
if (sz < 0) {
has_hi = true;
size = (size_t)-sz;
} else {
size = (size_t)sz;
}
break;
}
case RailsEsc:
cmap = rails_friendly_chars;
size = rails_friendly_size((uint8_t*)str, cnt);
Expand All @@ -812,7 +825,7 @@ oj_dump_cstr(const char *str, size_t cnt, bool is_sym, bool escape1, Out out) {
str++;
is_sym = 0; // just to make sure
}
if (cnt == size) {
if (cnt == size && !has_hi) {
if (is_sym) {
*out->cur++ = ':';
}
Expand Down
12 changes: 10 additions & 2 deletions ext/oj/mimic_json.c
Original file line number Diff line number Diff line change
Expand Up @@ -510,8 +510,6 @@ mimic_parse_core(int argc, VALUE *argv, VALUE self, bool bang) {
pi.options.create_ok = No;
pi.options.allow_nan = (bang ? Yes : No);
pi.options.nilnil = No;
//pi.options.bigdec_load = FloatDec;
pi.options.bigdec_load = RubyDec;
pi.options.mode = CompatMode;
pi.max_depth = 100;

Expand Down Expand Up @@ -561,6 +559,16 @@ mimic_parse_core(int argc, VALUE *argv, VALUE self, bool bang) {
pi.options.array_class = v;
}
}
if (Qtrue == rb_funcall(ropts, oj_has_key_id, 1, oj_decimal_class_sym)) {
v = rb_hash_lookup(ropts, oj_decimal_class_sym);
if (rb_cFloat == v) {
pi.options.bigdec_load = FloatDec;
} else if (oj_bigdecimal_class == v) {
pi.options.bigdec_load = BigDec;
} else if (Qnil == v) {
pi.options.bigdec_load = AutoDec;
}
}
v = rb_hash_lookup(ropts, oj_max_nesting_sym);
if (Qtrue == v) {
pi.max_depth = 100;
Expand Down
15 changes: 15 additions & 0 deletions ext/oj/oj.c
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,7 @@ VALUE oj_slash_string;
VALUE oj_allow_nan_sym;
VALUE oj_array_class_sym;
VALUE oj_create_additions_sym;
VALUE oj_decimal_class_sym;
VALUE oj_hash_class_sym;
VALUE oj_indent_sym;
VALUE oj_object_class_sym;
Expand Down Expand Up @@ -581,6 +582,19 @@ oj_parse_options(VALUE ropts, Options copts) {
rb_raise(rb_eArgError, ":bigdecimal_load must be :bigdecimal, :float, or :auto.");
}
}
if (Qtrue == rb_funcall(ropts, oj_has_key_id, 1, oj_decimal_class_sym)) {
v = rb_hash_lookup(ropts, oj_decimal_class_sym);
if (rb_cFloat == v) {
copts->bigdec_load = FloatDec;
} else if (oj_bigdecimal_class == v) {
copts->bigdec_load = BigDec;
} else if (Qnil == v) {
copts->bigdec_load = AutoDec;
} else {
rb_raise(rb_eArgError, ":decimal_class must be BigDecimal, Float, or nil.");
}
}

if (Qtrue == rb_funcall(ropts, oj_has_key_id, 1, create_id_sym)) {
v = rb_hash_lookup(ropts, create_id_sym);
if (Qnil == v) {
Expand Down Expand Up @@ -1674,6 +1688,7 @@ Init_oj() {
oj_array_nl_sym = ID2SYM(rb_intern("array_nl")); rb_gc_register_address(&oj_array_nl_sym);
oj_ascii_only_sym = ID2SYM(rb_intern("ascii_only")); rb_gc_register_address(&oj_ascii_only_sym);
oj_create_additions_sym = ID2SYM(rb_intern("create_additions"));rb_gc_register_address(&oj_create_additions_sym);
oj_decimal_class_sym = ID2SYM(rb_intern("decimal_class")); rb_gc_register_address(&oj_decimal_class_sym);
oj_hash_class_sym = ID2SYM(rb_intern("hash_class")); rb_gc_register_address(&oj_hash_class_sym);
oj_indent_sym = ID2SYM(rb_intern("indent")); rb_gc_register_address(&oj_indent_sym);
oj_max_nesting_sym = ID2SYM(rb_intern("max_nesting")); rb_gc_register_address(&oj_max_nesting_sym);
Expand Down
1 change: 1 addition & 0 deletions ext/oj/oj.h
Original file line number Diff line number Diff line change
Expand Up @@ -310,6 +310,7 @@ extern VALUE oj_array_class_sym;
extern VALUE oj_array_nl_sym;
extern VALUE oj_ascii_only_sym;
extern VALUE oj_create_additions_sym;
extern VALUE oj_decimal_class_sym;
extern VALUE oj_hash_class_sym;
extern VALUE oj_indent_sym;
extern VALUE oj_max_nesting_sym;
Expand Down
2 changes: 1 addition & 1 deletion lib/oj/version.rb
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@

module Oj
# Current version of the module.
VERSION = '3.10.16'
VERSION = '3.10.17'
end
2 changes: 1 addition & 1 deletion pages/Modes.md
Original file line number Diff line number Diff line change
Expand Up @@ -94,7 +94,7 @@ information.
| :array_nl | String | | | | | | x | |
| :ascii_only | Boolean | x | x | 2 | 2 | x | x | |
| :auto_define | Boolean | | | | | x | x | |
| :bigdecimal_as_decimal | Boolean | | | | 3 | x | x | |
| :bigdecimal_as_decimal | Boolean | | | x | 3 | x | x | |
| :bigdecimal_load | Boolean | | | | | | x | |
| :circular | Boolean | x | x | x | x | x | x | |
| :class_cache | Boolean | | | | | x | x | |
Expand Down
4 changes: 4 additions & 0 deletions pages/Options.md
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,10 @@ Determines how to load decimals.

- `:auto` the most precise for the number of digits is used.

This can also be set with `:decimal_class` when used as a load or
parse option to match the JSON gem. In that case either `Float`,
`BigDecimal`, or `nil` can be provided.

### :circular [Boolean]

Detect circular references while dumping. In :compat mode raise a
Expand Down
9 changes: 8 additions & 1 deletion test/test_compat.rb
Original file line number Diff line number Diff line change
Expand Up @@ -277,12 +277,19 @@ def test_bignum_compat
# BigDecimal
def test_bigdecimal
# BigDecimals are dumped as strings and can not be restored to the
# original value.
# original value without using an undocumented feature of the JSON gem.
json = Oj.dump(BigDecimal('3.14159265358979323846'))
# 2.4.0 changes the exponent to lowercase
assert_equal('"0.314159265358979323846e1"', json.downcase)
end

def test_bigdecimal_load
big = BigDecimal('3.14159265358979323846')
# :decimal_class is the undocumented feature.
json = Oj.load('3.14159265358979323846', mode: :compat, decimal_class: BigDecimal)
assert_equal(big, json)
end

def test_infinity
assert_raises(Oj::ParseError) { Oj.load('Infinity', :mode => :strict) }
x = Oj.load('Infinity', :mode => :compat)
Expand Down
9 changes: 9 additions & 0 deletions test/test_rails.rb
Original file line number Diff line number Diff line change
Expand Up @@ -23,4 +23,13 @@ def test_bigdecimal_dump
assert_equal('0.123e3', json.downcase)
end

def test_invalid_encoding
assert_raises(EncodingError) {
Oj.dump("\"\xf3j", mode: :rails)
}
assert_raises(EncodingError) {
Oj.dump("\xf3j", mode: :rails)
}
end

end

0 comments on commit 4a39fab

Please sign in to comment.