oj 3.8.1 → 3.10.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (50) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +2 -2
  3. data/ext/oj/compat.c +5 -5
  4. data/ext/oj/custom.c +65 -38
  5. data/ext/oj/dump.c +9 -12
  6. data/ext/oj/dump_compat.c +8 -10
  7. data/ext/oj/dump_object.c +18 -11
  8. data/ext/oj/dump_strict.c +6 -5
  9. data/ext/oj/extconf.rb +6 -0
  10. data/ext/oj/mimic_json.c +15 -3
  11. data/ext/oj/object.c +8 -5
  12. data/ext/oj/oj.c +50 -31
  13. data/ext/oj/oj.h +6 -4
  14. data/ext/oj/parse.c +16 -3
  15. data/ext/oj/parse.h +1 -0
  16. data/ext/oj/rails.c +40 -4
  17. data/ext/oj/resolve.c +3 -3
  18. data/ext/oj/sparse.c +5 -0
  19. data/ext/oj/util.c +5 -5
  20. data/ext/oj/val_stack.c +9 -9
  21. data/ext/oj/val_stack.h +9 -9
  22. data/ext/oj/wab.c +9 -9
  23. data/lib/oj/version.rb +1 -1
  24. data/pages/Options.md +4 -0
  25. data/pages/Rails.md +21 -21
  26. data/test/activesupport5/abstract_unit.rb +45 -0
  27. data/test/activesupport5/decoding_test.rb +68 -60
  28. data/test/activesupport5/encoding_test.rb +111 -96
  29. data/test/activesupport5/encoding_test_cases.rb +33 -25
  30. data/test/activesupport5/test_helper.rb +43 -21
  31. data/test/activesupport5/time_zone_test_helpers.rb +18 -3
  32. data/test/activesupport6/abstract_unit.rb +44 -0
  33. data/test/activesupport6/decoding_test.rb +133 -0
  34. data/test/activesupport6/encoding_test.rb +507 -0
  35. data/test/activesupport6/encoding_test_cases.rb +98 -0
  36. data/test/activesupport6/test_common.rb +17 -0
  37. data/test/activesupport6/test_helper.rb +163 -0
  38. data/test/activesupport6/time_zone_test_helpers.rb +39 -0
  39. data/test/bar.rb +8 -11
  40. data/test/baz.rb +16 -0
  41. data/test/foo.rb +42 -157
  42. data/test/test_compat.rb +0 -7
  43. data/test/test_custom.rb +25 -6
  44. data/test/test_integer_range.rb +1 -2
  45. data/test/test_object.rb +4 -3
  46. data/test/test_rails.rb +26 -0
  47. data/test/test_strict.rb +24 -1
  48. data/test/test_various.rb +41 -62
  49. data/test/tests.rb +1 -0
  50. metadata +22 -2
@@ -28,6 +28,7 @@ have_func('rb_ivar_count')
28
28
  have_func('rb_ivar_foreach')
29
29
  have_func('stpcpy')
30
30
  have_func('rb_data_object_wrap')
31
+ have_func('pthread_mutex_init')
31
32
 
32
33
  dflags['OJ_DEBUG'] = true unless ENV['OJ_DEBUG'].nil?
33
34
 
@@ -41,6 +42,11 @@ end
41
42
 
42
43
  $CPPFLAGS += ' -Wall'
43
44
  #puts "*** $CPPFLAGS: #{$CPPFLAGS}"
45
+ # Adding the __attribute__ flag only works with gcc compilers and even then it
46
+ # does not work to check args with varargs so just remove the check.
47
+ CONFIG['warnflags'].slice!(/ -Wsuggest-attribute=format/)
48
+ CONFIG['warnflags'].slice!(/ -Wdeclaration-after-statement/)
49
+ CONFIG['warnflags'].slice!(/ -Wmissing-noreturn/)
44
50
 
45
51
  create_makefile(File.join(extension_name, extension_name))
46
52
 
