oj 3.13.7 → 3.13.23

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 (83) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +75 -0
  3. data/README.md +11 -0
  4. data/ext/oj/buf.h +4 -0
  5. data/ext/oj/circarray.c +1 -1
  6. data/ext/oj/code.c +15 -22
  7. data/ext/oj/compat.c +10 -10
  8. data/ext/oj/custom.c +66 -112
  9. data/ext/oj/dump.c +147 -184
  10. data/ext/oj/dump.h +25 -8
  11. data/ext/oj/dump_compat.c +47 -89
  12. data/ext/oj/dump_leaf.c +14 -58
  13. data/ext/oj/dump_object.c +72 -188
  14. data/ext/oj/dump_strict.c +19 -31
  15. data/ext/oj/encoder.c +43 -0
  16. data/ext/oj/extconf.rb +5 -4
  17. data/ext/oj/fast.c +36 -24
  18. data/ext/oj/intern.c +22 -12
  19. data/ext/oj/intern.h +1 -1
  20. data/ext/oj/mimic_json.c +74 -73
  21. data/ext/oj/object.c +54 -72
  22. data/ext/oj/odd.c +83 -63
  23. data/ext/oj/odd.h +13 -13
  24. data/ext/oj/oj.c +166 -175
  25. data/ext/oj/oj.h +25 -3
  26. data/ext/oj/parse.c +123 -79
  27. data/ext/oj/parse.h +2 -0
  28. data/ext/oj/parser.c +77 -21
  29. data/ext/oj/parser.h +12 -0
  30. data/ext/oj/rails.c +46 -70
  31. data/ext/oj/rails.h +1 -1
  32. data/ext/oj/reader.c +2 -0
  33. data/ext/oj/saj.c +11 -23
  34. data/ext/oj/saj2.c +333 -85
  35. data/ext/oj/saj2.h +23 -0
  36. data/ext/oj/sparse.c +4 -0
  37. data/ext/oj/stream_writer.c +3 -1
  38. data/ext/oj/strict.c +13 -13
  39. data/ext/oj/string_writer.c +12 -5
  40. data/ext/oj/usual.c +86 -131
  41. data/ext/oj/usual.h +68 -0
  42. data/ext/oj/val_stack.c +1 -1
  43. data/ext/oj/validate.c +21 -26
  44. data/ext/oj/wab.c +22 -27
  45. data/lib/oj/saj.rb +20 -6
  46. data/lib/oj/state.rb +1 -1
  47. data/lib/oj/version.rb +1 -1
  48. data/pages/Compatibility.md +1 -1
  49. data/pages/JsonGem.md +15 -0
  50. data/pages/Modes.md +6 -3
  51. data/pages/Options.md +6 -0
  52. data/pages/Rails.md +12 -0
  53. data/test/activesupport7/abstract_unit.rb +49 -0
  54. data/test/activesupport7/decoding_test.rb +125 -0
  55. data/test/activesupport7/encoding_test.rb +486 -0
  56. data/test/activesupport7/encoding_test_cases.rb +104 -0
  57. data/test/activesupport7/time_zone_test_helpers.rb +47 -0
  58. data/test/bar.rb +3 -8
  59. data/test/bug.rb +16 -0
  60. data/test/foo.rb +71 -7
  61. data/test/helper.rb +8 -2
  62. data/test/json_gem/json_generator_test.rb +5 -4
  63. data/test/json_gem/json_parser_test.rb +8 -1
  64. data/test/json_gem/test_helper.rb +7 -3
  65. data/test/perf_dump.rb +50 -0
  66. data/test/test_compat.rb +25 -0
  67. data/test/test_custom.rb +13 -2
  68. data/test/test_fast.rb +37 -7
  69. data/test/test_file.rb +23 -7
  70. data/test/test_gc.rb +11 -0
  71. data/test/test_object.rb +8 -10
  72. data/test/test_parser.rb +3 -19
  73. data/test/test_parser_debug.rb +27 -0
  74. data/test/test_parser_saj.rb +92 -2
  75. data/test/test_saj.rb +1 -1
  76. data/test/test_scp.rb +2 -4
  77. data/test/test_strict.rb +2 -0
  78. data/test/test_various.rb +32 -2
  79. data/test/test_wab.rb +2 -0
  80. data/test/tests.rb +9 -1
  81. data/test/tests_mimic.rb +9 -0
  82. data/test/tests_mimic_addition.rb +9 -0
  83. metadata +15 -115
