oj 3.9.1 → 3.10.3

Sign up to get free protection for your applications and to get access to all the features.
Files changed (46) 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 +6 -2
  5. data/ext/oj/dump.c +10 -13
  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 +5 -0
  10. data/ext/oj/mimic_json.c +15 -3
  11. data/ext/oj/object.c +2 -1
  12. data/ext/oj/oj.c +47 -28
  13. data/ext/oj/oj.h +4 -2
  14. data/ext/oj/parse.c +16 -3
  15. data/ext/oj/parse.h +1 -0
  16. data/ext/oj/rails.c +37 -4
  17. data/ext/oj/sparse.c +5 -0
  18. data/ext/oj/util.c +5 -5
  19. data/ext/oj/wab.c +9 -9
  20. data/lib/oj/version.rb +1 -1
  21. data/pages/Rails.md +60 -21
  22. data/test/activesupport5/abstract_unit.rb +45 -0
  23. data/test/activesupport5/decoding_test.rb +68 -60
  24. data/test/activesupport5/encoding_test.rb +111 -96
  25. data/test/activesupport5/encoding_test_cases.rb +33 -25
  26. data/test/activesupport5/test_helper.rb +43 -21
  27. data/test/activesupport5/time_zone_test_helpers.rb +18 -3
  28. data/test/activesupport6/abstract_unit.rb +44 -0
  29. data/test/activesupport6/decoding_test.rb +133 -0
  30. data/test/activesupport6/encoding_test.rb +507 -0
  31. data/test/activesupport6/encoding_test_cases.rb +98 -0
  32. data/test/activesupport6/test_common.rb +17 -0
  33. data/test/activesupport6/test_helper.rb +163 -0
  34. data/test/activesupport6/time_zone_test_helpers.rb +39 -0
  35. data/test/bar.rb +21 -11
  36. data/test/baz.rb +16 -0
  37. data/test/foo.rb +39 -8
  38. data/test/test_compat.rb +0 -7
  39. data/test/test_custom.rb +25 -6
  40. data/test/test_integer_range.rb +1 -2
  41. data/test/test_object.rb +12 -3
  42. data/test/test_rails.rb +26 -0
  43. data/test/test_strict.rb +24 -1
  44. data/test/test_various.rb +41 -62
  45. data/test/tests.rb +1 -0
  46. metadata +23 -3
