json 3.0.0.rc1 → 3.0.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 +7 -2
- data/ext/json/ext/generator/generator.c +15 -11
- data/ext/json/ext/parser/parser.c +47 -9
- data/lib/json/common.rb +50 -1
- data/lib/json/version.rb +1 -1
- metadata +1 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: b4a457db7bd0eba155736808c796e81741e7ea72438768e183a7b7a795e8de4e
|
|
4
|
+
data.tar.gz: 61bb013e78224c5c91f620d3097986735411befd5139f33eaecb2c3c510ababe
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 6e0b4b4a8160235dcef28c7924ecda754a7ae792a714e6b5fbc5baa810fd358129c4340dd1056d31df823bf139f2b42bccb9c2d400c7282079d3dcf3b2f8c2c8
|
|
7
|
+
data.tar.gz: e67782447e1934dcada56b848982e6bb15708997b9eca220f7b631470734e0b14b2b227ac82612b03b8eb6b3d54b7686d4627a6d551d05b8252da40203f07087
|
data/CHANGES.md
CHANGED
|
@@ -2,6 +2,11 @@
|
|
|
2
2
|
|
|
3
3
|
### Unreleased
|
|
4
4
|
|
|
5
|
+
### 2026-09-07 (3.0.0)
|
|
6
|
+
|
|
7
|
+
* Add `JSON::ParserError#json_path` to locate parse errors in the document as a JSONPath-style string (e.g. `$.foo[0].bar`). For duplicate key errors it points at the duplicated key itself.
|
|
8
|
+
* Fix the parser to also reject lone trailing UTF-16 surrogates (`\uDCxx` with no leading partner), symmetric to the leading-surrogate case. The Java parser already rejected these; this closes the CRuby/JRuby parity gap.
|
|
9
|
+
|
|
5
10
|
### 2026-08-11 (3.0.0.rc1)
|
|
6
11
|
|
|
7
12
|
With the removal of the insecure `create_additions` option, `JSON.load` and `JSON.dump` are
|
|
@@ -24,8 +29,8 @@ JavaScript comments in documents are no longer supported by default.
|
|
|
24
29
|
Numerous rarely used aliases have been removed.
|
|
25
30
|
|
|
26
31
|
* `JSON.load` defaults are now safe to use.
|
|
27
|
-
* All unknown options
|
|
28
|
-
* The `allow_comments` parsing option now
|
|
32
|
+
* All unknown options will now cause an `ArgumentError` rather than to be ignored.
|
|
33
|
+
* The `allow_comments` parsing option now defaults to `false`.
|
|
29
34
|
* The `allow_duplicate_key` option now defaults to `false`, for both parsing and generating JSON.
|
|
30
35
|
* Removed the `limit` positional argument of `JSON.dump`.
|
|
31
36
|
* Removed the `escape_slash` alias of `script_safe`.
|
|
@@ -1124,15 +1124,6 @@ static void generate_json_fallback(FBuffer *buffer, struct generate_json_data *d
|
|
|
1124
1124
|
}
|
|
1125
1125
|
}
|
|
1126
1126
|
|
|
1127
|
-
static inline void generate_json_symbol(FBuffer *buffer, struct generate_json_data *data, VALUE obj)
|
|
1128
|
-
{
|
|
1129
|
-
if (data->state->strict) {
|
|
1130
|
-
generate_json_string(buffer, data, rb_sym2str(obj));
|
|
1131
|
-
} else {
|
|
1132
|
-
generate_json_fallback(buffer, data, obj);
|
|
1133
|
-
}
|
|
1134
|
-
}
|
|
1135
|
-
|
|
1136
1127
|
static void generate_json_null(FBuffer *buffer, struct generate_json_data *data, VALUE obj)
|
|
1137
1128
|
{
|
|
1138
1129
|
fbuffer_append(buffer, "null", 4);
|
|
@@ -1218,7 +1209,13 @@ start:
|
|
|
1218
1209
|
} else if (RB_FLONUM_P(obj)) {
|
|
1219
1210
|
generate_json_float(buffer, data, obj);
|
|
1220
1211
|
} else if (RB_STATIC_SYM_P(obj)) {
|
|
1221
|
-
|
|
1212
|
+
if (data->state->strict) {
|
|
1213
|
+
obj = rb_sym2str(obj);
|
|
1214
|
+
JSON_ASSERT(RBASIC_CLASS(obj) == rb_cString);
|
|
1215
|
+
goto generate_string;
|
|
1216
|
+
}
|
|
1217
|
+
|
|
1218
|
+
generate_json_fallback(buffer, data, obj);
|
|
1222
1219
|
} else {
|
|
1223
1220
|
goto general;
|
|
1224
1221
|
}
|
|
@@ -1239,6 +1236,7 @@ start:
|
|
|
1239
1236
|
case T_STRING:
|
|
1240
1237
|
if (fallback && klass != rb_cString) goto general;
|
|
1241
1238
|
|
|
1239
|
+
generate_string:
|
|
1242
1240
|
if (RB_LIKELY(valid_json_string_p(obj))) {
|
|
1243
1241
|
raw_generate_json_string(buffer, data, obj);
|
|
1244
1242
|
} else if (as_json_called) {
|
|
@@ -1250,7 +1248,13 @@ start:
|
|
|
1250
1248
|
}
|
|
1251
1249
|
break;
|
|
1252
1250
|
case T_SYMBOL:
|
|
1253
|
-
|
|
1251
|
+
if (data->state->strict) {
|
|
1252
|
+
obj = rb_sym2str(obj);
|
|
1253
|
+
JSON_ASSERT(RBASIC_CLASS(obj) == rb_cString);
|
|
1254
|
+
goto generate_string;
|
|
1255
|
+
}
|
|
1256
|
+
|
|
1257
|
+
generate_json_fallback(buffer, data, obj);
|
|
1254
1258
|
break;
|
|
1255
1259
|
case T_FLOAT:
|
|
1256
1260
|
if (fallback && klass != rb_cFloat) goto general;
|
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
static VALUE mJSON, eNestingError, eParserError, Encoding_UTF_8;
|
|
6
6
|
static VALUE CNaN, CInfinity, CMinusInfinity, JSON_empty_string;
|
|
7
7
|
|
|
8
|
-
static ID i_new, i_try_convert, i_encode, i_at_line, i_at_column;
|
|
8
|
+
static ID i_new, i_try_convert, i_encode, i_at_line, i_at_column, i_at_json_path;
|
|
9
9
|
#ifndef HAVE_RB_STR_TO_INTERNED_STR
|
|
10
10
|
static ID i_uminus;
|
|
11
11
|
#endif
|
|
@@ -651,11 +651,42 @@ static VALUE build_parse_error_message(const char *format, JSON_ParserState *sta
|
|
|
651
651
|
return rb_enc_sprintf(enc_utf8, format, ptr);
|
|
652
652
|
}
|
|
653
653
|
|
|
654
|
+
static VALUE json_path_new(JSON_ParserState *state, VALUE duplicate_key)
|
|
655
|
+
{
|
|
656
|
+
VALUE path = rb_ary_new_capa(state->current_nesting);
|
|
657
|
+
|
|
658
|
+
json_frame_stack *frames = state->frames;
|
|
659
|
+
rvalue_stack *values = state->value_stack;
|
|
660
|
+
|
|
661
|
+
for (long depth = 1; depth < frames->head; depth++) {
|
|
662
|
+
json_frame *frame = &frames->ptr[depth];
|
|
663
|
+
|
|
664
|
+
bool innermost = depth == frames->head - 1;
|
|
665
|
+
long child_head = innermost ? values->head : frames->ptr[depth + 1].value_stack_head;
|
|
666
|
+
long count = child_head - frame->value_stack_head;
|
|
667
|
+
|
|
668
|
+
if (frame->type == JSON_FRAME_ARRAY) {
|
|
669
|
+
rb_ary_push(path, LONG2NUM(frame->phase == JSON_PHASE_ARRAY_COMMA ? count - 1 : count));
|
|
670
|
+
} else if (innermost && !UNDEF_P(duplicate_key)) {
|
|
671
|
+
rb_ary_push(path, duplicate_key);
|
|
672
|
+
} else if (count & 1) {
|
|
673
|
+
rb_ary_push(path, values->ptr[child_head - 1]);
|
|
674
|
+
} else if (frame->phase == JSON_PHASE_OBJECT_COMMA && count >= 2) {
|
|
675
|
+
rb_ary_push(path, values->ptr[child_head - 2]);
|
|
676
|
+
} else {
|
|
677
|
+
break;
|
|
678
|
+
}
|
|
679
|
+
}
|
|
680
|
+
|
|
681
|
+
return path;
|
|
682
|
+
}
|
|
683
|
+
|
|
654
684
|
static VALUE parse_error_new(JSON_ParserState *state, VALUE message, long line, long column, bool eos)
|
|
655
685
|
{
|
|
656
686
|
VALUE exc = rb_exc_new_str(eParserError, message);
|
|
657
687
|
rb_ivar_set(exc, i_at_line, LONG2NUM(line));
|
|
658
688
|
rb_ivar_set(exc, i_at_column, LONG2NUM(column));
|
|
689
|
+
rb_ivar_set(exc, i_at_json_path, json_path_new(state, Qundef));
|
|
659
690
|
return exc;
|
|
660
691
|
}
|
|
661
692
|
|
|
@@ -1010,6 +1041,9 @@ NOINLINE(static) VALUE json_string_unescape(JSON_ParserState *state, JSON_Parser
|
|
|
1010
1041
|
raise_syntax_error_at("incomplete surrogate pair at %s", state, p);
|
|
1011
1042
|
break;
|
|
1012
1043
|
}
|
|
1044
|
+
} else if ((ch & 0xFC00) == 0xDC00) {
|
|
1045
|
+
raise_syntax_error_at("unpaired trailing surrogate at %s", state, p);
|
|
1046
|
+
break;
|
|
1013
1047
|
}
|
|
1014
1048
|
|
|
1015
1049
|
int unescape_len = convert_UTF32_to_UTF8(buffer, ch);
|
|
@@ -1199,14 +1233,17 @@ NORETURN(static) void raise_duplicate_key_error(JSON_ParserState *state, VALUE d
|
|
|
1199
1233
|
);
|
|
1200
1234
|
|
|
1201
1235
|
rb_str_concat(message, build_parse_error_message("", state));
|
|
1236
|
+
VALUE exc;
|
|
1202
1237
|
if (state->parser) { // line and columns can't be accurate in resumable
|
|
1203
|
-
|
|
1238
|
+
exc = parse_error_new(state, message, 0, 0, false);
|
|
1204
1239
|
} else {
|
|
1205
1240
|
long line, column;
|
|
1206
1241
|
cursor_position(state, &line, &column);
|
|
1207
1242
|
rb_str_catf(message, " at line %ld column %ld", line, column);
|
|
1208
|
-
|
|
1243
|
+
exc = parse_error_new(state, message, line, column, false);
|
|
1209
1244
|
}
|
|
1245
|
+
rb_ivar_set(exc, i_at_json_path, json_path_new(state, duplicate_key));
|
|
1246
|
+
rb_exc_raise(exc);
|
|
1210
1247
|
}
|
|
1211
1248
|
|
|
1212
1249
|
NOINLINE(static) void json_on_duplicate_key(JSON_ParserState *state, JSON_ParserConfig *config, size_t count, const VALUE *pairs)
|
|
@@ -2122,6 +2159,12 @@ static VALUE cParser_parse(JSON_ParserConfig *config, VALUE src)
|
|
|
2122
2159
|
// the rvalue stack.
|
|
2123
2160
|
VALUE result = complete ? *rvalue_stack_peek(state->value_stack, 1) : Qundef;
|
|
2124
2161
|
|
|
2162
|
+
if (complete) {
|
|
2163
|
+
json_ensure_eof(state, config);
|
|
2164
|
+
} else {
|
|
2165
|
+
raise_eos_error("unexpected end of input", state);
|
|
2166
|
+
}
|
|
2167
|
+
|
|
2125
2168
|
// This may be skipped in case of exception, but
|
|
2126
2169
|
// it won't cause a leak.
|
|
2127
2170
|
rvalue_stack_eagerly_release(value_stack_handle);
|
|
@@ -2130,12 +2173,6 @@ static VALUE cParser_parse(JSON_ParserConfig *config, VALUE src)
|
|
|
2130
2173
|
RB_GC_GUARD(frame_stack_handle);
|
|
2131
2174
|
RB_GC_GUARD(Vsource);
|
|
2132
2175
|
|
|
2133
|
-
if (complete) {
|
|
2134
|
-
json_ensure_eof(state, config);
|
|
2135
|
-
} else {
|
|
2136
|
-
raise_eos_error("unexpected end of input", state);
|
|
2137
|
-
}
|
|
2138
|
-
|
|
2139
2176
|
return result;
|
|
2140
2177
|
}
|
|
2141
2178
|
|
|
@@ -2871,6 +2908,7 @@ void Init_parser(void)
|
|
|
2871
2908
|
i_encode = rb_intern("encode");
|
|
2872
2909
|
i_at_line = rb_intern("@line");
|
|
2873
2910
|
i_at_column = rb_intern("@column");
|
|
2911
|
+
i_at_json_path = rb_intern("@json_path");
|
|
2874
2912
|
|
|
2875
2913
|
binary_encindex = rb_ascii8bit_encindex();
|
|
2876
2914
|
utf8_encindex = rb_utf8_encindex();
|
data/lib/json/common.rb
CHANGED
|
@@ -142,7 +142,56 @@ module JSON
|
|
|
142
142
|
|
|
143
143
|
# This exception is raised if a parser error occurs.
|
|
144
144
|
class ParserError < JSONError
|
|
145
|
-
|
|
145
|
+
# Line number where the parser encountered an error.
|
|
146
|
+
# Is <tt>nil</tt> when raised by JSON::ResumableParser.
|
|
147
|
+
attr_reader :line
|
|
148
|
+
|
|
149
|
+
# Column number where the parser encountered an error.
|
|
150
|
+
# Is <tt>nil</tt> when raised by JSON::ResumableParser.
|
|
151
|
+
attr_reader :column
|
|
152
|
+
|
|
153
|
+
# Returns a best effort JSONPath string representing where in the document
|
|
154
|
+
# the parser encountered an error:
|
|
155
|
+
#
|
|
156
|
+
# begin
|
|
157
|
+
# JSON.parse('{"articles": [ { "title": invalid } ]}')
|
|
158
|
+
# rescue JSON::ParserError => error
|
|
159
|
+
# error.json_path # => "$.articles[0].title"
|
|
160
|
+
# end
|
|
161
|
+
def json_path
|
|
162
|
+
return @json_path if String === @json_path
|
|
163
|
+
|
|
164
|
+
if Array === @json_path
|
|
165
|
+
path = build_json_path(@json_path)
|
|
166
|
+
@json_path = path unless frozen?
|
|
167
|
+
return path
|
|
168
|
+
end
|
|
169
|
+
end
|
|
170
|
+
|
|
171
|
+
private
|
|
172
|
+
|
|
173
|
+
def build_json_path(segments)
|
|
174
|
+
error = false
|
|
175
|
+
path = segments.filter_map do |segment|
|
|
176
|
+
next if error
|
|
177
|
+
|
|
178
|
+
case segment
|
|
179
|
+
when Integer
|
|
180
|
+
"[#{segment}]"
|
|
181
|
+
when String, Symbol
|
|
182
|
+
if segment.match?(/\A[a-zA-Z\$\_][a-zA-Z\$\_0-9]*\z/)
|
|
183
|
+
".#{segment}"
|
|
184
|
+
else
|
|
185
|
+
segment = segment.to_s.gsub(/["\\]/, { '"' => '\\"', '\\' => '\\\\' })
|
|
186
|
+
%{["#{segment}"]}
|
|
187
|
+
end
|
|
188
|
+
else
|
|
189
|
+
error = true
|
|
190
|
+
nil
|
|
191
|
+
end
|
|
192
|
+
end.join
|
|
193
|
+
"$#{path}".freeze
|
|
194
|
+
end
|
|
146
195
|
end
|
|
147
196
|
|
|
148
197
|
# This exception is raised if the nesting of parsed data structures is too
|
data/lib/json/version.rb
CHANGED