oj 3.9.0 → 3.10.2

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 +7 -3
  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 +5 -0
  10. data/ext/oj/mimic_json.c +15 -3
  11. data/ext/oj/object.c +6 -2
  12. data/ext/oj/oj.c +47 -28
  13. data/ext/oj/oj.h +4 -2
  14. data/ext/oj/parse.c +22 -3
  15. data/ext/oj/parse.h +1 -0
  16. data/ext/oj/rails.c +38 -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 +59 -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 +8 -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
  }
@@ -1032,6 +1041,12 @@ CLEANUP:
1032
1041
  if (pi->str_rx.head != oj_default_options.str_rx.head) {
1033
1042
  oj_rxclass_cleanup(&pi->str_rx);
1034
1043
  }
1044
+ // TBD if validate only then (Qundef == result ??)
1045
+ // if pi->err or 0 != line
1046
+ // rb_get_errinfo();??
1047
+ // rb_set_errinfo(Qnil);
1048
+ // return nil or error
1049
+
1035
1050
  if (err_has(&pi->err)) {
1036
1051
  rb_set_errinfo(Qnil);
1037
1052
  if (Qnil != pi->err_class) {
@@ -1049,6 +1064,10 @@ CLEANUP:
1049
1064
  msg = rb_str_append(msg, oj_encode(rb_str_new2(pi->json)));
1050
1065
  }
1051
1066
  args[0] = msg;
1067
+ if (pi->err.clas == oj_parse_error_class) {
1068
+ // The error was an Oj::ParseError so change to a JSON::ParserError.
1069
+ pi->err.clas = oj_json_parser_error_class;
1070
+ }
1052
1071
  rb_exc_raise(rb_class_new_instance(1, args, pi->err.clas));
1053
1072
  } else {
1054
1073
  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,14 +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
 
1074
1097
  pv = rb_iv_get(encoding, "@escape_html_entities_in_json");
1075
1098
  escape_html = Qtrue == pv;
1076
1099
  rb_undef_method(encoding, "escape_html_entities_in_json=");
1077
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);
1078
1103
 
1079
1104
  pv = rb_iv_get(encoding, "@time_precision");
1080
1105
  oj_default_options.sec_prec = NUM2INT(pv);
1106
+ oj_default_options.sec_prec_set = true;
1081
1107
  rb_undef_method(encoding, "time_precision=");
1082
1108
  rb_define_module_function(encoding, "time_precision=", rails_time_precision, 1);
1083
1109
  rb_gv_set("$VERBOSE", verbose);
@@ -1285,7 +1311,8 @@ dump_array(VALUE a, int depth, Out out, bool as_ok) {
1285
1311
  }
1286
1312
 
1287
1313
  static int
1288
- hash_cb(VALUE key, VALUE value, Out out) {
1314
+ hash_cb(VALUE key, VALUE value, VALUE ov) {
1315
+ Out out = (Out)ov;
1289
1316
  int depth = out->depth;
1290
1317
  long size;
1291
1318
  int rtype = rb_type(key);
@@ -1398,14 +1425,17 @@ dump_hash(VALUE obj, int depth, Out out, bool as_ok) {
1398
1425
 
1399
1426
  static void
1400
1427
  dump_obj(VALUE obj, int depth, Out out, bool as_ok) {
1428
+ VALUE clas;
1429
+
1401
1430
  if (oj_code_dump(oj_compat_codes, obj, depth, out)) {
1402
1431
  out->argc = 0;
1403
1432
  return;
1404
1433
  }
1434
+ clas = rb_obj_class(obj);
1405
1435
  if (as_ok) {
1406
1436
  ROpt ro;
1407
1437
 
1408
- 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) {
1409
1439
  ro->dump(obj, depth, out, as_ok);
1410
1440
  } else if (Yes == out->opts->raw_json && rb_respond_to(obj, oj_raw_json_id)) {
1411
1441
  oj_dump_raw_json(obj, depth, out);
@@ -1413,6 +1443,8 @@ dump_obj(VALUE obj, int depth, Out out, bool as_ok) {
1413
1443
  dump_as_json(obj, depth, out, true);
1414
1444
  } else if (rb_respond_to(obj, oj_to_hash_id)) {
1415
1445
  dump_to_hash(obj, depth, out);
1446
+ } else if (oj_bigdecimal_class == clas) {
1447
+ dump_bigdecimal(obj, depth, out, false);
1416
1448
  } else {
1417
1449
  oj_dump_obj_to_s(obj, out);
1418
1450
  }
@@ -1421,6 +1453,8 @@ dump_obj(VALUE obj, int depth, Out out, bool as_ok) {
1421
1453
  } else if (rb_respond_to(obj, oj_to_hash_id)) {
1422
1454
  // Always attempt to_hash.
1423
1455
  dump_to_hash(obj, depth, out);
1456
+ } else if (oj_bigdecimal_class == clas) {
1457
+ dump_bigdecimal(obj, depth, out, false);
1424
1458
  } else {
1425
1459
  oj_dump_obj_to_s(obj, out);
1426
1460
  }
@@ -1439,7 +1473,7 @@ static DumpFunc rails_funcs[] = {
1439
1473
  dump_hash, // RUBY_T_HASH = 0x08,
1440
1474
  dump_obj, // RUBY_T_STRUCT = 0x09,
1441
1475
  oj_dump_bignum, // RUBY_T_BIGNUM = 0x0a,
1442
- NULL, // RUBY_T_FILE = 0x0b,
1476
+ dump_as_string, // RUBY_T_FILE = 0x0b,
1443
1477
  dump_obj, // RUBY_T_DATA = 0x0c,
1444
1478
  NULL, // RUBY_T_MATCH = 0x0d,
1445
1479
  // 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.0'
4
+ VERSION = '3.10.2'
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,46 @@ 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[:json]
93
+ obj = options.delete(:json)
94
+ options[:text] = Oj.dump(obj, :mode => :rails)
95
+ options[:content_type] = 'application/json'
96
+ end
97
+ super
98
+ end
99
+ end
100
+ ```
101
+
102
+ Usage:
103
+
104
+ ```ruby
105
+ class MyController < ApplicationController
106
+ prepend OjJsonEncoder
107
+ def index
108
+ render :json => { :hello => 'world' }
109
+ end
110
+ end
111
+ ```
112
+
113
+ ### Older Ruby Version Support (Pre 2.3.0)
114
+
115
+ If you are using an older version of Ruby, you can pin `oj` to an earlier version in your Gemfile:
116
+
117
+ ```ruby
118
+ gem 'oj', '3.7.12'
119
+ ```
82
120
 
83
121
  ### Notes:
84
122
 
@@ -87,8 +125,8 @@ Oj::Rails.set_decoder() method replaces that method with the Oj equivalent.
87
125
  significant digits which can be either 16 or 17 depending on the value.
88
126
 
89
127
  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
128
+ an example, a non-String object that has a `to_s()` method will become the
129
+ return value of the `to_s()` method in the output without checking to see if
92
130
  that has already been used. This could occur is a mix of String and Symbols
93
131
  are used as keys or if a other non-String objects such as Numerics are mixed
94
132
  with numbers as Strings.