Skip to content

Commit 9427d4a

Browse files
committed
Improve duplicate key warning and errors to include the key name
Followup: #818
1 parent 6d29d75 commit 9427d4a

3 files changed

Lines changed: 47 additions & 4 deletions

File tree

CHANGES.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@
22

33
### Unreleased
44

5+
* Improve duplicate key warning and errors to include the key name.
6+
57
### 2025-07-24 (2.13.1)
68

79
* Fix support for older compilers without `__builtin_cpu_supports`.

ext/json/ext/parser/parser.c

Lines changed: 44 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -426,6 +426,7 @@ static void emit_parse_warning(const char *message, JSON_ParserState *state)
426426
}
427427

428428
#define PARSE_ERROR_FRAGMENT_LEN 32
429+
429430
#ifdef RBIMPL_ATTR_NORETURN
430431
RBIMPL_ATTR_NORETURN()
431432
#endif
@@ -830,21 +831,61 @@ static inline VALUE json_decode_array(JSON_ParserState *state, JSON_ParserConfig
830831
return array;
831832
}
832833

834+
static VALUE json_find_duplicated_key(size_t count, const VALUE *pairs)
835+
{
836+
VALUE set = rb_hash_new_capa(count / 2);
837+
for (size_t index = 0; index < count; index += 2) {
838+
size_t before = RHASH_SIZE(set);
839+
VALUE key = pairs[index];
840+
rb_hash_aset(set, key, Qtrue);
841+
if (RHASH_SIZE(set) == before) {
842+
return key;
843+
}
844+
}
845+
return Qfalse;
846+
}
847+
848+
static void emit_duplicate_key_warning(JSON_ParserState *state, VALUE duplicate_key)
849+
{
850+
VALUE message = rb_sprintf(
851+
"detected duplicate key \"%s\" in JSON object. This will raise an error in json 3.0 unless enabled via `allow_duplicate_key: true`",
852+
RSTRING_PTR(duplicate_key)
853+
);
854+
855+
emit_parse_warning(RSTRING_PTR(message), state);
856+
RB_GC_GUARD(message);
857+
}
858+
859+
#ifdef RBIMPL_ATTR_NORETURN
860+
RBIMPL_ATTR_NORETURN()
861+
#endif
862+
static void raise_duplicate_key_error(JSON_ParserState *state, VALUE duplicate_key)
863+
{
864+
VALUE message = rb_sprintf(
865+
"duplicate key \"%s\"",
866+
RSTRING_PTR(duplicate_key)
867+
);
868+
869+
raise_parse_error(RSTRING_PTR(message), state);
870+
RB_GC_GUARD(message);
871+
}
872+
833873
static inline VALUE json_decode_object(JSON_ParserState *state, JSON_ParserConfig *config, size_t count)
834874
{
835875
size_t entries_count = count / 2;
836876
VALUE object = rb_hash_new_capa(entries_count);
837-
rb_hash_bulk_insert(count, rvalue_stack_peek(state->stack, count), object);
877+
const VALUE *pairs = rvalue_stack_peek(state->stack, count);
878+
rb_hash_bulk_insert(count, pairs, object);
838879

839880
if (RB_UNLIKELY(RHASH_SIZE(object) < entries_count)) {
840881
switch (config->on_duplicate_key) {
841882
case JSON_IGNORE:
842883
break;
843884
case JSON_DEPRECATED:
844-
emit_parse_warning("detected duplicate keys in JSON object. This will raise an error in json 3.0 unless enabled via `allow_duplicate_key: true`", state);
885+
emit_duplicate_key_warning(state, json_find_duplicated_key(count, pairs));
845886
break;
846887
case JSON_RAISE:
847-
raise_parse_error("duplicate key", state);
888+
raise_duplicate_key_error(state, json_find_duplicated_key(count, pairs));
848889
break;
849890
}
850891
}

test/json/json_parser_test.rb

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -335,7 +335,7 @@ def test_parse_duplicate_key
335335
expected = {"a" => 2}
336336
assert_equal expected, parse('{"a": 1, "a": 2}', allow_duplicate_key: true)
337337
assert_raise(ParserError) { parse('{"a": 1, "a": 2}', allow_duplicate_key: false) }
338-
assert_deprecated_warning(/duplicate keys/) do
338+
assert_deprecated_warning(/duplicate key "a"/) do
339339
assert_equal expected, parse('{"a": 1, "a": 2}')
340340
end
341341
end

0 commit comments

Comments
 (0)