Skip to content

Commit 23f8e28

Browse files
committed
option to check duplicate object keys
1 parent 4d22409 commit 23f8e28

File tree

11 files changed

+95
-13
lines changed

11 files changed

+95
-13
lines changed

include/boost/json/detail/handler.hpp

+1
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@ struct handler
3535
max_string_size = string::max_size();
3636

3737
value_stack st;
38+
bool ignore_duplicate_keys = true;
3839

3940
template<class... Args>
4041
explicit

include/boost/json/detail/impl/handler.ipp

+6-1
Original file line numberDiff line numberDiff line change
@@ -51,8 +51,13 @@ bool
5151
handler::
5252
on_object_end(
5353
std::size_t n,
54-
error_code&)
54+
error_code& ec)
5555
{
56+
if( !ignore_duplicate_keys )
57+
ec = st.check_duplicates(n);
58+
if( ec.failed() )
59+
return false;
60+
5661
st.push_object(n);
5762
return true;
5863
}

include/boost/json/error.hpp

+3
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,9 @@ enum class error
6666
/// error occured when trying to read input
6767
input_error,
6868

69+
/// duplicate object key
70+
duplicate_key,
71+
6972
//
7073
// generic errors
7174
//

include/boost/json/impl/error.ipp

+2
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,7 @@ case error::array_too_large: return "array too large";
4545
case error::key_too_large: return "key too large";
4646
case error::string_too_large: return "string too large";
4747
case error::input_error: return "input error";
48+
case error::duplicate_key: return "duplicate key";
4849

4950
case error::exception: return "got exception";
5051
case error::test_failure: return "test failure";
@@ -93,6 +94,7 @@ case error::array_too_large:
9394
case error::key_too_large:
9495
case error::string_too_large:
9596
case error::input_error:
97+
case error::duplicate_key:
9698
return condition::parse_error;
9799

98100
case error::missing_slash:

include/boost/json/impl/parser.ipp

+5-6
Original file line numberDiff line numberDiff line change
@@ -32,20 +32,19 @@ parser(
3232
size)
3333
{
3434
reset();
35+
p_.handler().ignore_duplicate_keys = opt.ignore_duplicate_keys;
3536
}
3637

3738
parser::
3839
parser(
3940
storage_ptr sp,
4041
parse_options const& opt) noexcept
41-
: p_(
42-
opt,
42+
: parser(
4343
std::move(sp),
44-
nullptr,
44+
opt,
45+
static_cast<unsigned char*>(nullptr),
4546
0)
46-
{
47-
reset();
48-
}
47+
{ }
4948

5049
void
5150
parser::

include/boost/json/impl/stream_parser.ipp

+5-6
Original file line numberDiff line numberDiff line change
@@ -32,20 +32,19 @@ stream_parser(
3232
size)
3333
{
3434
reset();
35+
p_.handler().ignore_duplicate_keys = opt.ignore_duplicate_keys;
3536
}
3637

3738
stream_parser::
3839
stream_parser(
3940
storage_ptr sp,
4041
parse_options const& opt) noexcept
41-
: p_(
42-
opt,
42+
: stream_parser(
4343
std::move(sp),
44-
nullptr,
44+
opt,
45+
static_cast<unsigned char*>(nullptr),
4546
0)
46-
{
47-
reset();
48-
}
47+
{ }
4948

5049
void
5150
stream_parser::

include/boost/json/impl/value_stack.ipp

+38
Original file line numberDiff line numberDiff line change
@@ -82,6 +82,37 @@ has_chars()
8282
return chars_ != 0;
8383
}
8484

85+
error_code
86+
value_stack::
87+
stack::
88+
check_duplicates(std::size_t n)
89+
{
90+
error_code ec;
91+
92+
for( value* first = top_ - 2 * n; first != top_; first += 2 )
93+
{
94+
BOOST_ASSERT( first->is_string() );
95+
value* other = first + 2;
96+
while( true )
97+
{
98+
BOOST_ASSERT( other->is_string() );
99+
if( first->get_string() == other->get_string() )
100+
{
101+
BOOST_JSON_FAIL( ec, error::duplicate_key );
102+
goto before_return;
103+
}
104+
105+
if( other == top_ )
106+
break;
107+
108+
other += 2;
109+
}
110+
}
111+
112+
before_return:
113+
return ec;
114+
}
115+
85116
//--------------------------------------
86117

87118
// destroy the values but
@@ -467,6 +498,13 @@ push_null()
467498
st_.push(nullptr, sp_);
468499
}
469500

501+
error_code
502+
value_stack::
503+
check_duplicates(std::size_t n)
504+
{
505+
return st_.check_duplicates(n);
506+
}
507+
470508
BOOST_JSON_NS_END
471509

472510
#endif

include/boost/json/parse_options.hpp

+14
Original file line numberDiff line numberDiff line change
@@ -76,6 +76,20 @@ struct parse_options
7676
@ref stream_parser.
7777
*/
7878
bool allow_invalid_utf8 = false;
79+
80+
81+
/** Unique keys restriction setting
82+
83+
Forbid duplicate keys to appear in objects.
84+
85+
@note Since @ref basic_parser doesn't store parsed elements directly,
86+
this option has to be taken account by implementers of handlers.
87+
88+
@see
89+
@ref basic_parser,
90+
@ref stream_parser.
91+
*/
92+
bool ignore_duplicate_keys = true;
7993
};
8094

8195
BOOST_JSON_NS_END

include/boost/json/value_stack.hpp

+5
Original file line numberDiff line numberDiff line change
@@ -140,6 +140,7 @@ class value_stack
140140
inline void run_dtors(bool b) noexcept;
141141
inline std::size_t size() const noexcept;
142142
inline bool has_chars();
143+
inline error_code check_duplicates(std::size_t n);
143144

144145
inline void clear() noexcept;
145146
inline void maybe_grow();
@@ -501,6 +502,10 @@ class value_stack
501502
BOOST_JSON_DECL
502503
void
503504
push_null();
505+
506+
BOOST_JSON_DECL
507+
error_code
508+
check_duplicates(std::size_t n);
504509
};
505510

506511
BOOST_JSON_NS_END

test/error.cpp

+1
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,7 @@ class error_test
6161
check(condition::parse_error, error::key_too_large);
6262
check(condition::parse_error, error::string_too_large);
6363
check(condition::parse_error, error::input_error);
64+
check(condition::parse_error, error::duplicate_key);
6465

6566
check(condition::pointer_parse_error, error::missing_slash);
6667
check(condition::pointer_parse_error, error::invalid_escape);

test/parse.cpp

+15
Original file line numberDiff line numberDiff line change
@@ -203,13 +203,28 @@ class parse_test
203203
BOOST_TEST_THROWS( parse(ss), system_error );
204204
}
205205

206+
void
207+
testDuplicates()
208+
{
209+
value jv = parse( R"( {"a": 1, "a": 2} )" );
210+
BOOST_TEST( jv.as_object().size() == 1 );
211+
212+
parse_options opt;
213+
214+
error_code ec;
215+
opt.ignore_duplicate_keys = false;
216+
jv = parse( R"( {"a": 1, "a": 2} )", ec, {}, opt );
217+
BOOST_TEST( ec == error::duplicate_key );
218+
}
219+
206220
void
207221
run()
208222
{
209223
testParse();
210224
testMemoryUsage();
211225
testIssue726();
212226
testIstream();
227+
testDuplicates();
213228
}
214229
};
215230

0 commit comments

Comments
 (0)