Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with
or
.
Download ZIP
Browse files

added bigdecimal option, worked around active_support bug, and made 2…

….0.0 compatible
  • Loading branch information...
commit 6b2d449900273269d93cee6f8954b91449b3838d 1 parent a1fb5ab
@ohler55 authored
View
22 README.md
@@ -32,23 +32,21 @@ A fast JSON parser and Object marshaller as a Ruby gem.
## <a name="release">Release Notes</a>
-### Release 2.0.7
-
- - Fixed bug where undefined classes specified in a JSON document would freeze Ruby instead of raising an exception when
- the auto_define option was not set. (It seems that Ruby freezes on trying to add variables to nil.)
+### Release 2.0.8
-### Release 2.0.6
+ - Added :bigdecimal_load option that forces all decimals in a JSON string to be read as BigDecimals instead of as
+ Floats. This is useful if precision is important.
- - Worked around an undocumented feature in linux when using make that misreports the stack limits.
+ - Worked around bug in active_support 2.3.x where BigDecimal.as_json() returned self instead of a JSON primitive. Of
+ course that creates a loop and blows the stack. Oj ignores the as_json() for any object that returns itself and
+ instead encodes the object as it sees fit which is usually what is expected.
-### Release 2.0.5
+ - All tests pass with Ruby 2.0.0-p0. Had to modify Exception encoding slightly.
- - DateTimes are now output the same in compat mode for both 1.8.7 and 1.9.3 even though they are implemented differently in each Ruby.
-
- - Objects implemented as data structs can now change the encoding by implemented either to_json(), as_json(), or to_hash().
+### Release 2.0.7
- - Added an option to allow BigDecimals to be dumped as either a string or as a number. There was no agreement on which
- was the best or correct so both are possible with the correct option setting.
+ - Fixed bug where undefined classes specified in a JSON document would freeze Ruby instead of raising an exception when
+ the auto_define option was not set. (It seems that Ruby freezes on trying to add variables to nil.)
## <a name="description">Description</a>
View
3  build_test.sh
@@ -10,7 +10,8 @@ for ruby in \
rbx-1.2.4\
rbx-2.0.0-dev\
ree-1.8.7-2012.02\
- 1.9.3-p362
+ 1.9.3-p385\
+ 2.0.0-p0
do
echo "\n********************************************************************************"
echo "Building $ruby\n"
View
70 ext/oj/dump.c
@@ -1117,6 +1117,8 @@ dump_data_null(VALUE obj, Out out) {
static void
dump_data_comp(VALUE obj, int depth, Out out) {
+ VALUE o2;
+
if (rb_respond_to(obj, oj_to_hash_id)) {
VALUE h = rb_funcall(obj, oj_to_hash_id, 0);
@@ -1124,8 +1126,8 @@ dump_data_comp(VALUE obj, int depth, Out out) {
rb_raise(rb_eTypeError, "%s.to_hash() did not return a Hash.\n", rb_class2name(rb_obj_class(obj)));
}
dump_hash(h, depth, out->opts->mode, out);
- } else if (rb_respond_to(obj, oj_as_json_id)) {
- dump_val(rb_funcall(obj, oj_as_json_id, 0), depth, out);
+ } else if (rb_respond_to(obj, oj_as_json_id) && obj != (o2 = rb_funcall(obj, oj_as_json_id, 0))) {
+ dump_val(o2, depth, out);
} else if (rb_respond_to(obj, oj_to_json_id)) {
VALUE rs = rb_funcall(obj, oj_to_json_id, 0);
const char *s = StringValuePtr(rs);
@@ -1281,6 +1283,11 @@ dump_attr_cb(ID key, VALUE value, Out out) {
size_t size = depth * out->indent + 1;
const char *attr = rb_id2name(key);
+#if HAS_EXCEPTION_MAGIC
+ if (0 == strcmp("bt", attr) || 0 == strcmp("mesg", attr)) {
+ return ST_CONTINUE;
+ }
+#endif
if (out->end - out->cur <= (long)size) {
grow(out, size);
}
@@ -1369,6 +1376,34 @@ dump_obj_attrs(VALUE obj, VALUE clas, slot_t id, int depth, Out out) {
}
#else
size = d2 * out->indent + 1;
+ for (i = cnt; 0 < i; i--, np++) {
+ if (out->end - out->cur <= (long)size) {
+ grow(out, size);
+ }
+ vid = rb_to_id(*np);
+ fill_indent(out, d2);
+ attr = rb_id2name(vid);
+ if ('@' == *attr) {
+ attr++;
+ dump_cstr(attr, strlen(attr), 0, 0, out);
+ } else {
+ char buf[32];
+
+ *buf = '~';
+ strncpy(buf + 1, attr, sizeof(buf) - 2);
+ buf[sizeof(buf) - 1] = '\0';
+ dump_cstr(buf, strlen(attr) + 1, 0, 0, out);
+ }
+ *out->cur++ = ':';
+ dump_val(rb_ivar_get(obj, vid), d2, out);
+ if (out->end - out->cur <= 2) {
+ grow(out, 2);
+ }
+ if (1 < i) {
+ *out->cur++ = ',';
+ }
+ }
+#endif
#if HAS_EXCEPTION_MAGIC
if (Qtrue == rb_obj_is_kind_of(obj, rb_eException)) {
if (',' != *(out->cur - 1)) {
@@ -1397,37 +1432,6 @@ dump_obj_attrs(VALUE obj, VALUE clas, slot_t id, int depth, Out out) {
if (out->end - out->cur <= 2) {
grow(out, 2);
}
- if (0 < cnt) {
- *out->cur++ = ',';
- }
- }
-#endif
- for (i = cnt; 0 < i; i--, np++) {
- if (out->end - out->cur <= (long)size) {
- grow(out, size);
- }
- vid = rb_to_id(*np);
- fill_indent(out, d2);
- attr = rb_id2name(vid);
- if ('@' == *attr) {
- attr++;
- dump_cstr(attr, strlen(attr), 0, 0, out);
- } else {
- char buf[32];
-
- *buf = '~';
- strncpy(buf + 1, attr, sizeof(buf) - 2);
- buf[sizeof(buf) - 1] = '\0';
- dump_cstr(buf, strlen(attr) + 1, 0, 0, out);
- }
- *out->cur++ = ':';
- dump_val(rb_ivar_get(obj, vid), d2, out);
- if (out->end - out->cur <= 2) {
- grow(out, 2);
- }
- if (1 < i) {
- *out->cur++ = ',';
- }
}
#endif
out->depth = depth;
View
6 ext/oj/extconf.rb
@@ -26,8 +26,8 @@
'HAS_NANO_TIME' => ('ruby' == type && ('1' == version[0] && '9' == version[1]) || '2' <= version[0]) ? 1 : 0,
'HAS_RSTRUCT' => ('ruby' == type || 'ree' == type || 'tcs-ruby' == type) ? 1 : 0,
'HAS_IVAR_HELPERS' => ('ruby' == type && !is_windows && (('1' == version[0] && '9' == version[1]) || '2' <= version[0])) ? 1 : 0,
- 'HAS_EXCEPTION_MAGIC' => ('ruby' == type && !is_windows && (('1' == version[0] && '9' == version[1]) || '2' <= version[0])) ? 0 : 1,
- 'HAS_PROC_WITH_BLOCK' => ('ruby' == type && ('1' == version[0] && '9' == version[1]) || '2' <= version[0]) ? 1 : 0,
+ 'HAS_EXCEPTION_MAGIC' => ('ruby' == type && !is_windows && ('1' == version[0] && '9' == version[1])) ? 0 : 1,

Hello @ohler55, why Windows is being excluded in those defines?

@ohler55 Owner
ohler55 added a note

Hello @ohler55

I was wondering why Windows was excluded from HAVE_EXCEPTION_MAGIC usage.

Asking this because the code inside these ifdefs do actually works under Windows without issues.

Was there an specific reason to exclude Windows from getting the exception information triggered by the JSON parsing/dumping?

For a test, I just removed the && !is_windows and compiled again, run tests, all without issues.

Thank you.

@ohler55 Owner
ohler55 added a note
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
+ 'HAS_PROC_WITH_BLOCK' => ('ruby' == type && (('1' == version[0] && '9' == version[1]) || '2' <= version[0])) ? 1 : 0,
'HAS_TOP_LEVEL_ST_H' => ('ree' == type || ('ruby' == type && '1' == version[0] && '8' == version[1])) ? 1 : 0,
'IS_WINDOWS' => is_windows ? 1 : 0,
'SAFE_CACHE' => is_windows ? 0 : 1,
@@ -38,7 +38,7 @@
if 'x86_64-linux' == RUBY_PLATFORM && '1.9.3' == RUBY_VERSION && '2011-10-30' == RUBY_RELEASE_DATE
begin
dflags['NEEDS_STPCPY'] = nil if File.read('/etc/redhat-release').include?('CentOS release 5.4')
- rescue Exception => e
+ rescue Exception
end
else
dflags['NEEDS_STPCPY'] = nil if is_windows
View
19 ext/oj/load.c
@@ -742,10 +742,21 @@ read_num(ParseInfo pi) {
raise_error("number or other value", pi->str, pi->s);
}
pi->s += 8;
- if (neg) {
- return rb_float_new(-INFINITY);
+ if (Yes == pi->options->bigdec_load) {
+ char c = *pi->s;
+ VALUE num;
+
+ *pi->s = '\0';
+ num = rb_funcall(oj_bigdecimal_class, oj_new_id, 1, rb_str_new2(start));
+ *pi->s = c;
+
+ return num;
} else {
- return rb_float_new(INFINITY);
+ if (neg) {
+ return rb_float_new(-INFINITY);
+ } else {
+ return rb_float_new(INFINITY);
+ }
}
}
for (; '0' <= *pi->s && *pi->s <= '9'; pi->s++) {
@@ -800,7 +811,7 @@ read_num(ParseInfo pi) {
return LONG2NUM(n);
}
} else { // decimal
- if (big) {
+ if (big || Yes == pi->options->bigdec_load) {
char c = *pi->s;
VALUE num;
View
8 ext/oj/oj.c
@@ -85,6 +85,7 @@ VALUE oj_slash_string;
static VALUE ascii_only_sym;
static VALUE auto_define_sym;
static VALUE bigdecimal_as_decimal_sym;
+static VALUE bigdecimal_load_sym;
static VALUE circular_sym;
static VALUE compat_sym;
static VALUE create_id_sym;
@@ -131,6 +132,7 @@ struct _Options oj_default_options = {
ObjectMode, // mode
UnixTime, // time_format
Yes, // bigdec_as_num
+ No, // bigdec_load
json_class, // create_id
65536, // max_stack
9, // sec_prec
@@ -162,6 +164,7 @@ oj_get_odd(VALUE clas) {
* - mode: [:object|:strict|:compat|:null] load and dump modes to use for JSON
* - time_format: [:unix|:xmlschema|:ruby] time format when dumping in :compat mode
* - bigdecimal_as_decimal: [true|false|nil] dump BigDecimal as a decimal number or as a String
+ * - bigdecimal_load: [true|false|nil] load decimals as BigDecimal instead of as a Float
* - create_id: [String|nil] create id for json compatible object encoding, default is 'json_create'
* - max_stack: [Fixnum|nil] maximum json size to allocate on the stack, default is 65536
* - second_precision: [Fixnum|nil] number of digits after the decimal when dumping the seconds portion of time
@@ -179,6 +182,7 @@ get_def_opts(VALUE self) {
rb_hash_aset(opts, ascii_only_sym, (Yes == oj_default_options.ascii_only) ? Qtrue : ((No == oj_default_options.ascii_only) ? Qfalse : Qnil));
rb_hash_aset(opts, symbol_keys_sym, (Yes == oj_default_options.sym_key) ? Qtrue : ((No == oj_default_options.sym_key) ? Qfalse : Qnil));
rb_hash_aset(opts, bigdecimal_as_decimal_sym, (Yes == oj_default_options.bigdec_as_num) ? Qtrue : ((No == oj_default_options.bigdec_as_num) ? Qfalse : Qnil));
+ rb_hash_aset(opts, bigdecimal_load_sym, (Yes == oj_default_options.bigdec_load) ? Qtrue : ((No == oj_default_options.bigdec_load) ? Qfalse : Qnil));
switch (oj_default_options.mode) {
case StrictMode: rb_hash_aset(opts, mode_sym, strict_sym); break;
case CompatMode: rb_hash_aset(opts, mode_sym, compat_sym); break;
@@ -207,6 +211,7 @@ get_def_opts(VALUE self) {
* @param [true|false|nil] :symbol_keys convert hash keys to symbols
* @param [true|false|nil] :ascii_only encode all high-bit characters as escaped sequences if true
* @param [true|false|nil] :bigdecimal_as_decimal dump BigDecimal as a decimal number or as a String
+ * @param [true|false|nil] :bigdecimal_load load decimals as a BigDecimal instead of as a Float
* @param [:object|:strict|:compat|:null] load and dump mode to use for JSON
* :strict raises an exception when a non-supported Object is
* encountered. :compat attempts to extract variable values from an
@@ -232,6 +237,7 @@ set_def_opts(VALUE self, VALUE opts) {
{ symbol_keys_sym, &oj_default_options.sym_key },
{ ascii_only_sym, &oj_default_options.ascii_only },
{ bigdecimal_as_decimal_sym, &oj_default_options.bigdec_as_num },
+ { bigdecimal_load_sym, &oj_default_options.bigdec_load },
{ Qnil, 0 }
};
YesNoOpt o;
@@ -337,6 +343,7 @@ parse_options(VALUE ropts, Options copts) {
{ symbol_keys_sym, &copts->sym_key },
{ ascii_only_sym, &copts->ascii_only },
{ bigdecimal_as_decimal_sym, &copts->bigdec_as_num },
+ { bigdecimal_load_sym, &copts->bigdec_load },
{ Qnil, 0 }
};
YesNoOpt o;
@@ -1063,6 +1070,7 @@ void Init_oj() {
ascii_only_sym = ID2SYM(rb_intern("ascii_only")); rb_gc_register_address(&ascii_only_sym);
auto_define_sym = ID2SYM(rb_intern("auto_define")); rb_gc_register_address(&auto_define_sym);
bigdecimal_as_decimal_sym = ID2SYM(rb_intern("bigdecimal_as_decimal"));rb_gc_register_address(&bigdecimal_as_decimal_sym);
+ bigdecimal_load_sym = ID2SYM(rb_intern("bigdecimal_load"));rb_gc_register_address(&bigdecimal_load_sym);
circular_sym = ID2SYM(rb_intern("circular")); rb_gc_register_address(&circular_sym);
compat_sym = ID2SYM(rb_intern("compat")); rb_gc_register_address(&compat_sym);
create_id_sym = ID2SYM(rb_intern("create_id")); rb_gc_register_address(&create_id_sym);
View
1  ext/oj/oj.h
@@ -109,6 +109,7 @@ typedef struct _Options {
char mode; // Mode
char time_format; // TimeFormat
char bigdec_as_num; // YesNo
+ char bigdec_load; // YesNo
const char *create_id; // 0 or string
size_t max_stack; // max size to allocate on the stack
int sec_prec; // second precision when dumping time
View
2  lib/oj/version.rb
@@ -1,5 +1,5 @@
module Oj
# Current version of the module.
- VERSION = '2.0.7'
+ VERSION = '2.0.8'
end
View
2  notes
@@ -3,6 +3,8 @@
^c^d hide subtree
^c^s show subtree
+- active_support issue with BigDecimal.as_json returning self
+- add option to always use bigdecimal for loading
---------------------------
View
10 test/tests.rb
@@ -118,6 +118,7 @@ def test0_get_options
:mode=>:object,
:time_format=>:unix,
:bigdecimal_as_decimal=>true,
+ :bigdecimal_load=>false,
:max_stack=>65536,
:create_id=>'json_class'}, opts)
end
@@ -133,6 +134,7 @@ def test0_set_options
:mode=>:object,
:time_format=>:unix,
:bigdecimal_as_decimal=>true,
+ :bigdecimal_load=>false,
:max_stack=>65536,
:create_id=>'json_class'}
o2 = {
@@ -145,6 +147,7 @@ def test0_set_options
:mode=>:compat,
:time_format=>:ruby,
:bigdecimal_as_decimal=>false,
+ :bigdecimal_load=>true,
:max_stack=>4000,
:create_id=>nil}
o3 = { :indent => 4 }
@@ -743,6 +746,13 @@ def test_bigdecimal_compat
bg = Oj.load(json, :mode => :compat)
assert_equal(orig.to_s, bg)
end
+ def test_bigdecimal_load
+ orig = BigDecimal.new('80.51')
+ json = Oj.dump(orig, :mode => :compat, :bigdecimal_as_decimal => true)
+ bg = Oj.load(json, :mode => :compat, :bigdecimal_load => true)
+ assert_equal(BigDecimal, bg.class)
+ assert_equal(orig, bg)
+ end
def test_bigdecimal_compat_to_json
orig = BigDecimal.new('80.51')
BigDecimal.send(:define_method, :to_json) do
Please sign in to comment.
Something went wrong with that request. Please try again.