json 3.0.1 → 3.1.0
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/CHANGES.md +13 -0
- data/ext/json/ext/fbuffer/fbuffer.h +4 -0
- data/ext/json/ext/generator/generator.c +156 -44
- data/ext/json/ext/json.h +6 -0
- data/ext/json/ext/parser/parser.c +59 -54
- data/lib/json/common.rb +26 -26
- data/lib/json/ext/generator/state.rb +2 -6
- data/lib/json/ext.rb +129 -3
- data/lib/json/truffle_ruby/generator.rb +110 -30
- data/lib/json/version.rb +1 -1
- data/lib/json.rb +45 -37
- metadata +2 -2
|
@@ -20,9 +20,9 @@ static int utf8_encindex;
|
|
|
20
20
|
#ifndef HAVE_RB_HASH_BULK_INSERT
|
|
21
21
|
// For TruffleRuby
|
|
22
22
|
static void
|
|
23
|
-
rb_hash_bulk_insert(
|
|
23
|
+
rb_hash_bulk_insert(rb_len_t count, const VALUE *pairs, VALUE hash)
|
|
24
24
|
{
|
|
25
|
-
|
|
25
|
+
rb_len_t index = 0;
|
|
26
26
|
while (index < count) {
|
|
27
27
|
VALUE name = pairs[index++];
|
|
28
28
|
VALUE value = pairs[index++];
|
|
@@ -79,7 +79,7 @@ static rb_encoding *enc_utf8;
|
|
|
79
79
|
|
|
80
80
|
#define JSON_RVALUE_CACHE_MAX_ENTRY_LENGTH 55
|
|
81
81
|
|
|
82
|
-
static inline VALUE build_interned_string(const char *str, const
|
|
82
|
+
static inline VALUE build_interned_string(const char *str, const rb_len_t length)
|
|
83
83
|
{
|
|
84
84
|
# ifdef HAVE_RB_ENC_INTERNED_STR
|
|
85
85
|
return rb_enc_interned_str(str, length, enc_utf8);
|
|
@@ -89,7 +89,7 @@ static inline VALUE build_interned_string(const char *str, const long length)
|
|
|
89
89
|
# endif
|
|
90
90
|
}
|
|
91
91
|
|
|
92
|
-
static inline VALUE build_symbol(const char *str, const
|
|
92
|
+
static inline VALUE build_symbol(const char *str, const rb_len_t length)
|
|
93
93
|
{
|
|
94
94
|
return rb_str_intern(build_interned_string(str, length));
|
|
95
95
|
}
|
|
@@ -138,7 +138,7 @@ ALWAYS_INLINE(static) int rstring_cache_memcmp(const char *str, const char *rptr
|
|
|
138
138
|
ALWAYS_INLINE(static) int rstring_cache_cmp(const char *str, const long length, VALUE rstring)
|
|
139
139
|
{
|
|
140
140
|
const char *rstring_ptr;
|
|
141
|
-
|
|
141
|
+
rb_len_t rstring_length;
|
|
142
142
|
|
|
143
143
|
RSTRING_GETMEM(rstring, rstring_ptr, rstring_length);
|
|
144
144
|
|
|
@@ -323,14 +323,17 @@ static const rb_data_type_t JSON_Parser_rvalue_stack_type = {
|
|
|
323
323
|
static rvalue_stack *rvalue_stack_spill(rvalue_stack *old_stack, VALUE *handle, rvalue_stack **stack_ref)
|
|
324
324
|
{
|
|
325
325
|
rvalue_stack *stack;
|
|
326
|
-
|
|
327
|
-
*stack_ref = stack;
|
|
326
|
+
VALUE stack_handle = TypedData_Make_Struct(0, rvalue_stack, &JSON_Parser_rvalue_stack_type, stack);
|
|
328
327
|
MEMCPY(stack, old_stack, rvalue_stack, 1);
|
|
329
328
|
|
|
330
329
|
stack->capa = old_stack->capa << 1;
|
|
331
330
|
stack->ptr = ALLOC_N(VALUE, stack->capa);
|
|
332
331
|
stack->type = RVALUE_STACK_HEAP_ALLOCATED;
|
|
333
332
|
MEMCPY(stack->ptr, old_stack->ptr, VALUE, old_stack->head);
|
|
333
|
+
|
|
334
|
+
*handle = stack_handle;
|
|
335
|
+
*stack_ref = stack;
|
|
336
|
+
|
|
334
337
|
return stack;
|
|
335
338
|
}
|
|
336
339
|
|
|
@@ -580,7 +583,7 @@ static inline char peek(JSON_ParserState *state)
|
|
|
580
583
|
return *state->cursor;
|
|
581
584
|
}
|
|
582
585
|
|
|
583
|
-
static void cursor_position(JSON_ParserState *state, long *line_out,
|
|
586
|
+
static void cursor_position(JSON_ParserState *state, long *line_out, rb_len_t *column_out)
|
|
584
587
|
{
|
|
585
588
|
JSON_ASSERT(!state->parser);
|
|
586
589
|
JSON_ASSERT(state->cursor);
|
|
@@ -592,19 +595,15 @@ static void cursor_position(JSON_ParserState *state, long *line_out, long *colum
|
|
|
592
595
|
}
|
|
593
596
|
|
|
594
597
|
const char *cursor = state->cursor;
|
|
595
|
-
long column = 0;
|
|
596
598
|
long line = 1;
|
|
597
599
|
|
|
598
|
-
while (cursor
|
|
599
|
-
|
|
600
|
-
line++;
|
|
601
|
-
break;
|
|
602
|
-
}
|
|
603
|
-
column++;
|
|
600
|
+
while (cursor > state->start && cursor[-1] != '\n') {
|
|
601
|
+
cursor--;
|
|
604
602
|
}
|
|
603
|
+
rb_len_t column = rb_enc_strlen(cursor, state->cursor, enc_utf8) + 1;
|
|
605
604
|
|
|
606
|
-
while (cursor
|
|
607
|
-
if (
|
|
605
|
+
while (cursor > state->start) {
|
|
606
|
+
if (*--cursor == '\n') {
|
|
608
607
|
line++;
|
|
609
608
|
}
|
|
610
609
|
}
|
|
@@ -631,17 +630,15 @@ static VALUE build_parse_error_message(const char *format, JSON_ParserState *sta
|
|
|
631
630
|
}
|
|
632
631
|
|
|
633
632
|
if (len) {
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
}
|
|
640
|
-
|
|
641
|
-
if (buffer[len] >= 0xC0) { // multibyte character start
|
|
642
|
-
len--;
|
|
633
|
+
if (len == PARSE_ERROR_FRAGMENT_LEN) {
|
|
634
|
+
// Only trim when the byte limit splits a multibyte character.
|
|
635
|
+
while (len && (unsigned char)ptr[len] >= 0x80 && (unsigned char)ptr[len] < 0xC0) {
|
|
636
|
+
len--;
|
|
637
|
+
}
|
|
643
638
|
}
|
|
644
639
|
|
|
640
|
+
buffer[0] = '\'';
|
|
641
|
+
MEMCPY(buffer + 1, ptr, char, len);
|
|
645
642
|
buffer[len + 1] = '\'';
|
|
646
643
|
buffer[len + 2] = '\0';
|
|
647
644
|
ptr = (const char *)buffer;
|
|
@@ -681,11 +678,11 @@ static VALUE json_path_new(JSON_ParserState *state, VALUE duplicate_key)
|
|
|
681
678
|
return path;
|
|
682
679
|
}
|
|
683
680
|
|
|
684
|
-
static VALUE parse_error_new(JSON_ParserState *state, VALUE message, long line,
|
|
681
|
+
static VALUE parse_error_new(JSON_ParserState *state, VALUE message, long line, rb_len_t column, bool eos)
|
|
685
682
|
{
|
|
686
683
|
VALUE exc = rb_exc_new_str(eParserError, message);
|
|
687
684
|
rb_ivar_set(exc, i_at_line, LONG2NUM(line));
|
|
688
|
-
rb_ivar_set(exc, i_at_column,
|
|
685
|
+
rb_ivar_set(exc, i_at_column, LEN2NUM(column));
|
|
689
686
|
rb_ivar_set(exc, i_at_json_path, json_path_new(state, Qundef));
|
|
690
687
|
return exc;
|
|
691
688
|
}
|
|
@@ -703,9 +700,10 @@ NORETURN(static) void raise_parse_error(const char *format, JSON_ParserState *st
|
|
|
703
700
|
}
|
|
704
701
|
} else {
|
|
705
702
|
VALUE message = build_parse_error_message(format, state);
|
|
706
|
-
long line
|
|
703
|
+
long line;
|
|
704
|
+
rb_len_t column;
|
|
707
705
|
cursor_position(state, &line, &column);
|
|
708
|
-
rb_str_catf(message, " at line %ld column %
|
|
706
|
+
rb_str_catf(message, " at line %ld column %"PRIdLEN, line, column);
|
|
709
707
|
rb_exc_raise(parse_error_new(state, message, line, column, eos));
|
|
710
708
|
}
|
|
711
709
|
}
|
|
@@ -758,7 +756,7 @@ static const signed char digit_values[256] = {
|
|
|
758
756
|
static uint32_t unescape_unicode(JSON_ParserState *state, const char *sp, const char *spe)
|
|
759
757
|
{
|
|
760
758
|
if (RB_UNLIKELY(sp > spe - 4)) {
|
|
761
|
-
|
|
759
|
+
raise_syntax_error_at("incomplete unicode character escape sequence at %s", state, sp - 2);
|
|
762
760
|
}
|
|
763
761
|
|
|
764
762
|
const unsigned char *p = (const unsigned char *)sp;
|
|
@@ -895,12 +893,12 @@ static inline VALUE build_string(const char *start, const char *end, bool intern
|
|
|
895
893
|
VALUE result;
|
|
896
894
|
# ifdef HAVE_RB_ENC_INTERNED_STR
|
|
897
895
|
if (intern) {
|
|
898
|
-
result = rb_enc_interned_str(start, (
|
|
896
|
+
result = rb_enc_interned_str(start, (rb_len_t)(end - start), enc_utf8);
|
|
899
897
|
} else {
|
|
900
|
-
result = rb_utf8_str_new(start, (
|
|
898
|
+
result = rb_utf8_str_new(start, (rb_len_t)(end - start));
|
|
901
899
|
}
|
|
902
900
|
# else
|
|
903
|
-
result = rb_utf8_str_new(start, (
|
|
901
|
+
result = rb_utf8_str_new(start, (rb_len_t)(end - start));
|
|
904
902
|
if (intern) {
|
|
905
903
|
result = rb_funcall(rb_str_freeze(result), i_uminus, 0);
|
|
906
904
|
}
|
|
@@ -1051,13 +1049,13 @@ NOINLINE(static) VALUE json_string_unescape(JSON_ParserState *state, JSON_Parser
|
|
|
1051
1049
|
p = ++pe;
|
|
1052
1050
|
break;
|
|
1053
1051
|
}
|
|
1054
|
-
case 0:
|
|
1055
|
-
return Qundef;
|
|
1056
1052
|
default:
|
|
1057
1053
|
if ((unsigned char)*pe < 0x20) {
|
|
1058
1054
|
if (!config->allow_control_characters) {
|
|
1059
1055
|
if (*pe == '\n') {
|
|
1060
1056
|
raise_syntax_error_at("Invalid unescaped newline character (\\n) in string: %s", state, pe - 1);
|
|
1057
|
+
} else if (*pe == '\0') {
|
|
1058
|
+
raise_syntax_error_at("Invalid unescaped NULL character (\\0) in string: %s", state, pe - 1);
|
|
1061
1059
|
}
|
|
1062
1060
|
raise_syntax_error_at("invalid ASCII control character in string: %s", state, pe - 1);
|
|
1063
1061
|
}
|
|
@@ -1094,7 +1092,7 @@ NOINLINE(static) VALUE json_string_unescape(JSON_ParserState *state, JSON_Parser
|
|
|
1094
1092
|
|
|
1095
1093
|
typedef VALUE (*json_number_decode_func_t)(const char *ptr);
|
|
1096
1094
|
|
|
1097
|
-
static inline VALUE json_decode_large_number(const char *start,
|
|
1095
|
+
static inline VALUE json_decode_large_number(const char *start, rb_len_t len, json_number_decode_func_t func)
|
|
1098
1096
|
{
|
|
1099
1097
|
if (RB_LIKELY(len < MAX_NUMBER_STACK_BUFFER)) {
|
|
1100
1098
|
char buffer[MAX_NUMBER_STACK_BUFFER];
|
|
@@ -1117,7 +1115,7 @@ static VALUE json_decode_inum(const char *buffer)
|
|
|
1117
1115
|
return rb_cstr2inum(buffer, 10);
|
|
1118
1116
|
}
|
|
1119
1117
|
|
|
1120
|
-
NOINLINE(static) VALUE json_decode_large_integer(const char *start,
|
|
1118
|
+
NOINLINE(static) VALUE json_decode_large_integer(const char *start, rb_len_t len)
|
|
1121
1119
|
{
|
|
1122
1120
|
return json_decode_large_number(start, len, json_decode_inum);
|
|
1123
1121
|
}
|
|
@@ -1158,7 +1156,7 @@ static VALUE json_decode_dnum(const char *buffer)
|
|
|
1158
1156
|
return DBL2NUM(rb_cstr_to_dbl(buffer, 1));
|
|
1159
1157
|
}
|
|
1160
1158
|
|
|
1161
|
-
NOINLINE(static) VALUE json_decode_large_float(const char *start,
|
|
1159
|
+
NOINLINE(static) VALUE json_decode_large_float(const char *start, rb_len_t len)
|
|
1162
1160
|
{
|
|
1163
1161
|
return json_decode_large_number(start, len, json_decode_dnum);
|
|
1164
1162
|
}
|
|
@@ -1174,11 +1172,11 @@ static inline VALUE json_decode_float(JSON_ParserConfig *config, uint64_t mantis
|
|
|
1174
1172
|
return rb_funcallv(config->decimal_class, config->decimal_method_id, 1, &text);
|
|
1175
1173
|
}
|
|
1176
1174
|
|
|
1177
|
-
if (RB_UNLIKELY(exponent
|
|
1175
|
+
if (RB_UNLIKELY(exponent >= (int64_t)INT32_MAX)) {
|
|
1178
1176
|
return negative ? CMinusInfinity : CInfinity;
|
|
1179
1177
|
}
|
|
1180
1178
|
|
|
1181
|
-
if (RB_UNLIKELY(exponent
|
|
1179
|
+
if (RB_UNLIKELY(exponent <= INT32_MIN)) {
|
|
1182
1180
|
return rb_float_new(negative ? -0.0 : 0.0);
|
|
1183
1181
|
}
|
|
1184
1182
|
|
|
@@ -1237,9 +1235,10 @@ NORETURN(static) void raise_duplicate_key_error(JSON_ParserState *state, VALUE d
|
|
|
1237
1235
|
if (state->parser) { // line and columns can't be accurate in resumable
|
|
1238
1236
|
exc = parse_error_new(state, message, 0, 0, false);
|
|
1239
1237
|
} else {
|
|
1240
|
-
long line
|
|
1238
|
+
long line;
|
|
1239
|
+
rb_len_t column;
|
|
1241
1240
|
cursor_position(state, &line, &column);
|
|
1242
|
-
rb_str_catf(message, " at line %ld column %
|
|
1241
|
+
rb_str_catf(message, " at line %ld column %"PRIdLEN, line, column);
|
|
1243
1242
|
exc = parse_error_new(state, message, line, column, false);
|
|
1244
1243
|
}
|
|
1245
1244
|
rb_ivar_set(exc, i_at_json_path, json_path_new(state, duplicate_key));
|
|
@@ -1518,7 +1517,7 @@ static inline VALUE json_parse_number(JSON_ParserState *state, JSON_ParserConfig
|
|
|
1518
1517
|
}
|
|
1519
1518
|
|
|
1520
1519
|
if (RB_UNLIKELY(exponent_digits >= 20 || abs_exponent > (uint64_t)INT64_MAX)) {
|
|
1521
|
-
exponent = negative_exponent ?
|
|
1520
|
+
exponent = negative_exponent ? INT32_MIN : INT32_MAX;
|
|
1522
1521
|
} else {
|
|
1523
1522
|
exponent = negative_exponent ? -(int64_t)abs_exponent : (int64_t)abs_exponent;
|
|
1524
1523
|
}
|
|
@@ -1815,7 +1814,7 @@ ALWAYS_INLINE(static) bool json_parse_any(JSON_ParserState *state, JSON_ParserCo
|
|
|
1815
1814
|
if (RB_LIKELY(peek(state) == '"')) {
|
|
1816
1815
|
VALUE string = json_parse_string(state, config, true);
|
|
1817
1816
|
if (UNDEF_P(string)) {
|
|
1818
|
-
if (resumable) {
|
|
1817
|
+
if (resumable && eos(state)) {
|
|
1819
1818
|
state->cursor = start;
|
|
1820
1819
|
return false;
|
|
1821
1820
|
} else {
|
|
@@ -2021,7 +2020,15 @@ static int parser_config_init_i(VALUE key, VALUE val, VALUE data)
|
|
|
2021
2020
|
else if (key == sym_allow_invalid_escape) { config->allow_invalid_escape = RTEST(val); }
|
|
2022
2021
|
else if (key == sym_symbolize_names) { config->symbolize_names = RTEST(val); }
|
|
2023
2022
|
else if (key == sym_freeze) { config->freeze = RTEST(val); }
|
|
2024
|
-
else if (key == sym_on_load) {
|
|
2023
|
+
else if (key == sym_on_load) {
|
|
2024
|
+
if (RTEST(val) && !rb_obj_is_proc(val)) {
|
|
2025
|
+
val = rb_check_funcall(val, rb_intern("to_proc"), 0, NULL);
|
|
2026
|
+
if (val == Qundef || !rb_obj_is_proc(val)) {
|
|
2027
|
+
rb_raise(rb_eTypeError, "on_load must be a Proc");
|
|
2028
|
+
}
|
|
2029
|
+
}
|
|
2030
|
+
parser_config_wb_write(self, &config->on_load_proc, RTEST(val) ? val : Qfalse);
|
|
2031
|
+
}
|
|
2025
2032
|
else if (key == sym_allow_duplicate_key) { config->allow_duplicate_key = RTEST(val); }
|
|
2026
2033
|
else if (key == sym_decimal_class) {
|
|
2027
2034
|
if (RTEST(val)) {
|
|
@@ -2041,8 +2048,8 @@ static int parser_config_init_i(VALUE key, VALUE val, VALUE data)
|
|
|
2041
2048
|
parser_config_wb_write(self, &config->decimal_class, rb_path_to_class(mod_path));
|
|
2042
2049
|
|
|
2043
2050
|
const char *method_name_beg = last_colon + 1;
|
|
2044
|
-
|
|
2045
|
-
|
|
2051
|
+
rb_len_t before_len = method_name_beg - name_cstr;
|
|
2052
|
+
rb_len_t len = RSTRING_LEN(name) - before_len;
|
|
2046
2053
|
VALUE method_name = rb_str_substr(name, before_len, len);
|
|
2047
2054
|
config->decimal_method_id = SYM2ID(rb_str_intern(method_name));
|
|
2048
2055
|
} else {
|
|
@@ -2109,9 +2116,7 @@ static VALUE cParser_parse(JSON_ParserConfig *config, VALUE src)
|
|
|
2109
2116
|
// Ensure the string isn't mutated under us.
|
|
2110
2117
|
// The classic API to use is `rb_str_locktmp`, but then we'd
|
|
2111
2118
|
// need to use `rb_protect` to make sure we always unlock.
|
|
2112
|
-
|
|
2113
|
-
Vsource = rb_str_new_frozen(Vsource);
|
|
2114
|
-
}
|
|
2119
|
+
Vsource = rb_str_new_frozen(Vsource);
|
|
2115
2120
|
|
|
2116
2121
|
VALUE rvalue_stack_buffer[RVALUE_STACK_INITIAL_CAPA];
|
|
2117
2122
|
rvalue_stack value_stack = {
|
|
@@ -2135,7 +2140,7 @@ static VALUE cParser_parse(JSON_ParserConfig *config, VALUE src)
|
|
|
2135
2140
|
.head = 1,
|
|
2136
2141
|
};
|
|
2137
2142
|
|
|
2138
|
-
|
|
2143
|
+
rb_len_t len;
|
|
2139
2144
|
const char *start;
|
|
2140
2145
|
|
|
2141
2146
|
RSTRING_GETMEM(Vsource, start, len);
|
|
@@ -2454,7 +2459,7 @@ static VALUE cResumableParser_feed(VALUE self, VALUE str)
|
|
|
2454
2459
|
rb_str_append(parser->buffer, str);
|
|
2455
2460
|
}
|
|
2456
2461
|
|
|
2457
|
-
|
|
2462
|
+
rb_len_t len;
|
|
2458
2463
|
const char *start;
|
|
2459
2464
|
RSTRING_GETMEM(parser->buffer, start, len);
|
|
2460
2465
|
parser->state.start = start;
|
|
@@ -2760,7 +2765,7 @@ static VALUE cResumableParser_rest(VALUE self)
|
|
|
2760
2765
|
|
|
2761
2766
|
size_t offset = parser->state.cursor - parser->state.start;
|
|
2762
2767
|
const char *ptr;
|
|
2763
|
-
|
|
2768
|
+
rb_len_t len;
|
|
2764
2769
|
RSTRING_GETMEM(parser->buffer, ptr, len);
|
|
2765
2770
|
return rb_utf8_str_new(ptr + offset, len - offset);
|
|
2766
2771
|
}
|
data/lib/json/common.rb
CHANGED
|
@@ -80,14 +80,6 @@ module JSON
|
|
|
80
80
|
def generator=(generator) # :nodoc:
|
|
81
81
|
old, $VERBOSE = $VERBOSE, nil
|
|
82
82
|
|
|
83
|
-
# The default proc used when the +sort_keys+ generation option is +true+.
|
|
84
|
-
# It returns a new hash with the entries sorted by their keys.
|
|
85
|
-
sort_keys_proc = ->(hash) { hash.sort.to_h }
|
|
86
|
-
if defined?(::Ractor) && Ractor.respond_to?(:shareable_lambda)
|
|
87
|
-
sort_keys_proc = Ractor.shareable_lambda(&sort_keys_proc)
|
|
88
|
-
end
|
|
89
|
-
generator::State.default_sort_keys_proc = sort_keys_proc
|
|
90
|
-
|
|
91
83
|
@generator = generator
|
|
92
84
|
if generator.const_defined?(:GeneratorMethods)
|
|
93
85
|
generator_methods = generator::GeneratorMethods
|
|
@@ -143,11 +135,11 @@ module JSON
|
|
|
143
135
|
# This exception is raised if a parser error occurs.
|
|
144
136
|
class ParserError < JSONError
|
|
145
137
|
# Line number where the parser encountered an error.
|
|
146
|
-
# Is
|
|
138
|
+
# Is +nil+ when raised by JSON::ResumableParser.
|
|
147
139
|
attr_reader :line
|
|
148
140
|
|
|
149
|
-
#
|
|
150
|
-
# Is
|
|
141
|
+
# One-based column number where the parser encountered an error, counted in Unicode codepoints.
|
|
142
|
+
# Is +nil+ when raised by JSON::ResumableParser.
|
|
151
143
|
attr_reader :column
|
|
152
144
|
|
|
153
145
|
# Returns a best effort JSONPath string representing where in the document
|
|
@@ -179,7 +171,10 @@ module JSON
|
|
|
179
171
|
when Integer
|
|
180
172
|
"[#{segment}]"
|
|
181
173
|
when String, Symbol
|
|
182
|
-
if segment.
|
|
174
|
+
if !segment.valid_encoding?
|
|
175
|
+
segment = segment.b.gsub(/["\\]/n, { '"' => '\\"', '\\' => '\\\\' }).force_encoding(Encoding::UTF_8)
|
|
176
|
+
%{["#{segment}"]}
|
|
177
|
+
elsif segment.match?(/\A[a-zA-Z\$\_][a-zA-Z\$\_0-9]*\z/)
|
|
183
178
|
".#{segment}"
|
|
184
179
|
else
|
|
185
180
|
segment = segment.to_s.gsub(/["\\]/, { '"' => '\\"', '\\' => '\\\\' })
|
|
@@ -232,7 +227,7 @@ module JSON
|
|
|
232
227
|
Fragment = Struct.new(:json) do
|
|
233
228
|
def initialize(json)
|
|
234
229
|
unless string = String.try_convert(json)
|
|
235
|
-
raise TypeError, "
|
|
230
|
+
raise TypeError, "no implicit conversion of #{json.class} into String"
|
|
236
231
|
end
|
|
237
232
|
|
|
238
233
|
super(string)
|
|
@@ -290,7 +285,7 @@ module JSON
|
|
|
290
285
|
# ---
|
|
291
286
|
#
|
|
292
287
|
# Raises an exception if +source+ is not valid JSON:
|
|
293
|
-
# # Raises JSON::ParserError unexpected character: 'invalid' at line 1 column 1
|
|
288
|
+
# # Raises JSON::ParserError (unexpected character: 'invalid' at line 1 column 1):
|
|
294
289
|
# JSON.parse('invalid')
|
|
295
290
|
#
|
|
296
291
|
def parse(source, on_load: nil, object_class: nil, array_class: nil, **options)
|
|
@@ -324,8 +319,8 @@ module JSON
|
|
|
324
319
|
# parse(File.read(path), **)
|
|
325
320
|
#
|
|
326
321
|
# See method #parse.
|
|
327
|
-
def load_file(filespec,
|
|
328
|
-
parse(File.read(filespec, encoding: Encoding::UTF_8),
|
|
322
|
+
def load_file(filespec, **options)
|
|
323
|
+
parse(File.read(filespec, encoding: Encoding::UTF_8), **options)
|
|
329
324
|
end
|
|
330
325
|
|
|
331
326
|
# :call-seq:
|
|
@@ -335,8 +330,8 @@ module JSON
|
|
|
335
330
|
# JSON.parse!(File.read(path), **)
|
|
336
331
|
#
|
|
337
332
|
# See method #parse!
|
|
338
|
-
def load_file!(filespec,
|
|
339
|
-
parse!(File.read(filespec, encoding: Encoding::UTF_8),
|
|
333
|
+
def load_file!(filespec, **options)
|
|
334
|
+
parse!(File.read(filespec, encoding: Encoding::UTF_8), **options)
|
|
340
335
|
end
|
|
341
336
|
|
|
342
337
|
# :call-seq:
|
|
@@ -372,7 +367,7 @@ module JSON
|
|
|
372
367
|
#
|
|
373
368
|
# Raises an exception if +obj+ contains circular references:
|
|
374
369
|
# a = []; b = []; a.push(b); b.push(a)
|
|
375
|
-
# # Raises JSON::NestingError (nesting of 100 is too deep):
|
|
370
|
+
# # Raises JSON::NestingError (nesting of 100 is too deep. Did you try to serialize objects with circular references?):
|
|
376
371
|
# JSON.generate(a)
|
|
377
372
|
#
|
|
378
373
|
def generate(obj, opts = nil)
|
|
@@ -707,10 +702,10 @@ module JSON
|
|
|
707
702
|
unless source.is_a?(String)
|
|
708
703
|
if source.respond_to? :to_str
|
|
709
704
|
source = source.to_str
|
|
705
|
+
elsif source.respond_to? :read
|
|
706
|
+
source = source.read
|
|
710
707
|
elsif source.respond_to? :to_io
|
|
711
708
|
source = source.to_io.read
|
|
712
|
-
elsif source.respond_to?(:read)
|
|
713
|
-
source = source.read
|
|
714
709
|
end
|
|
715
710
|
end
|
|
716
711
|
|
|
@@ -771,7 +766,7 @@ module JSON
|
|
|
771
766
|
end
|
|
772
767
|
|
|
773
768
|
opts = {
|
|
774
|
-
allow_nan:
|
|
769
|
+
allow_nan: !(kwargs && kwargs[:rfc8785]),
|
|
775
770
|
}
|
|
776
771
|
opts[:max_nesting] = _deprecated_limit if _deprecated_limit
|
|
777
772
|
opts.merge!(kwargs) if kwargs
|
|
@@ -812,9 +807,9 @@ module JSON
|
|
|
812
807
|
private_constant :EXCLUDED_GENERATOR_OPTIONS
|
|
813
808
|
|
|
814
809
|
# :call-seq:
|
|
815
|
-
# JSON.new(options
|
|
810
|
+
# JSON::Coder.new(**options, &block)
|
|
816
811
|
#
|
|
817
|
-
#
|
|
812
|
+
# Keyword arguments +options+, if given, are options for both parsing and generating.
|
|
818
813
|
# See {Parsing Options}[rdoc-ref:JSON@Parsing+Options],
|
|
819
814
|
# and {Generating Options}[rdoc-ref:JSON@Generating+Options].
|
|
820
815
|
#
|
|
@@ -835,7 +830,12 @@ module JSON
|
|
|
835
830
|
#
|
|
836
831
|
# puts MyApp::API_JSON_CODER.dump(Time.now.utc) # => "2025-01-21T08:41:44.286Z"
|
|
837
832
|
#
|
|
838
|
-
|
|
833
|
+
# The conversion callback can also be passed as +as_json:+. If both are given, the block takes precedence.
|
|
834
|
+
# Passing +false+ or +nil+ for +strict:+ raises ArgumentError.
|
|
835
|
+
#
|
|
836
|
+
def initialize(object_class: nil, array_class: nil, on_load: nil, strict: true, **options, &as_json)
|
|
837
|
+
raise ArgumentError, "JSON::Coder requires strict mode" unless strict
|
|
838
|
+
|
|
839
839
|
if object_class || array_class
|
|
840
840
|
on_load = ParserOptions.on_load(on_load, object_class, array_class)
|
|
841
841
|
end
|
|
@@ -847,7 +847,7 @@ module JSON
|
|
|
847
847
|
@state = State.new(
|
|
848
848
|
**generator_options,
|
|
849
849
|
strict: true,
|
|
850
|
-
as_json: as_json,
|
|
850
|
+
as_json: as_json || generator_options[:as_json],
|
|
851
851
|
).freeze
|
|
852
852
|
end
|
|
853
853
|
|
|
@@ -54,7 +54,8 @@ module JSON
|
|
|
54
54
|
strict: strict?,
|
|
55
55
|
depth: depth,
|
|
56
56
|
buffer_initial_length: buffer_initial_length,
|
|
57
|
-
sort_keys: sort_keys
|
|
57
|
+
sort_keys: sort_keys,
|
|
58
|
+
rfc8785: rfc8785?,
|
|
58
59
|
}
|
|
59
60
|
|
|
60
61
|
allow_duplicate_key = allow_duplicate_key?
|
|
@@ -62,11 +63,6 @@ module JSON
|
|
|
62
63
|
result[:allow_duplicate_key] = allow_duplicate_key
|
|
63
64
|
end
|
|
64
65
|
|
|
65
|
-
instance_variables.each do |iv|
|
|
66
|
-
iv = iv.to_s[1..-1]
|
|
67
|
-
result[iv.to_sym] = self[iv]
|
|
68
|
-
end
|
|
69
|
-
|
|
70
66
|
result
|
|
71
67
|
end
|
|
72
68
|
|
data/lib/json/ext.rb
CHANGED
|
@@ -28,17 +28,143 @@ module JSON
|
|
|
28
28
|
end
|
|
29
29
|
end
|
|
30
30
|
|
|
31
|
+
class << self
|
|
32
|
+
if defined?(::Ractor) && Ractor.respond_to?(:shareable_lambda)
|
|
33
|
+
def shareable_lambda(block) # :nodoc:
|
|
34
|
+
Ractor.shareable_lambda(&block)
|
|
35
|
+
end
|
|
36
|
+
else
|
|
37
|
+
def shareable_lambda(block) # :nodoc:
|
|
38
|
+
block
|
|
39
|
+
end
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
def valid_encoding?(str) # :nodoc:
|
|
43
|
+
str.ascii_only? || (str.encoding == Encoding::UTF_8 && str.valid_encoding?)
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
def coerce_key(key, as_json) # :nodoc:
|
|
47
|
+
as_json_called = false
|
|
48
|
+
|
|
49
|
+
unless String === key || Symbol === key
|
|
50
|
+
key = as_json.call(key, true)
|
|
51
|
+
as_json_called = true
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
if Symbol === key
|
|
55
|
+
key = key.to_s
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
unless String === key
|
|
59
|
+
raise GeneratorError.new("#{key.class} not allowed as object key in JSON", key)
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
unless valid_encoding?(key)
|
|
63
|
+
if as_json_called
|
|
64
|
+
raise GeneratorError.new("source sequence is illegal/malformed utf-8", key)
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
key = as_json.call(key, true)
|
|
68
|
+
unless String === key
|
|
69
|
+
raise GeneratorError.new("#{key.class} not allowed as object key in JSON", key)
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
unless Ext.valid_encoding?(key)
|
|
73
|
+
raise GeneratorError.new("source sequence is illegal/malformed utf-8", key)
|
|
74
|
+
end
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
key
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
def on_duplicate_key(hash, as_json)
|
|
81
|
+
set = {}
|
|
82
|
+
hash.each_key do |key|
|
|
83
|
+
key = coerce_key(key, as_json)
|
|
84
|
+
if set[key]
|
|
85
|
+
raise GeneratorError.new("detected duplicate key #{key.inspect}", key)
|
|
86
|
+
else
|
|
87
|
+
set[key] = true
|
|
88
|
+
end
|
|
89
|
+
end
|
|
90
|
+
end
|
|
91
|
+
end
|
|
92
|
+
|
|
31
93
|
require 'json/ext/parser'
|
|
32
94
|
Ext::Parser::Config = Ext::ParserConfig
|
|
33
95
|
JSON.parser = Ext::Parser
|
|
34
96
|
|
|
35
|
-
if RUBY_ENGINE == 'truffleruby'
|
|
97
|
+
generator = if RUBY_ENGINE == 'truffleruby'
|
|
36
98
|
require 'json/truffle_ruby/generator'
|
|
37
|
-
JSON
|
|
99
|
+
JSON::TruffleRuby::Generator
|
|
38
100
|
else
|
|
39
101
|
require 'json/ext/generator'
|
|
40
|
-
|
|
102
|
+
Generator
|
|
41
103
|
end
|
|
104
|
+
|
|
105
|
+
# The default proc used when the +sort_keys+ generation option is +true+.
|
|
106
|
+
# It returns a new hash with the entries sorted by their keys.
|
|
107
|
+
generator::State.default_sort_keys_proc = shareable_lambda(->(hash) {
|
|
108
|
+
hash.sort.to_h
|
|
109
|
+
})
|
|
110
|
+
|
|
111
|
+
# Directly lifted from Gregg Kellogg's json-canonicalization
|
|
112
|
+
generator::State.rfc8785_number_formatter_proc = shareable_lambda(->(num) {
|
|
113
|
+
if Integer === num
|
|
114
|
+
if num > Float::MAX || num < -Float::MAX
|
|
115
|
+
raise GeneratorError.new("Integer out of range for RFC 8785", num)
|
|
116
|
+
end
|
|
117
|
+
num = num.to_f
|
|
118
|
+
end
|
|
119
|
+
if num.zero?
|
|
120
|
+
"0"
|
|
121
|
+
else
|
|
122
|
+
if num < 0
|
|
123
|
+
num, sign = -num, '-'
|
|
124
|
+
end
|
|
125
|
+
native_rep = num.to_s
|
|
126
|
+
decimal, exponential = native_rep.split('e')
|
|
127
|
+
exp_val = exponential.to_i
|
|
128
|
+
exponential = exp_val > 0 ? ('+' + exp_val.to_s) : exp_val.to_s
|
|
129
|
+
|
|
130
|
+
integral, fractional = decimal.split('.')
|
|
131
|
+
fractional = fractional.sub(/0+$/, '') # Remove trailing zeros
|
|
132
|
+
|
|
133
|
+
if exp_val > 0 && exp_val < 21
|
|
134
|
+
while exp_val > 0
|
|
135
|
+
integral += fractional.to_s[0] || '0'
|
|
136
|
+
fractional = fractional.to_s[1..-1]
|
|
137
|
+
exp_val -= 1
|
|
138
|
+
end
|
|
139
|
+
exponential = nil
|
|
140
|
+
elsif exp_val == 0
|
|
141
|
+
exponential = nil
|
|
142
|
+
elsif exp_val < 0 && exp_val > -7
|
|
143
|
+
# Small numbers are shown as 0.etc with e-6 as lower limit
|
|
144
|
+
fractional, integral, exponential = integral + fractional.to_s, '0', nil
|
|
145
|
+
fractional = ("0" * (-exp_val - 1)) + fractional
|
|
146
|
+
end
|
|
147
|
+
|
|
148
|
+
fractional = nil if fractional.to_s.empty?
|
|
149
|
+
sign.to_s + integral + (fractional ? ".#{fractional}" : '') + (exponential ? "e#{exponential}" : '')
|
|
150
|
+
end
|
|
151
|
+
})
|
|
152
|
+
|
|
153
|
+
generator::State.rfc8785_sort_keys_proc = shareable_lambda(->(hash, as_json) {
|
|
154
|
+
converted = if as_json
|
|
155
|
+
hash.transform_keys { |k| Ext.coerce_key(k, as_json) }
|
|
156
|
+
else
|
|
157
|
+
hash.transform_keys(&:to_s)
|
|
158
|
+
end
|
|
159
|
+
|
|
160
|
+
if converted.size < hash.size
|
|
161
|
+
Ext.on_duplicate_key(hash, as_json)
|
|
162
|
+
end
|
|
163
|
+
|
|
164
|
+
converted.sort_by { |k,| k.encode(Encoding::UTF_16) }.to_h
|
|
165
|
+
})
|
|
166
|
+
|
|
167
|
+
JSON.generator = generator
|
|
42
168
|
end
|
|
43
169
|
|
|
44
170
|
if defined?(ResumableParser) # Not yet available on JRuby
|