oj 3.13.11 → 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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +50 -0
- data/README.md +2 -0
- data/ext/oj/buf.h +4 -0
- data/ext/oj/circarray.c +1 -1
- data/ext/oj/code.c +15 -22
- data/ext/oj/compat.c +10 -10
- data/ext/oj/custom.c +62 -108
- data/ext/oj/dump.c +85 -97
- data/ext/oj/dump.h +12 -8
- data/ext/oj/dump_compat.c +46 -88
- data/ext/oj/dump_leaf.c +14 -58
- data/ext/oj/dump_object.c +33 -156
- data/ext/oj/dump_strict.c +17 -29
- data/ext/oj/extconf.rb +5 -4
- data/ext/oj/fast.c +24 -22
- data/ext/oj/intern.c +15 -11
- data/ext/oj/intern.h +1 -1
- data/ext/oj/mimic_json.c +44 -32
- data/ext/oj/object.c +42 -41
- data/ext/oj/odd.c +83 -63
- data/ext/oj/odd.h +13 -13
- data/ext/oj/oj.c +57 -22
- data/ext/oj/oj.h +24 -3
- data/ext/oj/parse.c +114 -78
- data/ext/oj/parse.h +2 -0
- data/ext/oj/parser.c +77 -21
- data/ext/oj/parser.h +12 -0
- data/ext/oj/rails.c +41 -65
- data/ext/oj/rails.h +1 -1
- data/ext/oj/reader.c +2 -0
- data/ext/oj/saj.c +11 -23
- data/ext/oj/saj2.c +333 -85
- data/ext/oj/saj2.h +23 -0
- data/ext/oj/sparse.c +4 -0
- data/ext/oj/stream_writer.c +3 -1
- data/ext/oj/strict.c +13 -13
- data/ext/oj/string_writer.c +12 -5
- data/ext/oj/usual.c +82 -129
- data/ext/oj/usual.h +68 -0
- data/ext/oj/val_stack.c +1 -1
- data/ext/oj/validate.c +21 -26
- data/ext/oj/wab.c +21 -26
- data/lib/oj/saj.rb +20 -6
- data/lib/oj/state.rb +1 -1
- data/lib/oj/version.rb +1 -1
- data/pages/Compatibility.md +1 -1
- data/pages/Options.md +6 -0
- data/test/activesupport7/abstract_unit.rb +49 -0
- data/test/activesupport7/decoding_test.rb +125 -0
- data/test/activesupport7/encoding_test.rb +486 -0
- data/test/activesupport7/encoding_test_cases.rb +104 -0
- data/test/activesupport7/time_zone_test_helpers.rb +47 -0
- data/test/bar.rb +3 -8
- data/test/foo.rb +3 -3
- data/test/helper.rb +8 -2
- data/test/json_gem/json_generator_test.rb +5 -4
- data/test/json_gem/json_parser_test.rb +8 -1
- data/test/json_gem/test_helper.rb +7 -3
- data/test/perf_dump.rb +50 -0
- data/test/test_compat.rb +25 -0
- data/test/test_custom.rb +13 -2
- data/test/test_file.rb +23 -7
- data/test/test_gc.rb +11 -0
- data/test/test_object.rb +8 -10
- data/test/test_parser.rb +3 -19
- data/test/test_parser_debug.rb +27 -0
- data/test/test_parser_saj.rb +92 -2
- data/test/test_scp.rb +2 -4
- data/test/test_strict.rb +2 -0
- data/test/test_various.rb +8 -3
- data/test/test_wab.rb +2 -0
- data/test/tests.rb +9 -0
- data/test/tests_mimic.rb +9 -0
- data/test/tests_mimic_addition.rb +9 -0
- metadata +13 -116
data/ext/oj/usual.h
ADDED
@@ -0,0 +1,68 @@
|
|
1
|
+
// Copyright (c) 2022, Peter Ohler, All rights reserved.
|
2
|
+
|
3
|
+
#include <ruby.h>
|
4
|
+
#include <stdbool.h>
|
5
|
+
#include <stdint.h>
|
6
|
+
|
7
|
+
struct _cache;
|
8
|
+
struct _ojParser;
|
9
|
+
|
10
|
+
// Used to mark the start of each Hash, Array, or Object. The members point at
|
11
|
+
// positions of the start in the value stack and if not an Array into the key
|
12
|
+
// stack.
|
13
|
+
typedef struct _col {
|
14
|
+
long vi; // value stack index
|
15
|
+
long ki; // key stack index if an hash else -1 for an array
|
16
|
+
} * Col;
|
17
|
+
|
18
|
+
typedef union _key {
|
19
|
+
struct {
|
20
|
+
int16_t len;
|
21
|
+
char buf[30];
|
22
|
+
};
|
23
|
+
struct {
|
24
|
+
int16_t xlen; // should be the same as len
|
25
|
+
char * key;
|
26
|
+
};
|
27
|
+
} * Key;
|
28
|
+
|
29
|
+
#define MISS_AUTO 'A'
|
30
|
+
#define MISS_RAISE 'R'
|
31
|
+
#define MISS_IGNORE 'I'
|
32
|
+
|
33
|
+
typedef struct _usual {
|
34
|
+
VALUE *vhead;
|
35
|
+
VALUE *vtail;
|
36
|
+
VALUE *vend;
|
37
|
+
|
38
|
+
Col chead;
|
39
|
+
Col ctail;
|
40
|
+
Col cend;
|
41
|
+
|
42
|
+
Key khead;
|
43
|
+
Key ktail;
|
44
|
+
Key kend;
|
45
|
+
|
46
|
+
VALUE (*get_key)(ojParser p, Key kp);
|
47
|
+
struct _cache *key_cache; // same as str_cache or sym_cache
|
48
|
+
struct _cache *str_cache;
|
49
|
+
struct _cache *sym_cache;
|
50
|
+
struct _cache *class_cache;
|
51
|
+
struct _cache *attr_cache;
|
52
|
+
|
53
|
+
VALUE array_class;
|
54
|
+
VALUE hash_class;
|
55
|
+
|
56
|
+
char * create_id;
|
57
|
+
uint8_t create_id_len;
|
58
|
+
uint8_t cache_str;
|
59
|
+
uint8_t cache_xrate;
|
60
|
+
uint8_t miss_class;
|
61
|
+
bool cache_keys;
|
62
|
+
bool ignore_json_create;
|
63
|
+
} * Usual;
|
64
|
+
|
65
|
+
// Initialize the parser with the usual delegate. If the usual delegate is
|
66
|
+
// wrapped then this function is called first and then the parser functions
|
67
|
+
// can be replaced.
|
68
|
+
extern void oj_init_usual(struct _ojParser *p, Usual d);
|
data/ext/oj/val_stack.c
CHANGED
data/ext/oj/validate.c
CHANGED
@@ -2,50 +2,45 @@
|
|
2
2
|
|
3
3
|
#include "parser.h"
|
4
4
|
|
5
|
-
static void
|
6
|
-
noop(ojParser p) {
|
5
|
+
static void noop(ojParser p) {
|
7
6
|
}
|
8
7
|
|
9
|
-
static VALUE
|
10
|
-
option(ojParser p, const char *key, VALUE value) {
|
8
|
+
static VALUE option(ojParser p, const char *key, VALUE value) {
|
11
9
|
rb_raise(rb_eArgError, "%s is not an option for the validate delegate", key);
|
12
10
|
return Qnil;
|
13
11
|
}
|
14
12
|
|
15
|
-
static VALUE
|
16
|
-
result(ojParser p) {
|
13
|
+
static VALUE result(ojParser p) {
|
17
14
|
return Qnil;
|
18
15
|
}
|
19
16
|
|
20
|
-
static void
|
21
|
-
dfree(ojParser p) {
|
17
|
+
static void dfree(ojParser p) {
|
22
18
|
}
|
23
19
|
|
24
|
-
static void
|
25
|
-
mark(ojParser p) {
|
20
|
+
static void mark(ojParser p) {
|
26
21
|
}
|
27
22
|
|
28
23
|
void oj_set_parser_validator(ojParser p) {
|
29
|
-
p->
|
30
|
-
Funcs end = p->funcs + 3;
|
24
|
+
Funcs end = p->funcs + 3;
|
31
25
|
Funcs f;
|
26
|
+
p->ctx = NULL;
|
32
27
|
|
33
28
|
for (f = p->funcs; f < end; f++) {
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
29
|
+
f->add_null = noop;
|
30
|
+
f->add_true = noop;
|
31
|
+
f->add_false = noop;
|
32
|
+
f->add_int = noop;
|
33
|
+
f->add_float = noop;
|
34
|
+
f->add_big = noop;
|
35
|
+
f->add_str = noop;
|
36
|
+
f->open_array = noop;
|
37
|
+
f->close_array = noop;
|
38
|
+
f->open_object = noop;
|
39
|
+
f->close_object = noop;
|
45
40
|
}
|
46
41
|
p->option = option;
|
47
42
|
p->result = result;
|
48
|
-
p->free
|
49
|
-
p->mark
|
50
|
-
p->start
|
43
|
+
p->free = dfree;
|
44
|
+
p->mark = mark;
|
45
|
+
p->start = noop;
|
51
46
|
}
|
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,9 +124,9 @@ 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
131
|
oj_dump_wab_val(RARRAY_AREF(a, i), d2, out);
|
132
132
|
if (i < cnt) {
|
@@ -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 =
|
205
|
-
nsec =
|
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
|
3
|
-
# JSON. The Oj::Saj handler class
|
4
|
-
# Oj::Saj key_parse() method
|
5
|
-
# is
|
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
data/pages/Compatibility.md
CHANGED
@@ -2,7 +2,7 @@
|
|
2
2
|
|
3
3
|
**Ruby**
|
4
4
|
|
5
|
-
Oj is compatible with Ruby 2.
|
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/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.
|
@@ -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
|