@@ -199,6 +199,7 @@ mimic_dump(int argc, VALUE *argv, VALUE self) {
199
199
  struct _out out;
200
200
  struct _options copts = oj_default_options;
201
201
  VALUE rstr;
202
+ VALUE active_hack[1];
202
203
 
203
204
  copts.str_rx.head = NULL;
204
205
  copts.str_rx.tail = NULL;
@@ -216,6 +217,7 @@ mimic_dump(int argc, VALUE *argv, VALUE self) {
216
217
  */
217
218
  copts.dump_opts.max_depth = MAX_DEPTH; // when using dump there is no limit
218
219
  out.omit_nil = copts.dump_opts.omit_nil;
220
+
219
221
  if (2 <= argc) {
220
222
  int limit;
221
223
 
@@ -230,7 +232,15 @@ mimic_dump(int argc, VALUE *argv, VALUE self) {
230
232
  copts.dump_opts.max_depth = limit;
231
233
  }
232
234
  }
233
- oj_dump_obj_to_json(*argv, &copts, &out);
235
+ // ActiveSupport in active_support/core_ext/object/json.rb check the
236
+ // optional argument type to to_json and it the argument is a
237
+ // ::JSON::State it calls the JSON gem code otherwise it calls the active
238
+ // support encoder code. To make sure the desired branch is called a
239
+ // default ::JSON::State argument is passed in. Basically a hack to get
240
+ // around the active support hack so two wrongs make a right this time.
241
+ active_hack[0] = rb_funcall(state_class, oj_new_id, 0);
242
+ oj_dump_obj_to_json_using_params(*argv, &copts, &out, 1, active_hack);
243
+
234
244
  if (0 == out.buf) {
235
245
  rb_raise(rb_eNoMemError, "Not enough memory.");
236
246
  }
@@ -690,8 +700,10 @@ static struct _options mimic_object_to_json_options = {
690
700
  No, // allow_nan
691
701
  No, // trace
692
702
  No, // safe
693
- 0, // integer_range_min
694
- 0, // integer_range_max
703
+ false, // sec_prec_set
704
+ No, // ignore_under
705
+ 0, // int_range_min
706
+ 0, // int_range_max
695
707
  oj_json_class,// create_id
696
708
  10, // create_id_len
697
709
  3, // sec_prec
@@ -276,7 +276,10 @@ hat_num(ParseInfo pi, Val parent, Val kval, NumInfo ni) {
276
276
  if (2 == kval->klen) {
277
277
  switch (kval->key[1]) {
278
278
  case 't': // time as a float
279
- {
279
+ if (0 == ni->div || 9 < ni->di) {
280
+ rb_raise(rb_eArgError, "Invalid time decimal representation.");
281
+ //parent->val = rb_time_nano_new(0, 0);
282
+ } else {
280
283
  int64_t nsec = ni->num * 1000000000LL / ni->div;
281
284
 
282
285
  if (ni->neg) {
@@ -407,7 +410,7 @@ oj_set_obj_ivar(Val parent, Val kval, VALUE value) {
407
410
  ID var_id;
408
411
  ID *slot;
409
412
 
410
- #if HAVE_LIBPTHREAD
413
+ #ifdef HAVE_PTHREAD_MUTEX_INIT
411
414
  pthread_mutex_lock(&oj_cache_mutex);
412
415
  #else
413
416
  rb_mutex_lock(oj_cache_mutex);
@@ -441,7 +444,7 @@ oj_set_obj_ivar(Val parent, Val kval, VALUE value) {
441
444
  }
442
445
  *slot = var_id;
443
446
  }
444
- #if HAVE_LIBPTHREAD
447
+ #ifdef HAVE_PTHREAD_MUTEX_INIT
445
448
  pthread_mutex_unlock(&oj_cache_mutex);
446
449
  #else
447
450
  rb_mutex_unlock(oj_cache_mutex);
@@ -665,7 +668,7 @@ end_hash(ParseInfo pi) {
665
668
  static void
666
669
  array_append_cstr(ParseInfo pi, const char *str, size_t len, const char *orig) {
667
670
  volatile VALUE rval = Qnil;
668
-
671
+
669
672
  if (3 <= len && 0 != pi->circ_array) {
670
673
  if ('i' == str[1]) {
671
674
  long i = read_long(str + 2, len - 2);
@@ -694,7 +697,7 @@ array_append_cstr(ParseInfo pi, const char *str, size_t len, const char *orig) {
694
697
  static void
695
698
  array_append_num(ParseInfo pi, NumInfo ni) {
696
699
  volatile VALUE rval = oj_num_as_value(ni);
697
-
700
+
698
701
  rb_ary_push(stack_peek(&pi->stack)->val, rval);
699
702
  if (Yes == pi->options.trace) {
700
703
  oj_trace_parse_call("append_number", pi, __FILE__, __LINE__, rval);
@@ -119,6 +119,7 @@ static VALUE float_prec_sym;
119
119
  static VALUE float_sym;
120
120
  static VALUE huge_sym;
121
121
  static VALUE ignore_sym;
122
+ static VALUE ignore_under_sym;
122
123
  static VALUE json_sym;
123
124
  static VALUE match_string_sym;
124
125
  static VALUE mode_sym;
@@ -149,7 +150,7 @@ static VALUE xss_safe_sym;
149
150
 
150
151
  rb_encoding *oj_utf8_encoding = 0;
151
152
 
152
- #if HAVE_LIBPTHREAD
153
+ #ifdef HAVE_PTHREAD_MUTEX_INIT
153
154
  pthread_mutex_t oj_cache_mutex;
154
155
  #else
155
156
  VALUE oj_cache_mutex = Qnil;
@@ -181,8 +182,10 @@ struct _options oj_default_options = {
181
182
  Yes, // allow_nan
182
183
  No, // trace
183
184
  No, // safe
184
- 0, // integer_range_min
185
- 0, // integer_range_max
185
+ false, // sec_prec_set
186
+ No, // ignore_under
187
+ 0, // int_range_min
188
+ 0, // int_range_max
186
189
  oj_json_class, // create_id
187
190
  10, // create_id_len
188
191
  9, // sec_prec
@@ -251,6 +254,7 @@ struct _options oj_default_options = {
251
254
  * - *:array_class* [_Class_|_nil_] Class to use instead of Array on load
252
255
  * - *:omit_nil* [_true_|_false_] if true Hash and Object attributes with nil values are omitted
253
256
  * - *:ignore* [_nil_|Array] either nil or an Array of classes to ignore when dumping
257
+ * - *:ignore_under* [Boolean] if true then attributes that start with _ are ignored when dumping in object or custom mode.
254
258
  * - *:integer_range* [_Range_] Dump integers outside range as strings.
255
259
  * - *:trace* [_true,_|_false_] Trace all load and dump calls, default is false (trace is off)
256
260
  * - *:safe* [_true,_|_false_] Safe mimic breaks JSON mimic to be safer, default is false (safe is off)
@@ -286,6 +290,7 @@ get_def_opts(VALUE self) {
286
290
  rb_hash_aset(opts, oj_trace_sym, (Yes == oj_default_options.trace) ? Qtrue : ((No == oj_default_options.trace) ? Qfalse : Qnil));
287
291
  rb_hash_aset(opts, oj_safe_sym, (Yes == oj_default_options.safe) ? Qtrue : ((No == oj_default_options.safe) ? Qfalse : Qnil));
288
292
  rb_hash_aset(opts, float_prec_sym, INT2FIX(oj_default_options.float_prec));
293
+ rb_hash_aset(opts, ignore_under_sym, (Yes == oj_default_options.ignore_under) ? Qtrue : ((No == oj_default_options.ignore_under) ? Qfalse : Qnil));
289
294
  switch (oj_default_options.mode) {
290
295
  case StrictMode: rb_hash_aset(opts, mode_sym, strict_sym); break;
291
296
  case CompatMode: rb_hash_aset(opts, mode_sym, compat_sym); break;
@@ -297,16 +302,16 @@ get_def_opts(VALUE self) {
297
302
  default: rb_hash_aset(opts, mode_sym, object_sym); break;
298
303
  }
299
304
 
300
- if (oj_default_options.integer_range_max != 0 || oj_default_options.integer_range_min != 0) {
301
- VALUE range = rb_obj_alloc(rb_cRange);
302
- VALUE min = LONG2FIX(oj_default_options.integer_range_min);
303
- VALUE max = LONG2FIX(oj_default_options.integer_range_max);
304
- rb_ivar_set(range, oj_begin_id, min);
305
- rb_ivar_set(range, oj_end_id, max);
306
- rb_hash_aset(opts, integer_range_sym, range);
307
- }
308
- else {
309
- rb_hash_aset(opts, integer_range_sym, Qnil);
305
+ if (oj_default_options.int_range_max != 0 || oj_default_options.int_range_min != 0) {
306
+ VALUE range = rb_obj_alloc(rb_cRange);
307
+ VALUE min = LONG2FIX(oj_default_options.int_range_min);
308
+ VALUE max = LONG2FIX(oj_default_options.int_range_max);
309
+
310
+ rb_ivar_set(range, oj_begin_id, min);
311
+ rb_ivar_set(range, oj_end_id, max);
312
+ rb_hash_aset(opts, integer_range_sym, range);
313
+ } else {
314
+ rb_hash_aset(opts, integer_range_sym, Qnil);
310
315
  }
311
316
  switch (oj_default_options.escape_mode) {
312
317
  case NLEsc: rb_hash_aset(opts, escape_mode_sym, newline_sym); break;
@@ -329,7 +334,7 @@ get_def_opts(VALUE self) {
329
334
  case AutoDec:
330
335
  default: rb_hash_aset(opts, bigdecimal_load_sym, auto_sym); break;
331
336
  }
332
- rb_hash_aset(opts, create_id_sym, (0 == oj_default_options.create_id) ? Qnil : rb_str_new2(oj_default_options.create_id));
337
+ rb_hash_aset(opts, create_id_sym, (NULL == oj_default_options.create_id) ? Qnil : rb_str_new2(oj_default_options.create_id));
333
338
  rb_hash_aset(opts, oj_space_sym, (0 == oj_default_options.dump_opts.after_size) ? Qnil : rb_str_new2(oj_default_options.dump_opts.after_sep));
334
339
  rb_hash_aset(opts, oj_space_before_sym, (0 == oj_default_options.dump_opts.before_size) ? Qnil : rb_str_new2(oj_default_options.dump_opts.before_sep));
335
340
  rb_hash_aset(opts, oj_object_nl_sym, (0 == oj_default_options.dump_opts.hash_size) ? Qnil : rb_str_new2(oj_default_options.dump_opts.hash_nl));
@@ -398,6 +403,7 @@ get_def_opts(VALUE self) {
398
403
  * - *:array_class* [_Class_|_nil_] Class to use instead of Array on load.
399
404
  * - *:omit_nil* [_true_|_false_] if true Hash and Object attributes with nil values are omitted.
400
405
  * - *:ignore* [_nil_|Array] either nil or an Array of classes to ignore when dumping
406
+ * - *:ignore_under* [_Boolean_] if true then attributes that start with _ are ignored when dumping in object or custom mode.
401
407
  * - *:integer_range* [_Range_] Dump integers outside range as strings.
402
408
  * - *:trace* [_Boolean_] turn trace on or off.
403
409
  * - *:safe* [_Boolean_] turn safe mimic on or off.
@@ -431,6 +437,7 @@ oj_parse_options(VALUE ropts, Options copts) {
431
437
  { oj_allow_nan_sym, &copts->allow_nan },
432
438
  { oj_trace_sym, &copts->trace },
433
439
  { oj_safe_sym, &copts->safe },
440
+ { ignore_under_sym, &copts->ignore_under },
434
441
  { oj_create_additions_sym, &copts->create_ok },
435
442
  { Qnil, 0 }
436
443
  };
@@ -506,8 +513,12 @@ oj_parse_options(VALUE ropts, Options copts) {
506
513
  n = NUM2INT(v);
507
514
  if (0 > n) {
508
515
  n = 0;
516
+ copts->sec_prec_set = false;
509
517
  } else if (9 < n) {
510
518
  n = 9;
519
+ copts->sec_prec_set = true;
520
+ } else {
521
+ copts->sec_prec_set = true;
511
522
  }
512
523
  copts->sec_prec = n;
513
524
  }
@@ -738,24 +749,26 @@ oj_parse_options(VALUE ropts, Options copts) {
738
749
  }
739
750
  }
740
751
  if (Qnil != (v = rb_hash_lookup(ropts, integer_range_sym))) {
741
- if (TYPE(v) == T_STRUCT && rb_obj_class(v) == rb_cRange) {
742
- VALUE min = rb_funcall(v, oj_begin_id, 0);
743
- VALUE max = rb_funcall(v, oj_end_id, 0);
744
-
745
- if (TYPE(min) != T_FIXNUM || TYPE(max) != T_FIXNUM) {
746
- rb_raise(rb_eArgError, ":integer_range range bounds is not Fixnum.");
747
- }
748
-
749
- copts->integer_range_min = FIX2LONG(min);
750
- copts->integer_range_max = FIX2LONG(max);
751
- } else if (Qfalse != v) {
752
- rb_raise(rb_eArgError, ":integer_range must be a range of Fixnum.");
753
- }
752
+ if (TYPE(v) == T_STRUCT && rb_obj_class(v) == rb_cRange) {
753
+ VALUE min = rb_funcall(v, oj_begin_id, 0);
754
+ VALUE max = rb_funcall(v, oj_end_id, 0);
755
+
756
+ if (TYPE(min) != T_FIXNUM || TYPE(max) != T_FIXNUM) {
757
+ rb_raise(rb_eArgError, ":integer_range range bounds is not Fixnum.");
758
+ }
759
+
760
+ copts->int_range_min = FIX2LONG(min);
761
+ copts->int_range_max = FIX2LONG(max);
762
+ } else if (Qfalse != v) {
763
+ rb_raise(rb_eArgError, ":integer_range must be a range of Fixnum.");
764
+ }
754
765
  }
755
766
  }
756
767
 
757
768
  static int
758
- match_string_cb(VALUE key, VALUE value, RxClass rc) {
769
+ match_string_cb(VALUE key, VALUE value, VALUE rx) {
770
+ RxClass rc = (RxClass)rx;
771
+
759
772
  if (T_CLASS != rb_type(value)) {
760
773
  rb_raise(rb_eArgError, "for :match_string, the hash values must be a Class.");
761
774
  }
@@ -954,11 +967,13 @@ load_file(int argc, VALUE *argv, VALUE self) {
954
967
  }
955
968
  switch (mode) {
956
969
  case StrictMode:
970
+ case NullMode:
957
971
  oj_set_strict_callbacks(&pi);
958
972
  return oj_pi_sparse(argc, argv, &pi, fd);
959
- case NullMode:
960
- case CompatMode:
961
973
  case CustomMode:
974
+ oj_set_custom_callbacks(&pi);
975
+ return oj_pi_sparse(argc, argv, &pi, fd);
976
+ case CompatMode:
962
977
  case RailsMode:
963
978
  oj_set_compat_callbacks(&pi);
964
979
  return oj_pi_sparse(argc, argv, &pi, fd);
@@ -1052,6 +1067,9 @@ dump(int argc, VALUE *argv, VALUE self) {
1052
1067
  if (2 == argc) {
1053
1068
  oj_parse_options(argv[1], &copts);
1054
1069
  }
1070
+ if (CompatMode == copts.mode && copts.escape_mode != ASCIIEsc) {
1071
+ copts.escape_mode = JSONEsc;
1072
+ }
1055
1073
  out.buf = buf;
1056
1074
  out.end = buf + sizeof(buf) - 10;
1057
1075
  out.allocated = false;
@@ -1640,6 +1658,7 @@ Init_oj() {
1640
1658
  float_sym = ID2SYM(rb_intern("float")); rb_gc_register_address(&float_sym);
1641
1659
  huge_sym = ID2SYM(rb_intern("huge")); rb_gc_register_address(&huge_sym);
1642
1660
  ignore_sym = ID2SYM(rb_intern("ignore")); rb_gc_register_address(&ignore_sym);
1661
+ ignore_under_sym = ID2SYM(rb_intern("ignore_under")); rb_gc_register_address(&ignore_under_sym);
1643
1662
  json_sym = ID2SYM(rb_intern("json")); rb_gc_register_address(&json_sym);
1644
1663
  match_string_sym = ID2SYM(rb_intern("match_string")); rb_gc_register_address(&match_string_sym);
1645
1664
  mode_sym = ID2SYM(rb_intern("mode")); rb_gc_register_address(&mode_sym);
@@ -1692,7 +1711,7 @@ Init_oj() {
1692
1711
  oj_odd_init();
1693
1712
  oj_mimic_rails_init();
1694
1713
 
1695
- #if HAVE_LIBPTHREAD
1714
+ #ifdef HAVE_PTHREAD_MUTEX_INIT
1696
1715
  if (0 != (err = pthread_mutex_init(&oj_cache_mutex, 0))) {
1697
1716
  rb_raise(rb_eException, "failed to initialize a mutex. %s", strerror(err));
1698
1717
  }
@@ -21,7 +21,7 @@ extern "C" {
21
21
  #include <stdint.h>
22
22
  #include <stdbool.h>
23
23
 
24
- #if HAVE_LIBPTHREAD
24
+ #ifdef HAVE_PTHREAD_MUTEX_INIT
25
25
  #include <pthread.h>
26
26
  #endif
27
27
  #include "cache8.h"
@@ -149,8 +149,10 @@ typedef struct _options {
149
149
  char allow_nan; // YEsyNo for parsing only
150
150
  char trace; // YesNo
151
151
  char safe; // YesNo
152
- int64_t integer_range_min; // dump numbers outside range as string
153
- int64_t integer_range_max;
152
+ char sec_prec_set; // boolean (0 or 1)
153
+ char ignore_under; // YesNo - ignore attrs starting with _ if true in object and custom modes
154
+ int64_t int_range_min; // dump numbers below as string
155
+ int64_t int_range_max; // dump numbers above as string
154
156
  const char *create_id; // 0 or string
155
157
  size_t create_id_len; // length of create_id
156
158
  int sec_prec; // second precision when dumping time
@@ -370,7 +372,7 @@ extern bool oj_use_hash_alt;
370
372
  extern bool oj_use_array_alt;
371
373
  extern bool string_writer_optimized;
372
374
 
373
- #if HAVE_LIBPTHREAD
375
+ #ifdef HAVE_PTHREAD_MUTEX_INIT
374
376
  extern pthread_mutex_t oj_cache_mutex;
375
377
  #else
376
378
  extern VALUE oj_cache_mutex;
@@ -400,6 +400,10 @@ read_num(ParseInfo pi) {
400
400
  pi->cur++;
401
401
  ni.neg = 1;
402
402
  } else if ('+' == *pi->cur) {
403
+ if (StrictMode == pi->options.mode) {
404
+ oj_set_error_at(pi, oj_parse_error_class, __FILE__, __LINE__, "not a number or other value");
405
+ return;
406
+ }
403
407
  pi->cur++;
404
408
  }
405
409
  if ('I' == *pi->cur) {
@@ -446,8 +450,13 @@ read_num(ParseInfo pi) {
446
450
  if ('.' == *pi->cur) {
447
451
  pi->cur++;
448
452
  // A trailing . is not a valid decimal but if encountered allow it
449
- // except when mimicing the JSON gem.
450
- if (CompatMode == pi->options.mode) {
453
+ // except when mimicing the JSON gem or in strict mode.
454
+ if (StrictMode == pi->options.mode || CompatMode == pi->options.mode) {
455
+ int pos = (int)(pi->cur - ni.str);
456
+ if (1 == pos || (2 == pos && ni.neg)) {
457
+ oj_set_error_at(pi, oj_parse_error_class, __FILE__, __LINE__, "not a number");
458
+ return;
459
+ }
451
460
  if (*pi->cur < '0' || '9' < *pi->cur) {
452
461
  oj_set_error_at(pi, oj_parse_error_class, __FILE__, __LINE__, "not a number");
453
462
  return;
@@ -731,7 +740,7 @@ oj_parse2(ParseInfo pi) {
731
740
  }
732
741
 
733
742
  static VALUE
734
- rescue_big_decimal(VALUE str) {
743
+ rescue_big_decimal(VALUE str, VALUE ignore) {
735
744
  rb_raise(oj_parse_error_class, "Invalid value for BigDecimal()");
736
745
  return Qnil;
737
746
  }
@@ -1049,6 +1058,10 @@ CLEANUP:
1049
1058
  msg = rb_str_append(msg, oj_encode(rb_str_new2(pi->json)));
1050
1059
  }
1051
1060
  args[0] = msg;
1061
+ if (pi->err.clas == oj_parse_error_class) {
1062
+ // The error was an Oj::ParseError so change to a JSON::ParserError.
1063
+ pi->err.clas = oj_json_parser_error_class;
1064
+ }
1052
1065
  rb_exc_raise(rb_class_new_instance(1, args, pi->err.clas));
1053
1066
  } else {
1054
1067
  oj_err_raise(&pi->err);
@@ -80,6 +80,7 @@ extern VALUE oj_num_as_value(NumInfo ni);
80
80
  extern void oj_set_strict_callbacks(ParseInfo pi);
81
81
  extern void oj_set_object_callbacks(ParseInfo pi);
82
82
  extern void oj_set_compat_callbacks(ParseInfo pi);
83
+ extern void oj_set_custom_callbacks(ParseInfo pi);
83
84
  extern void oj_set_wab_callbacks(ParseInfo pi);
84
85
 
85
86
  extern void oj_sparse2(ParseInfo pi);
@@ -87,7 +87,8 @@ copy_opts(ROptTable src, ROptTable dest) {
87
87
  }
88
88
 
89
89
  static int
90
- dump_attr_cb(ID key, VALUE value, Out out) {
90
+ dump_attr_cb(ID key, VALUE value, VALUE ov) {
91
+ Out out = (Out)ov;
91
92
  int depth = out->depth;
92
93
  size_t size = depth * out->indent + 1;
93
94
  const char *attr = rb_id2name(key);
@@ -214,6 +215,8 @@ dump_bigdecimal(VALUE obj, int depth, Out out, bool as_ok) {
214
215
 
215
216
  if ('I' == *str || 'N' == *str || ('-' == *str && 'I' == str[1])) {
216
217
  oj_dump_nil(Qnil, depth, out, false);
218
+ } else if (out->opts->int_range_max != 0 || out->opts->int_range_min != 0) {
219
+ oj_dump_cstr(str, (int)RSTRING_LEN(rstr), 0, 0, out);
217
220
  } else if (Yes == out->opts->bigdec_as_num) {
218
221
  oj_dump_raw(str, (int)RSTRING_LEN(rstr), out);
219
222
  } else {
@@ -1009,9 +1012,11 @@ rails_encode(int argc, VALUE *argv, VALUE self) {
1009
1012
  }
1010
1013
  }
1011
1014
 
1015
+ // TBD provide a get function as well
1012
1016
  static VALUE
1013
1017
  rails_use_standard_json_time_format(VALUE self, VALUE state) {
1014
1018
  if (Qtrue == state || Qfalse == state) {
1019
+ // no change needed
1015
1020
  } else if (Qnil == state) {
1016
1021
  state = Qfalse;
1017
1022
  } else {
@@ -1023,6 +1028,11 @@ rails_use_standard_json_time_format(VALUE self, VALUE state) {
1023
1028
  return state;
1024
1029
  }
1025
1030
 
1031
+ static VALUE
1032
+ rails_use_standard_json_time_format_get(VALUE self) {
1033
+ return xml_time ? Qtrue : Qfalse;
1034
+ }
1035
+
1026
1036
  static VALUE
1027
1037
  rails_escape_html_entities_in_json(VALUE self, VALUE state) {
1028
1038
  rb_iv_set(self, "@escape_html_entities_in_json", state);
@@ -1031,10 +1041,16 @@ rails_escape_html_entities_in_json(VALUE self, VALUE state) {
1031
1041
  return state;
1032
1042
  }
1033
1043
 
1044
+ static VALUE
1045
+ rails_escape_html_entities_in_json_get(VALUE self) {
1046
+ return escape_html ? Qtrue : Qfalse;
1047
+ }
1048
+
1034
1049
  static VALUE
1035
1050
  rails_time_precision(VALUE self, VALUE prec) {
1036
1051
  rb_iv_set(self, "@time_precision", prec);
1037
1052
  oj_default_options.sec_prec = NUM2INT(prec);
1053
+ oj_default_options.sec_prec_set = true;
1038
1054
 
1039
1055
  return prec;
1040
1056
  }
@@ -1053,7 +1069,12 @@ rails_set_encoder(VALUE self) {
1053
1069
  VALUE encoding;
1054
1070
  VALUE pv;
1055
1071
  VALUE verbose;
1072
+ VALUE enc = resolve_classpath("ActiveSupport::JSON::Encoding");
1056
1073
 
1074
+ if (Qnil != enc) {
1075
+ escape_html = Qtrue == rb_iv_get(self, "@escape_html_entities_in_json");
1076
+ xml_time = Qtrue == rb_iv_get(enc, "@use_standard_json_time_format");
1077
+ }
1057
1078
  if (rb_const_defined_at(rb_cObject, rb_intern("ActiveSupport"))) {
1058
1079
  active = rb_const_get_at(rb_cObject, rb_intern("ActiveSupport"));
1059
1080
  } else {
@@ -1070,12 +1091,19 @@ rails_set_encoder(VALUE self) {
1070
1091
  rb_gv_set("$VERBOSE", Qfalse);
1071
1092
  rb_undef_method(encoding, "use_standard_json_time_format=");
1072
1093
  rb_define_module_function(encoding, "use_standard_json_time_format=", rails_use_standard_json_time_format, 1);
1094
+ rb_undef_method(encoding, "use_standard_json_time_format");
1095
+ rb_define_module_function(encoding, "use_standard_json_time_format", rails_use_standard_json_time_format_get, 0);
1073
1096
 
1097
+ pv = rb_iv_get(encoding, "@escape_html_entities_in_json");
1098
+ escape_html = Qtrue == pv;
1074
1099
  rb_undef_method(encoding, "escape_html_entities_in_json=");
1075
1100
  rb_define_module_function(encoding, "escape_html_entities_in_json=", rails_escape_html_entities_in_json, 1);
1101
+ rb_undef_method(encoding, "escape_html_entities_in_json");
1102
+ rb_define_module_function(encoding, "escape_html_entities_in_json", rails_escape_html_entities_in_json_get, 0);
1076
1103
 
1077
1104
  pv = rb_iv_get(encoding, "@time_precision");
1078
1105
  oj_default_options.sec_prec = NUM2INT(pv);
1106
+ oj_default_options.sec_prec_set = true;
1079
1107
  rb_undef_method(encoding, "time_precision=");
1080
1108
  rb_define_module_function(encoding, "time_precision=", rails_time_precision, 1);
1081
1109
  rb_gv_set("$VERBOSE", verbose);
@@ -1283,7 +1311,8 @@ dump_array(VALUE a, int depth, Out out, bool as_ok) {
1283
1311
  }
1284
1312
 
1285
1313
  static int
1286
- hash_cb(VALUE key, VALUE value, Out out) {
1314
+ hash_cb(VALUE key, VALUE value, VALUE ov) {
1315
+ Out out = (Out)ov;
1287
1316
  int depth = out->depth;
1288
1317
  long size;
1289
1318
  int rtype = rb_type(key);
@@ -1396,14 +1425,17 @@ dump_hash(VALUE obj, int depth, Out out, bool as_ok) {
1396
1425
 
1397
1426
  static void
1398
1427
  dump_obj(VALUE obj, int depth, Out out, bool as_ok) {
1428
+ VALUE clas;
1429
+
1399
1430
  if (oj_code_dump(oj_compat_codes, obj, depth, out)) {
1400
1431
  out->argc = 0;
1401
1432
  return;
1402
1433
  }
1434
+ clas = rb_obj_class(obj);
1403
1435
  if (as_ok) {
1404
1436
  ROpt ro;
1405
1437
 
1406
- if (NULL != (ro = oj_rails_get_opt(out->ropts, rb_obj_class(obj))) && ro->on) {
1438
+ if (NULL != (ro = oj_rails_get_opt(out->ropts, clas)) && ro->on) {
1407
1439
  ro->dump(obj, depth, out, as_ok);
1408
1440
  } else if (Yes == out->opts->raw_json && rb_respond_to(obj, oj_raw_json_id)) {
1409
1441
  oj_dump_raw_json(obj, depth, out);
@@ -1411,6 +1443,8 @@ dump_obj(VALUE obj, int depth, Out out, bool as_ok) {
1411
1443
  dump_as_json(obj, depth, out, true);
1412
1444
  } else if (rb_respond_to(obj, oj_to_hash_id)) {
1413
1445
  dump_to_hash(obj, depth, out);
1446
+ } else if (oj_bigdecimal_class == clas) {
1447
+ dump_bigdecimal(obj, depth, out, false);
1414
1448
  } else {
1415
1449
  oj_dump_obj_to_s(obj, out);
1416
1450
  }
@@ -1419,6 +1453,8 @@ dump_obj(VALUE obj, int depth, Out out, bool as_ok) {
1419
1453
  } else if (rb_respond_to(obj, oj_to_hash_id)) {
1420
1454
  // Always attempt to_hash.
1421
1455
  dump_to_hash(obj, depth, out);
1456
+ } else if (oj_bigdecimal_class == clas) {
1457
+ dump_bigdecimal(obj, depth, out, false);
1422
1458
  } else {
1423
1459
  oj_dump_obj_to_s(obj, out);
1424
1460
  }
@@ -1437,7 +1473,7 @@ static DumpFunc rails_funcs[] = {
1437
1473
  dump_hash, // RUBY_T_HASH = 0x08,
1438
1474
  dump_obj, // RUBY_T_STRUCT = 0x09,
1439
1475
  oj_dump_bignum, // RUBY_T_BIGNUM = 0x0a,
1440
- NULL, // RUBY_T_FILE = 0x0b,
1476
+ dump_as_string, // RUBY_T_FILE = 0x0b,
1441
1477
  dump_obj, // RUBY_T_DATA = 0x0c,
1442
1478
  NULL, // RUBY_T_MATCH = 0x0d,
1443
1479
  // Rails raises a stack error on Complex and Rational. It also corrupts