@@ -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
@@ -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 {
@@ -1012,6 +1015,7 @@ rails_encode(int argc, VALUE *argv, VALUE self) {
1012
1015
  static VALUE
1013
1016
  rails_use_standard_json_time_format(VALUE self, VALUE state) {
1014
1017
  if (Qtrue == state || Qfalse == state) {
1018
+ // no change needed
1015
1019
  } else if (Qnil == state) {
1016
1020
  state = Qfalse;
1017
1021
  } else {
@@ -1023,6 +1027,11 @@ rails_use_standard_json_time_format(VALUE self, VALUE state) {
1023
1027
  return state;
1024
1028
  }
1025
1029
 
1030
+ static VALUE
1031
+ rails_use_standard_json_time_format_get(VALUE self) {
1032
+ return xml_time ? Qtrue : Qfalse;
1033
+ }
1034
+
1026
1035
  static VALUE
1027
1036
  rails_escape_html_entities_in_json(VALUE self, VALUE state) {
1028
1037
  rb_iv_set(self, "@escape_html_entities_in_json", state);
@@ -1031,10 +1040,16 @@ rails_escape_html_entities_in_json(VALUE self, VALUE state) {
1031
1040
  return state;
1032
1041
  }
1033
1042
 
1043
+ static VALUE
1044
+ rails_escape_html_entities_in_json_get(VALUE self) {
1045
+ return escape_html ? Qtrue : Qfalse;
1046
+ }
1047
+
1034
1048
  static VALUE
1035
1049
  rails_time_precision(VALUE self, VALUE prec) {
1036
1050
  rb_iv_set(self, "@time_precision", prec);
1037
1051
  oj_default_options.sec_prec = NUM2INT(prec);
1052
+ oj_default_options.sec_prec_set = true;
1038
1053
 
1039
1054
  return prec;
1040
1055
  }
@@ -1053,7 +1068,12 @@ rails_set_encoder(VALUE self) {
1053
1068
  VALUE encoding;
1054
1069
  VALUE pv;
1055
1070
  VALUE verbose;
1071
+ VALUE enc = resolve_classpath("ActiveSupport::JSON::Encoding");
1056
1072
 
1073
+ if (Qnil != enc) {
1074
+ escape_html = Qtrue == rb_iv_get(self, "@escape_html_entities_in_json");
1075
+ xml_time = Qtrue == rb_iv_get(enc, "@use_standard_json_time_format");
1076
+ }
1057
1077
  if (rb_const_defined_at(rb_cObject, rb_intern("ActiveSupport"))) {
1058
1078
  active = rb_const_get_at(rb_cObject, rb_intern("ActiveSupport"));
1059
1079
  } else {
@@ -1070,14 +1090,19 @@ rails_set_encoder(VALUE self) {
1070
1090
  rb_gv_set("$VERBOSE", Qfalse);
1071
1091
  rb_undef_method(encoding, "use_standard_json_time_format=");
1072
1092
  rb_define_module_function(encoding, "use_standard_json_time_format=", rails_use_standard_json_time_format, 1);
1093
+ rb_undef_method(encoding, "use_standard_json_time_format");
1094
+ rb_define_module_function(encoding, "use_standard_json_time_format", rails_use_standard_json_time_format_get, 0);
1073
1095
 
1074
1096
  pv = rb_iv_get(encoding, "@escape_html_entities_in_json");
1075
1097
  escape_html = Qtrue == pv;
1076
1098
  rb_undef_method(encoding, "escape_html_entities_in_json=");
1077
1099
  rb_define_module_function(encoding, "escape_html_entities_in_json=", rails_escape_html_entities_in_json, 1);
1100
+ rb_undef_method(encoding, "escape_html_entities_in_json");
1101
+ rb_define_module_function(encoding, "escape_html_entities_in_json", rails_escape_html_entities_in_json_get, 0);
1078
1102
 
1079
1103
  pv = rb_iv_get(encoding, "@time_precision");
1080
1104
  oj_default_options.sec_prec = NUM2INT(pv);
1105
+ oj_default_options.sec_prec_set = true;
1081
1106
  rb_undef_method(encoding, "time_precision=");
1082
1107
  rb_define_module_function(encoding, "time_precision=", rails_time_precision, 1);
1083
1108
  rb_gv_set("$VERBOSE", verbose);
@@ -1285,7 +1310,8 @@ dump_array(VALUE a, int depth, Out out, bool as_ok) {
1285
1310
  }
1286
1311
 
1287
1312
  static int
1288
- hash_cb(VALUE key, VALUE value, Out out) {
1313
+ hash_cb(VALUE key, VALUE value, VALUE ov) {
1314
+ Out out = (Out)ov;
1289
1315
  int depth = out->depth;
1290
1316
  long size;
1291
1317
  int rtype = rb_type(key);
@@ -1398,14 +1424,17 @@ dump_hash(VALUE obj, int depth, Out out, bool as_ok) {
1398
1424
 
1399
1425
  static void
1400
1426
  dump_obj(VALUE obj, int depth, Out out, bool as_ok) {
1427
+ VALUE clas;
1428
+
1401
1429
  if (oj_code_dump(oj_compat_codes, obj, depth, out)) {
1402
1430
  out->argc = 0;
1403
1431
  return;
1404
1432
  }
1433
+ clas = rb_obj_class(obj);
1405
1434
  if (as_ok) {
1406
1435
  ROpt ro;
1407
1436
 
1408
- if (NULL != (ro = oj_rails_get_opt(out->ropts, rb_obj_class(obj))) && ro->on) {
1437
+ if (NULL != (ro = oj_rails_get_opt(out->ropts, clas)) && ro->on) {
1409
1438
  ro->dump(obj, depth, out, as_ok);
1410
1439
  } else if (Yes == out->opts->raw_json && rb_respond_to(obj, oj_raw_json_id)) {
1411
1440
  oj_dump_raw_json(obj, depth, out);
@@ -1413,6 +1442,8 @@ dump_obj(VALUE obj, int depth, Out out, bool as_ok) {
1413
1442
  dump_as_json(obj, depth, out, true);
1414
1443
  } else if (rb_respond_to(obj, oj_to_hash_id)) {
1415
1444
  dump_to_hash(obj, depth, out);
1445
+ } else if (oj_bigdecimal_class == clas) {
1446
+ dump_bigdecimal(obj, depth, out, false);
1416
1447
  } else {
1417
1448
  oj_dump_obj_to_s(obj, out);
1418
1449
  }
@@ -1421,6 +1452,8 @@ dump_obj(VALUE obj, int depth, Out out, bool as_ok) {
1421
1452
  } else if (rb_respond_to(obj, oj_to_hash_id)) {
1422
1453
  // Always attempt to_hash.
1423
1454
  dump_to_hash(obj, depth, out);
1455
+ } else if (oj_bigdecimal_class == clas) {
1456
+ dump_bigdecimal(obj, depth, out, false);
1424
1457
  } else {
1425
1458
  oj_dump_obj_to_s(obj, out);
1426
1459
  }
@@ -1439,7 +1472,7 @@ static DumpFunc rails_funcs[] = {
1439
1472
  dump_hash, // RUBY_T_HASH = 0x08,
1440
1473
  dump_obj, // RUBY_T_STRUCT = 0x09,
1441
1474
  oj_dump_bignum, // RUBY_T_BIGNUM = 0x0a,
1442
- NULL, // RUBY_T_FILE = 0x0b,
1475
+ dump_as_string, // RUBY_T_FILE = 0x0b,
1443
1476
  dump_obj, // RUBY_T_DATA = 0x0c,
1444
1477
  NULL, // RUBY_T_MATCH = 0x0d,
1445
1478
  // Rails raises a stack error on Complex and Rational. It also corrupts
@@ -773,6 +773,7 @@ oj_sparse2(ParseInfo pi) {
773
773
  first = 0;
774
774
  }
775
775
  start = pi->rd.pos;
776
+ // TBD break if option set to allow that
776
777
  }
777
778
  }
778
779
  }
@@ -898,6 +899,10 @@ CLEANUP:
898
899
  // idea.
899
900
  VALUE args[] = { oj_encode(rb_str_new2(pi->err.msg)) };
900
901
 
902
+ if (pi->err.clas == oj_parse_error_class) {
903
+ // The error was an Oj::ParseError so change to a JSON::ParserError.
904
+ pi->err.clas = oj_json_parser_error_class;
905
+ }
901
906
  rb_exc_raise(rb_class_new_instance(1, args, pi->err.clas));
902
907
  } else {
903
908
  oj_err_raise(&pi->err);
@@ -110,7 +110,7 @@ sec_as_time(int64_t secs, TimeInfo ti) {
110
110
  }
111
111
  }
112
112
  }
113
- ti->year = (qc - shift) * 400 + c * 100 + qy * 4 + y;
113
+ ti->year = (int)((qc - (int64_t)shift) * 400 + c * 100 + qy * 4 + y);
114
114
  if (leap) {
115
115
  ms = eom_leap_secs;
116
116
  } else {
@@ -125,12 +125,12 @@ sec_as_time(int64_t secs, TimeInfo ti) {
125
125
  break;
126
126
  }
127
127
  }
128
- ti->day = secs / 86400LL;
128
+ ti->day = (int)(secs / 86400LL);
129
129
  secs = secs - (int64_t)ti->day * 86400LL;
130
130
  ti->day++;
131
- ti->hour = secs / 3600LL;
131
+ ti->hour = (int)(secs / 3600LL);
132
132
  secs = secs - (int64_t)ti->hour * 3600LL;
133
- ti->min = secs / 60LL;
133
+ ti->min = (int)(secs / 60LL);
134
134
  secs = secs - (int64_t)ti->min * 60LL;
135
- ti->sec = secs;
135
+ ti->sec = (int)secs;
136
136
  }
@@ -148,11 +148,12 @@ dump_array(VALUE a, int depth, Out out, bool as_ok) {
148
148
  }
149
149
 
150
150
  static int
151
- hash_cb(VALUE key, VALUE value, Out out) {
151
+ hash_cb(VALUE key, VALUE value, VALUE ov) {
152
+ Out out = (Out)ov;
152
153
  int depth = out->depth;
153
154
  long size;
154
155
  int rtype = rb_type(key);
155
-
156
+
156
157
  if (rtype != T_SYMBOL) {
157
158
  rb_raise(rb_eTypeError, "In :wab mode all Hash keys must be Symbols, not %s.\n", rb_class2name(rb_obj_class(key)));
158
159
  }
@@ -270,7 +271,7 @@ static DumpFunc wab_funcs[] = {
270
271
  void
271
272
  oj_dump_wab_val(VALUE obj, int depth, Out out) {
272
273
  int type = rb_type(obj);
273
-
274
+
274
275
  if (Yes == out->opts->trace) {
275
276
  oj_trace("dump", obj, __FILE__, __LINE__, depth, TraceIn);
276
277
  }
@@ -324,7 +325,7 @@ add_value(ParseInfo pi, VALUE val) {
324
325
  static bool
325
326
  uuid_check(const char *str, int len) {
326
327
  int i;
327
-
328
+
328
329
  for (i = 0; i < 8; i++, str++) {
329
330
  if ('x' != hex_chars[*(uint8_t*)str]) {
330
331
  return false;
@@ -380,7 +381,7 @@ time_parse(const char *s, int len) {
380
381
  long nsecs = 0;
381
382
  int i;
382
383
  time_t secs;
383
-
384
+
384
385
  memset(&tm, 0, sizeof(tm));
385
386
  if ('-' == *s) {
386
387
  s++;
@@ -444,7 +445,7 @@ protect_uri(VALUE rstr) {
444
445
  static VALUE
445
446
  cstr_to_rstr(const char *str, size_t len) {
446
447
  volatile VALUE v = Qnil;
447
-
448
+
448
449
  if (30 == len && '-' == str[4] && '-' == str[7] && 'T' == str[10] && ':' == str[13] && ':' == str[16] && '.' == str[19] && 'Z' == str[29]) {
449
450
  if (Qnil != (v = time_parse(str, (int)len))) {
450
451
  return v;
@@ -521,7 +522,7 @@ hash_set_cstr(ParseInfo pi, Val parent, const char *str, size_t len, const char
521
522
  static void
522
523
  hash_set_num(ParseInfo pi, Val parent, NumInfo ni) {
523
524
  volatile VALUE rval = Qnil;
524
-
525
+
525
526
  if (ni->infinity || ni->nan) {
526
527
  oj_set_error_at(pi, oj_parse_error_class, __FILE__, __LINE__, "not a number or other value");
527
528
  }
@@ -551,7 +552,7 @@ start_array(ParseInfo pi) {
551
552
  static void
552
553
  array_append_cstr(ParseInfo pi, const char *str, size_t len, const char *orig) {
553
554
  volatile VALUE rval = cstr_to_rstr(str, len);
554
-
555
+
555
556
  rb_ary_push(stack_peek(&pi->stack)->val, rval);
556
557
  if (Yes == pi->options.trace) {
557
558
  oj_trace_parse_call("set_value", pi, __FILE__, __LINE__, rval);
@@ -628,4 +629,3 @@ oj_wab_parse_cstr(int argc, VALUE *argv, char *json, size_t len) {
628
629
 
629
630
  return oj_pi_parse(argc, argv, &pi, json, len, true);
630
631
  }
631
-
@@ -1,5 +1,5 @@
1
1
 
2
2
  module Oj
3
3
  # Current version of the module.
4
- VERSION = '3.9.1'
4
+ VERSION = '3.10.3'
5
5
  end
@@ -26,44 +26,44 @@ directly. If Rails mode is also desired then use the `Oj.default_options` to
26
26
  change the default mode.
27
27
 
28
28
  Some of the Oj options are supported as arguments to the encoder if called
29
- from Oj::Rails.encode() but when using the Oj::Rails::Encoder class the
30
- encode() method does not support optional arguments as required by the
29
+ from `Oj::Rails.encode()` but when using the `Oj::Rails::Encoder` class the
30
+ `encode()` method does not support optional arguments as required by the
31
31
  ActiveSupport compliance guidelines. The general approach Rails takes for
32
32
  configuring encoding options is to either set global values or to create a new
33
33
  instance of the Encoder class and provide options in the initializer.
34
34
 
35
35
  The globals that ActiveSupport uses for encoding are:
36
36
 
37
- * ActiveSupport::JSON::Encoding.use_standard_json_time_format
38
- * ActiveSupport::JSON::Encoding.escape_html_entities_in_json
39
- * ActiveSupport::JSON::Encoding.time_precision
40
- * ActiveSupport::JSON::Encoding.json_encoder
37
+ * `ActiveSupport::JSON::Encoding.use_standard_json_time_format`
38
+ * `ActiveSupport::JSON::Encoding.escape_html_entities_in_json`
39
+ * `ActiveSupport::JSON::Encoding.time_precision`
40
+ * `ActiveSupport::JSON::Encoding.json_encoder`
41
41
 
42
42
  Those globals are aliased to also be accessed from the ActiveSupport module
43
- directly so ActiveSupport::JSON::Encoding.time_precision can also be accessed
44
- from ActiveSupport.time_precision. Oj makes use of these globals in mimicing
45
- Rails after the Oj::Rails.set_encode() method is called. That also sets the
46
- ActiveSupport.json_encoder to the Oj::Rails::Encoder class.
43
+ directly so `ActiveSupport::JSON::Encoding.time_precision` can also be accessed
44
+ from `ActiveSupport.time_precision`. Oj makes use of these globals in mimicing
45
+ Rails after the `Oj::Rails.set_encode()` method is called. That also sets the
46
+ `ActiveSupport.json_encoder` to the `Oj::Rails::Encoder` class.
47
47
 
48
- Options passed into a call to to_json() are passed to the as_json()
48
+ Options passed into a call to `to_json()` are passed to the `as_json()`
49
49
  methods. These are mostly ignored by Oj and simply passed on without
50
50
  modifications as per the guidelines. The exception to this are the options
51
- specific to Oj such as the :circular option which it used to detect circular
51
+ specific to Oj such as the `:circular` option which it used to detect circular
52
52
  references while encoding.
53
53
 
54
54
  By default Oj acts like the ActiveSupport encoder and honors any changes in
55
- the as_json() methods. There are some optimized Oj encoders for some
56
- classes. When the optimized encoder it toggled the as_json() methods will not
55
+ the `as_json()` methods. There are some optimized Oj encoders for some
56
+ classes. When the optimized encoder it toggled the `as_json()` methods will not
57
57
  be called for that class but instead the optimized version will be called. The
58
58
  optimized version is the same as the ActiveSupport default encoding for a
59
- given class. The optimized versions are toggled with the optimize() and
60
- deoptimize() methods. There is a default optimized version for every class
59
+ given class. The optimized versions are toggled with the `optimize()` and
60
+ `deoptimize()` methods. There is a default optimized version for every class
61
61
  that takes the visible attributes and encodes them but that may not be the
62
62
  same as what Rails uses. Trial and error is the best approach for classes not
63
63
  listed here.
64
64
 
65
65
  The classes that can be put in optimized mode and are optimized when
66
- Oj::Rails.optimize is called with no arguments are:
66
+ `Oj::Rails.optimize` is called with no arguments are:
67
67
 
68
68
  * Array
69
69
  * BigDecimal
@@ -77,8 +77,47 @@ Oj::Rails.optimize is called with no arguments are:
77
77
  * any class inheriting from ActiveRecord::Base
78
78
  * any other class where all attributes should be dumped
79
79
 
80
- The ActiveSupport decoder is the JSON.parse() method. Calling the
81
- Oj::Rails.set_decoder() method replaces that method with the Oj equivalent.
80
+ The ActiveSupport decoder is the `JSON.parse()` method. Calling the
81
+ `Oj::Rails.set_decoder()` method replaces that method with the Oj equivalent.
82
+
83
+ ### Usage in Rails 3
84
+
85
+ To support Rails 3 you can create a new module mixin to prepend to controllers:
86
+
87
+ ```ruby
88
+ require 'oj'
89
+
90
+ module OjJsonEncoder
91
+ def render(options = nil, extra_options = {}, &block)
92
+ if options && options.is_a?(Hash) && options[:json]
93
+ obj = options.delete(:json)
94
+ obj = Oj.dump(obj, :mode => :rails) unless obj.is_a?(String)
95
+ options[:text] = obj
96
+ response.content_type ||= Mime::JSON
97
+ end
98
+ super
99
+ end
100
+ end
101
+ ```
102
+
103
+ Usage:
104
+
105
+ ```ruby
106
+ class MyController < ApplicationController
107
+ prepend OjJsonEncoder
108
+ def index
109
+ render :json => { :hello => 'world' }
110
+ end
111
+ end
112
+ ```
113
+
114
+ ### Older Ruby Version Support (Pre 2.3.0)
115
+
116
+ If you are using an older version of Ruby, you can pin `oj` to an earlier version in your Gemfile:
117
+
118
+ ```ruby
119
+ gem 'oj', '3.7.12'
120
+ ```
82
121
 
83
122
  ### Notes:
84
123
 
@@ -87,8 +126,8 @@ Oj::Rails.set_decoder() method replaces that method with the Oj equivalent.
87
126
  significant digits which can be either 16 or 17 depending on the value.
88
127
 
89
128
  2. Optimized Hashs do not collapse keys that become the same in the output. As
90
- an example, a non-String object that has a to_s() method will become the
91
- return value of the to_s() method in the output without checking to see if
129
+ an example, a non-String object that has a `to_s()` method will become the
130
+ return value of the `to_s()` method in the output without checking to see if
92
131
  that has already been used. This could occur is a mix of String and Symbols
93
132
  are used as keys or if a other non-String objects such as Numerics are mixed
94
133
  with numbers as Strings.