data/ext/oj/wab.c CHANGED
@@ -35,7 +35,7 @@ static VALUE uri_http_clas = Qundef;
35
35
 
36
36
  ///// dump functions /////
37
37
 
38
- static VALUE resolve_wab_uuid_class() {
38
+ static VALUE resolve_wab_uuid_class(void) {
39
39
  if (Qundef == wab_uuid_clas) {
40
40
  volatile VALUE wab_module;
41
41
 
@@ -50,7 +50,7 @@ static VALUE resolve_wab_uuid_class() {
50
50
  return wab_uuid_clas;
51
51
  }
52
52
 
53
- static VALUE resolve_uri_class() {
53
+ static VALUE resolve_uri_class(void) {
54
54
  if (Qundef == uri_clas) {
55
55
  uri_clas = Qnil;
56
56
  if (rb_const_defined_at(rb_cObject, rb_intern("URI"))) {
@@ -60,7 +60,7 @@ static VALUE resolve_uri_class() {
60
60
  return uri_clas;
61
61
  }
62
62
 
63
- static VALUE resolve_uri_http_class() {
63
+ static VALUE resolve_uri_http_class(void) {
64
64
  if (Qundef == uri_http_clas) {
65
65
  volatile VALUE uri_module;
66
66
 
@@ -124,11 +124,11 @@ static void dump_array(VALUE a, int depth, Out out, bool as_ok) {
124
124
  *out->cur++ = ']';
125
125
  } else {
126
126
  size = d2 * out->indent + 2;
127
+ assure_size(out, size * cnt);
127
128
  cnt--;
128
129
  for (i = 0; i <= cnt; i++) {
129
- assure_size(out, size);
130
130
  fill_indent(out, d2);
131
- oj_dump_wab_val(rb_ary_entry(a, i), d2, out);
131
+ oj_dump_wab_val(RARRAY_AREF(a, i), d2, out);
132
132
  if (i < cnt) {
133
133
  *out->cur++ = ',';
134
134
  }
@@ -194,20 +194,15 @@ static void dump_time(VALUE obj, Out out) {
194
194
  time_t sec;
195
195
  long long nsec;
196
196
 
197
- #ifdef HAVE_RB_TIME_TIMESPEC
198
197
  if (16 <= sizeof(struct timespec)) {
199
198
  struct timespec ts = rb_time_timespec(obj);
200
199
 
201
200
  sec = ts.tv_sec;
202
201
  nsec = ts.tv_nsec;
203
202
  } else {
204
- sec = rb_num2ll(rb_funcall2(obj, oj_tv_sec_id, 0, 0));
205
- nsec = rb_num2ll(rb_funcall2(obj, oj_tv_nsec_id, 0, 0));
203
+ sec = NUM2LL(rb_funcall2(obj, oj_tv_sec_id, 0, 0));
204
+ nsec = NUM2LL(rb_funcall2(obj, oj_tv_nsec_id, 0, 0));
206
205
  }
207
- #else
208
- sec = rb_num2ll(rb_funcall2(obj, oj_tv_sec_id, 0, 0));
209
- nsec = rb_num2ll(rb_funcall2(obj, oj_tv_nsec_id, 0, 0));
210
- #endif
211
206
 
212
207
  assure_size(out, 36);
213
208
  // 2012-01-05T23:58:07.123456000Z
@@ -271,7 +266,7 @@ static DumpFunc wab_funcs[] = {
271
266
  void oj_dump_wab_val(VALUE obj, int depth, Out out) {
272
267
  int type = rb_type(obj);
273
268
 
274
- if (Yes == out->opts->trace) {
269
+ if (RB_UNLIKELY(Yes == out->opts->trace)) {
275
270
  oj_trace("dump", obj, __FILE__, __LINE__, depth, TraceIn);
276
271
  }
277
272
  if (MAX_DEPTH < depth) {
@@ -282,7 +277,7 @@ void oj_dump_wab_val(VALUE obj, int depth, Out out) {
282
277
 
283
278
  if (NULL != f) {
284
279
  f(obj, depth, out, false);
285
- if (Yes == out->opts->trace) {
280
+ if (RB_UNLIKELY(Yes == out->opts->trace)) {
286
281
  oj_trace("dump", obj, __FILE__, __LINE__, depth, TraceOut);
287
282
  }
288
283
  return;
@@ -317,13 +312,13 @@ static VALUE calc_hash_key(ParseInfo pi, Val parent) {
317
312
  }
318
313
 
319
314
  static void hash_end(ParseInfo pi) {
320
- if (Yes == pi->options.trace) {
315
+ if (RB_UNLIKELY(Yes == pi->options.trace)) {
321
316
  oj_trace_parse_hash_end(pi, __FILE__, __LINE__);
322
317
  }
323
318
  }
324
319
 
325
320
  static void array_end(ParseInfo pi) {
326
- if (Yes == pi->options.trace) {
321
+ if (RB_UNLIKELY(Yes == pi->options.trace)) {
327
322
  oj_trace_parse_array_end(pi, __FILE__, __LINE__);
328
323
  }
329
324
  }
@@ -333,7 +328,7 @@ static VALUE noop_hash_key(ParseInfo pi, const char *key, size_t klen) {
333
328
  }
334
329
 
335
330
  static void add_value(ParseInfo pi, VALUE val) {
336
- if (Yes == pi->options.trace) {
331
+ if (RB_UNLIKELY(Yes == pi->options.trace)) {
337
332
  oj_trace_parse_call("add_value", pi, __FILE__, __LINE__, val);
338
333
  }
339
334
  pi->stack.head->val = val;
@@ -483,7 +478,7 @@ static VALUE cstr_to_rstr(ParseInfo pi, const char *str, size_t len) {
483
478
 
484
479
  static void add_cstr(ParseInfo pi, const char *str, size_t len, const char *orig) {
485
480
  pi->stack.head->val = cstr_to_rstr(pi, str, len);
486
- if (Yes == pi->options.trace) {
481
+ if (RB_UNLIKELY(Yes == pi->options.trace)) {
487
482
  oj_trace_parse_call("add_string", pi, __FILE__, __LINE__, pi->stack.head->val);
488
483
  }
489
484
  }
@@ -493,13 +488,13 @@ static void add_num(ParseInfo pi, NumInfo ni) {
493
488
  oj_set_error_at(pi, oj_parse_error_class, __FILE__, __LINE__, "not a number or other value");
494
489
  }
495
490
  pi->stack.head->val = oj_num_as_value(ni);
496
- if (Yes == pi->options.trace) {
491
+ if (RB_UNLIKELY(Yes == pi->options.trace)) {
497
492
  oj_trace_parse_call("add_number", pi, __FILE__, __LINE__, pi->stack.head->val);
498
493
  }
499
494
  }
500
495
 
501
496
  static VALUE start_hash(ParseInfo pi) {
502
- if (Yes == pi->options.trace) {
497
+ if (RB_UNLIKELY(Yes == pi->options.trace)) {
503
498
  oj_trace_parse_in("start_hash", pi, __FILE__, __LINE__);
504
499
  }
505
500
  if (Qnil != pi->options.hash_class) {
@@ -512,7 +507,7 @@ static void hash_set_cstr(ParseInfo pi, Val parent, const char *str, size_t len,
512
507
  volatile VALUE rval = cstr_to_rstr(pi, str, len);
513
508
 
514
509
  rb_hash_aset(stack_peek(&pi->stack)->val, calc_hash_key(pi, parent), rval);
515
- if (Yes == pi->options.trace) {
510
+ if (RB_UNLIKELY(Yes == pi->options.trace)) {
516
511
  oj_trace_parse_call("set_string", pi, __FILE__, __LINE__, rval);
517
512
  }
518
513
  }
@@ -525,20 +520,20 @@ static void hash_set_num(ParseInfo pi, Val parent, NumInfo ni) {
525
520
  }
526
521
  rval = oj_num_as_value(ni);
527
522
  rb_hash_aset(stack_peek(&pi->stack)->val, calc_hash_key(pi, parent), rval);
528
- if (Yes == pi->options.trace) {
523
+ if (RB_UNLIKELY(Yes == pi->options.trace)) {
529
524
  oj_trace_parse_call("set_number", pi, __FILE__, __LINE__, rval);
530
525
  }
531
526
  }
532
527
 
533
528
  static void hash_set_value(ParseInfo pi, Val parent, VALUE value) {
534
529
  rb_hash_aset(stack_peek(&pi->stack)->val, calc_hash_key(pi, parent), value);
535
- if (Yes == pi->options.trace) {
530
+ if (RB_UNLIKELY(Yes == pi->options.trace)) {
536
531
  oj_trace_parse_call("set_value", pi, __FILE__, __LINE__, value);
537
532
  }
538
533
  }
539
534
 
540
535
  static VALUE start_array(ParseInfo pi) {
541
- if (Yes == pi->options.trace) {
536
+ if (RB_UNLIKELY(Yes == pi->options.trace)) {
542
537
  oj_trace_parse_in("start_array", pi, __FILE__, __LINE__);
543
538
  }
544
539
  return rb_ary_new();
@@ -548,7 +543,7 @@ static void array_append_cstr(ParseInfo pi, const char *str, size_t len, const c
548
543
  volatile VALUE rval = cstr_to_rstr(pi, str, len);
549
544
 
550
545
  rb_ary_push(stack_peek(&pi->stack)->val, rval);
551
- if (Yes == pi->options.trace) {
546
+ if (RB_UNLIKELY(Yes == pi->options.trace)) {
552
547
  oj_trace_parse_call("set_value", pi, __FILE__, __LINE__, rval);
553
548
  }
554
549
  }
@@ -561,14 +556,14 @@ static void array_append_num(ParseInfo pi, NumInfo ni) {
561
556
  }
562
557
  rval = oj_num_as_value(ni);
563
558
  rb_ary_push(stack_peek(&pi->stack)->val, rval);
564
- if (Yes == pi->options.trace) {
559
+ if (RB_UNLIKELY(Yes == pi->options.trace)) {
565
560
  oj_trace_parse_call("append_number", pi, __FILE__, __LINE__, rval);
566
561
  }
567
562
  }
568
563
 
569
564
  static void array_append_value(ParseInfo pi, VALUE value) {
570
565
  rb_ary_push(stack_peek(&pi->stack)->val, value);
571
- if (Yes == pi->options.trace) {
566
+ if (RB_UNLIKELY(Yes == pi->options.trace)) {
572
567
  oj_trace_parse_call("append_value", pi, __FILE__, __LINE__, value);
573
568
  }
574
569
  }
data/lib/oj/saj.rb CHANGED
@@ -1,11 +1,17 @@
1
1
  module Oj
2
- # A SAX style parse handler for JSON hence the acronym SAJ for Simple API for
3
- # JSON. The Oj::Saj handler class should be subclassed and then used with the
4
- # Oj::Saj key_parse() method. The Saj methods will then be called as the file
5
- # is parsed.
2
+ # A SAX style parse handler for JSON hence the acronym SAJ for Simple API
3
+ # for JSON. The Oj::Saj handler class can be subclassed and then used with
4
+ # the Oj::Saj key_parse() method or with the more resent
5
+ # Oj::Parser.new(:saj). The Saj methods will then be called as the file is
6
+ # parsed.
7
+ #
8
+ # With Oj::Parser.new(:saj) each method can also include a line and column
9
+ # argument so hash_start(key) could also be hash_start(key, line,
10
+ # column). The error() method is no used with Oj::Parser.new(:saj) so it
11
+ # will never be called.
6
12
  #
7
13
  # @example
8
- #
14
+ #
9
15
  # require 'oj'
10
16
  #
11
17
  # class MySaj < ::Oj::Saj
@@ -23,6 +29,14 @@ module Oj
23
29
  # Oj.saj_parse(cnt, f)
24
30
  # end
25
31
  #
32
+ # or
33
+ #
34
+ # p = Oj::Parser.new(:saj)
35
+ # p.handler = MySaj.new()
36
+ # File.open('any.json', 'r') do |f|
37
+ # p.parse(f.read)
38
+ # end
39
+ #
26
40
  # To make the desired methods active while parsing the desired method should
27
41
  # be made public in the subclasses. If the methods remain private they will
28
42
  # not be called during parsing.
@@ -61,6 +75,6 @@ module Oj
61
75
 
62
76
  def error(message, line, column)
63
77
  end
64
-
78
+
65
79
  end # Saj
66
80
  end # Oj
data/lib/oj/state.rb CHANGED
@@ -80,7 +80,7 @@ module JSON
80
80
  # @param [Symbol] m method symbol
81
81
  # @return [Boolean] true for any method that matches an instance
82
82
  # variable reader, otherwise false.
83
- def respond_to?(m)
83
+ def respond_to?(m, include_all = false)
84
84
  return true if super
85
85
  return true if has_key?(key)
86
86
  return true if has_key?(key.to_s)
data/lib/oj/version.rb CHANGED
@@ -1,5 +1,5 @@
1
1
 
2
2
  module Oj
3
3
  # Current version of the module.
4
- VERSION = '3.13.7'
4
+ VERSION = '3.13.23'
5
5
  end
@@ -2,7 +2,7 @@
2
2
 
3
3
  **Ruby**
4
4
 
5
- Oj is compatible with Ruby 2.0.0, 2.1, 2.2, 2.3, 2.4 and RBX.
5
+ Oj is compatible with Ruby 2.4+ and RBX.
6
6
  Support for JRuby has been removed as JRuby no longer supports C extensions and
7
7
  there are bugs in the older versions that are not being fixed.
8
8
 
data/pages/JsonGem.md CHANGED
@@ -1,3 +1,18 @@
1
+ # JSON Quickstart
2
+
3
+ To have Oj universally "take over" many methods on the JSON constant (`load`, `parse`, etc.) with
4
+ their faster Oj counterparts, in a mode that is compatible with the json gem:
5
+
6
+ ```ruby
7
+ Oj.mimic_JSON()
8
+ ```
9
+
10
+ If the project does not already use the json gem, `JSON` will become available.
11
+ If the project does require the json gem, `Oj.mimic_JSON()` should be invoked after the
12
+ json gem has been required.
13
+
14
+ For more details and options, read on...
15
+
1
16
  # Oj JSON Gem Compatibility
2
17
 
3
18
  The `:compat` mode mimics the json gem. The json gem is built around the use
data/pages/Modes.md CHANGED
@@ -39,7 +39,8 @@ if a non-native type is encountered instead of raising an Exception.
39
39
  The `:compat` mode mimics the json gem. The json gem is built around the use
40
40
  of the `to_json(*)` method defined for a class. Oj attempts to provide the
41
41
  same functionality by being a drop in replacement with a few
42
- exceptions. [{file:JsonGem.md}](JsonGem.md) includes more details on
42
+ exceptions. To universally replace many `JSON` methods with their faster Oj counterparts,
43
+ simply run `Oj.mimic_json`. [{file:JsonGem.md}](JsonGem.md) includes more details on
43
44
  compatibility and use.
44
45
 
45
46
  ## :rails Mode
@@ -108,11 +109,11 @@ information.
108
109
  | :float_precision | Fixnum | x | x | | | | x | |
109
110
  | :hash_class | Class | | | x | x | | x | |
110
111
  | :ignore | Array | | | | | x | x | |
111
- | :indent | Integer | x | x | 3 | 4 | x | x | x |
112
+ | :indent | Integer | x | x | 4 | 4 | x | x | x |
112
113
  | :indent_str | String | | | x | x | | x | |
113
114
  | :integer_range | Range | x | x | x | x | x | x | x |
114
115
  | :match_string | Hash | | | x | x | | x | |
115
- | :max_nesting | Fixnum | 4 | 4 | x | | 5 | 4 | |
116
+ | :max_nesting | Fixnum | 5 | 5 | x | | 5 | 5 | |
116
117
  | :mode | Symbol | - | - | - | - | - | - | |
117
118
  | :nan | Symbol | | | | | | x | |
118
119
  | :nilnil | Boolean | | | | | | x | |
@@ -140,6 +141,8 @@ information.
140
141
  3. By default the bigdecimal_as decimal is not set and the default encoding
141
142
  for Rails is as a string. Setting the value to true will encode a
142
143
  BigDecimal as a number which breaks compatibility.
144
+ Note: after version 3.11.3 both `Oj.generate` and `JSON.generate`
145
+ will not honour this option in Rails Mode, detais on https://github.com/ohler55/oj/pull/716.
143
146
 
144
147
  4. The integer indent value in the default options will be honored by since
145
148
  the json gem expects a String type the indent in calls to 'to_json()',
data/pages/Options.md CHANGED
@@ -66,6 +66,10 @@ Determines how to load decimals.
66
66
 
67
67
  - `:auto` the most precise for the number of digits is used.
68
68
 
69
+ - `:fast` faster conversion to Float.
70
+
71
+ - `:ruby` convert to Float using the Ruby `to_f` conversion.
72
+
69
73
  This can also be set with `:decimal_class` when used as a load or
70
74
  parse option to match the JSON gem. In that case either `Float`,
71
75
  `BigDecimal`, or `nil` can be provided.
@@ -154,6 +158,8 @@ Determines the characters to escape when dumping. Only the :ascii and
154
158
 
155
159
  - `:json` follows the JSON specification. This is the default mode.
156
160
 
161
+ - `:slash` escapes `/` characters.
162
+
157
163
  - `:xss_safe` escapes HTML and XML characters such as `&` and `<`.
158
164
 
159
165
  - `:ascii` escapes all non-ascii or characters with the hi-bit set.
data/pages/Rails.md CHANGED
@@ -1,3 +1,15 @@
1
+ # Rails Quickstart
2
+
3
+ To universally replace Rails' use of the json gem with Oj, and also
4
+ have Oj "take over" many methods on the JSON constant (`load`, `parse`, etc.) with
5
+ their faster Oj counterparts, add this to an initializer:
6
+
7
+ ```ruby
8
+ Oj.optimize_rails()
9
+ ```
10
+
11
+ For more details and options, read on...
12
+
1
13
  # Oj Rails Compatibility
2
14
 
3
15
  The `:rails` mode mimics the ActiveSupport version 5 encoder. Rails and
@@ -0,0 +1,49 @@
1
+ # frozen_string_literal: true
2
+
3
+ ORIG_ARGV = ARGV.dup
4
+
5
+ require "bundler/setup"
6
+ require "active_support/core_ext/kernel/reporting"
7
+
8
+ silence_warnings do
9
+ Encoding.default_internal = Encoding::UTF_8
10
+ Encoding.default_external = Encoding::UTF_8
11
+ end
12
+
13
+ require "active_support/testing/autorun"
14
+ require "active_support/testing/method_call_assertions"
15
+
16
+ ENV["NO_RELOAD"] = "1"
17
+ require "active_support"
18
+
19
+ Thread.abort_on_exception = true
20
+
21
+ # Show backtraces for deprecated behavior for quicker cleanup.
22
+ ActiveSupport::Deprecation.debug = true
23
+
24
+ # Default to old to_time behavior but allow running tests with new behavior
25
+ ActiveSupport.to_time_preserves_timezone = ENV["PRESERVE_TIMEZONES"] == "1"
26
+
27
+ # Disable available locale checks to avoid warnings running the test suite.
28
+ I18n.enforce_available_locales = false
29
+
30
+ class ActiveSupport::TestCase
31
+ if Process.respond_to?(:fork) && !Gem.win_platform?
32
+ parallelize
33
+ else
34
+ parallelize(with: :threads)
35
+ end
36
+
37
+ include ActiveSupport::Testing::MethodCallAssertions
38
+
39
+ private
40
+ # Skips the current run on Rubinius using Minitest::Assertions#skip
41
+ def rubinius_skip(message = "")
42
+ skip message if RUBY_ENGINE == "rbx"
43
+ end
44
+
45
+ # Skips the current run on JRuby using Minitest::Assertions#skip
46
+ def jruby_skip(message = "")
47
+ skip message if defined?(JRUBY_VERSION)
48
+ end
49
+ end
@@ -0,0 +1,125 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "abstract_unit"
4
+ require "active_support/json"
5
+ require "active_support/time"
6
+ require_relative "time_zone_test_helpers"
7
+
8
+ class TestJSONDecoding < ActiveSupport::TestCase
9
+ include TimeZoneTestHelpers
10
+
11
+ class Foo
12
+ def self.json_create(object)
13
+ "Foo"
14
+ end
15
+ end
16
+
17
+ TESTS = {
18
+ %q({"returnTo":{"\/categories":"\/"}}) => { "returnTo" => { "/categories" => "/" } },
19
+ %q({"return\\"To\\":":{"\/categories":"\/"}}) => { "return\"To\":" => { "/categories" => "/" } },
20
+ %q({"returnTo":{"\/categories":1}}) => { "returnTo" => { "/categories" => 1 } },
21
+ %({"returnTo":[1,"a"]}) => { "returnTo" => [1, "a"] },
22
+ %({"returnTo":[1,"\\"a\\",", "b"]}) => { "returnTo" => [1, "\"a\",", "b"] },
23
+ %({"a": "'", "b": "5,000"}) => { "a" => "'", "b" => "5,000" },
24
+ %({"a": "a's, b's and c's", "b": "5,000"}) => { "a" => "a's, b's and c's", "b" => "5,000" },
25
+ # multibyte
26
+ %({"matzue": "松江", "asakusa": "浅草"}) => { "matzue" => "松江", "asakusa" => "浅草" },
27
+ %({"a": "2007-01-01"}) => { "a" => Date.new(2007, 1, 1) },
28
+ %({"a": "2007-01-01 01:12:34 Z"}) => { "a" => Time.utc(2007, 1, 1, 1, 12, 34) },
29
+ %(["2007-01-01 01:12:34 Z"]) => [Time.utc(2007, 1, 1, 1, 12, 34)],
30
+ %(["2007-01-01 01:12:34 Z", "2007-01-01 01:12:35 Z"]) => [Time.utc(2007, 1, 1, 1, 12, 34), Time.utc(2007, 1, 1, 1, 12, 35)],
31
+ # no time zone
32
+ %({"a": "2007-01-01 01:12:34"}) => { "a" => Time.new(2007, 1, 1, 1, 12, 34, "-05:00") },
33
+ # invalid date
34
+ %({"a": "1089-10-40"}) => { "a" => "1089-10-40" },
35
+ # xmlschema date notation
36
+ %({"a": "2009-08-10T19:01:02"}) => { "a" => Time.new(2009, 8, 10, 19, 1, 2, "-04:00") },
37
+ %({"a": "2009-08-10T19:01:02Z"}) => { "a" => Time.utc(2009, 8, 10, 19, 1, 2) },
38
+ %({"a": "2009-08-10T19:01:02+02:00"}) => { "a" => Time.utc(2009, 8, 10, 17, 1, 2) },
39
+ %({"a": "2009-08-10T19:01:02-05:00"}) => { "a" => Time.utc(2009, 8, 11, 00, 1, 2) },
40
+ # needs to be *exact*
41
+ %({"a": " 2007-01-01 01:12:34 Z "}) => { "a" => " 2007-01-01 01:12:34 Z " },
42
+ %({"a": "2007-01-01 : it's your birthday"}) => { "a" => "2007-01-01 : it's your birthday" },
43
+ %({"a": "Today is:\\n2020-05-21"}) => { "a" => "Today is:\n2020-05-21" },
44
+ %({"a": "2007-01-01 01:12:34 Z\\nwas my birthday"}) => { "a" => "2007-01-01 01:12:34 Z\nwas my birthday" },
45
+ %([]) => [],
46
+ %({}) => {},
47
+ %({"a":1}) => { "a" => 1 },
48
+ %({"a": ""}) => { "a" => "" },
49
+ %({"a":"\\""}) => { "a" => "\"" },
50
+ %({"a": null}) => { "a" => nil },
51
+ %({"a": true}) => { "a" => true },
52
+ %({"a": false}) => { "a" => false },
53
+ '{"bad":"\\\\","trailing":""}' => { "bad" => "\\", "trailing" => "" },
54
+ %q({"a": "http:\/\/test.host\/posts\/1"}) => { "a" => "http://test.host/posts/1" },
55
+ %q({"a": "\u003cunicode\u0020escape\u003e"}) => { "a" => "<unicode escape>" },
56
+ '{"a": "\\\\u0020skip double backslashes"}' => { "a" => "\\u0020skip double backslashes" },
57
+ %q({"a": "\u003cbr /\u003e"}) => { "a" => "<br />" },
58
+ %q({"b":["\u003ci\u003e","\u003cb\u003e","\u003cu\u003e"]}) => { "b" => ["<i>", "<b>", "<u>"] },
59
+ # test combination of dates and escaped or unicode encoded data in arrays
60
+ %q([{"d":"1970-01-01", "s":"\u0020escape"},{"d":"1970-01-01", "s":"\u0020escape"}]) =>
61
+ [{ "d" => Date.new(1970, 1, 1), "s" => " escape" }, { "d" => Date.new(1970, 1, 1), "s" => " escape" }],
62
+ %q([{"d":"1970-01-01","s":"http:\/\/example.com"},{"d":"1970-01-01","s":"http:\/\/example.com"}]) =>
63
+ [{ "d" => Date.new(1970, 1, 1), "s" => "http://example.com" },
64
+ { "d" => Date.new(1970, 1, 1), "s" => "http://example.com" }],
65
+ # tests escaping of "\n" char with Yaml backend
66
+ %q({"a":"\n"}) => { "a" => "\n" },
67
+ %q({"a":"\u000a"}) => { "a" => "\n" },
68
+ %q({"a":"Line1\u000aLine2"}) => { "a" => "Line1\nLine2" },
69
+ # prevent json unmarshalling
70
+ '{"json_class":"TestJSONDecoding::Foo"}' => { "json_class" => "TestJSONDecoding::Foo" },
71
+ # json "fragments" - these are invalid JSON, but ActionPack relies on this
72
+ '"a string"' => "a string",
73
+ "1.1" => 1.1,
74
+ "1" => 1,
75
+ "-1" => -1,
76
+ "true" => true,
77
+ "false" => false,
78
+ "null" => nil
79
+ }
80
+
81
+ TESTS.each_with_index do |(json, expected), index|
82
+ fail_message = "JSON decoding failed for #{json}"
83
+
84
+ test "json decodes #{index}" do
85
+ with_tz_default "Eastern Time (US & Canada)" do
86
+ with_parse_json_times(true) do
87
+ silence_warnings do
88
+ if expected.nil?
89
+ assert_nil ActiveSupport::JSON.decode(json), fail_message
90
+ else
91
+ assert_equal expected, ActiveSupport::JSON.decode(json), fail_message
92
+ end
93
+ end
94
+ end
95
+ end
96
+ end
97
+ end
98
+
99
+ test "json decodes time json with time parsing disabled" do
100
+ with_parse_json_times(false) do
101
+ expected = { "a" => "2007-01-01 01:12:34 Z" }
102
+ assert_equal expected, ActiveSupport::JSON.decode(%({"a": "2007-01-01 01:12:34 Z"}))
103
+ end
104
+ end
105
+
106
+ def test_failed_json_decoding
107
+ assert_raise(ActiveSupport::JSON.parse_error) { ActiveSupport::JSON.decode(%(undefined)) }
108
+ assert_raise(ActiveSupport::JSON.parse_error) { ActiveSupport::JSON.decode(%({a: 1})) }
109
+ assert_raise(ActiveSupport::JSON.parse_error) { ActiveSupport::JSON.decode(%({: 1})) }
110
+ assert_raise(ActiveSupport::JSON.parse_error) { ActiveSupport::JSON.decode(%()) }
111
+ end
112
+
113
+ def test_cannot_pass_unsupported_options
114
+ assert_raise(ArgumentError) { ActiveSupport::JSON.decode("", create_additions: true) }
115
+ end
116
+
117
+ private
118
+ def with_parse_json_times(value)
119
+ old_value = ActiveSupport.parse_json_times
120
+ ActiveSupport.parse_json_times = value
121
+ yield
122
+ ensure
123
+ ActiveSupport.parse_json_times = old_value
124
+ end
125
+ end