oj 3.8.1 → 3.10.1

Sign up to get free protection for your applications and to get access to all the features.
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