Skip to content

Commit f6ea83c

Browse files
author
Łukasz A.J. Wrona
authored
Merge pull request #4912 from LAJW/lajw/json-equality
JSON equality operator
2 parents ba7036c + c70cbab commit f6ea83c

File tree

4 files changed

+156
-54
lines changed

4 files changed

+156
-54
lines changed

scripts/cpplint.py

Lines changed: 0 additions & 54 deletions
Original file line numberDiff line numberDiff line change
@@ -3928,60 +3928,6 @@ def CheckBracesSpacing(filename, clean_lines, linenum, nesting_state, error):
39283928
"""
39293929
line = clean_lines.elided[linenum]
39303930

3931-
# Except after an opening paren, or after another opening brace (in case of
3932-
# an initializer list, for instance), you should have spaces before your
3933-
# braces when they are delimiting blocks, classes, namespaces etc.
3934-
# And since you should never have braces at the beginning of a line,
3935-
# this is an easy test. Except that braces used for initialization don't
3936-
# follow the same rule; we often don't want spaces before those.
3937-
match = Match(r'^(.*[^ ({>]){', line)
3938-
3939-
if match:
3940-
# Try a bit harder to check for brace initialization. This
3941-
# happens in one of the following forms:
3942-
# Constructor() : initializer_list_{} { ... }
3943-
# Constructor{}.MemberFunction()
3944-
# Type variable{};
3945-
# FunctionCall(type{}, ...);
3946-
# LastArgument(..., type{});
3947-
# LOG(INFO) << type{} << " ...";
3948-
# map_of_type[{...}] = ...;
3949-
# ternary = expr ? new type{} : nullptr;
3950-
# OuterTemplate<InnerTemplateConstructor<Type>{}>
3951-
#
3952-
# We check for the character following the closing brace, and
3953-
# silence the warning if it's one of those listed above, i.e.
3954-
# "{.;,)<>]:".
3955-
#
3956-
# To account for nested initializer list, we allow any number of
3957-
# closing braces up to "{;,)<". We can't simply silence the
3958-
# warning on first sight of closing brace, because that would
3959-
# cause false negatives for things that are not initializer lists.
3960-
# Silence this: But not this:
3961-
# Outer{ if (...) {
3962-
# Inner{...} if (...){ // Missing space before {
3963-
# }; }
3964-
#
3965-
# There is a false negative with this approach if people inserted
3966-
# spurious semicolons, e.g. "if (cond){};", but we will catch the
3967-
# spurious semicolon with a separate check.
3968-
leading_text = match.group(1)
3969-
(endline, endlinenum, endpos) = CloseExpression(
3970-
clean_lines, linenum, len(match.group(1)))
3971-
trailing_text = ''
3972-
if endpos > -1:
3973-
trailing_text = endline[endpos:]
3974-
for offset in xrange(endlinenum + 1,
3975-
min(endlinenum + 3, clean_lines.NumLines() - 1)):
3976-
trailing_text += clean_lines.elided[offset]
3977-
# We also suppress warnings for `uint64_t{expression}` etc., as the style
3978-
# guide recommends brace initialization for integral types to avoid
3979-
# overflow/truncation.
3980-
if (not Match(r'^[\s}]*[{.;,)<>\]:]', trailing_text)
3981-
and not _IsType(clean_lines, nesting_state, leading_text)):
3982-
error(filename, linenum, 'whitespace/braces', 5,
3983-
'Missing space before {')
3984-
39853931
# You shouldn't have a space before a semicolon at the end of the line.
39863932
# There's a special case for "for" since the style guide allows space before
39873933
# the semicolon there.

src/util/json.cpp

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ Author: Daniel Kroening, [email protected]
99
#include "json.h"
1010

1111
#include <ostream>
12+
#include <algorithm>
1213

1314
const jsont jsont::null_json_object(jsont::kindt::J_NULL);
1415

@@ -162,3 +163,44 @@ void jsont::swap(jsont &other)
162163
other.object.swap(object);
163164
other.value.swap(value);
164165
}
166+
167+
bool operator==(const jsont &left, const jsont &right)
168+
{
169+
if(left.kind != right.kind)
170+
return false;
171+
switch(left.kind)
172+
{
173+
case jsont::kindt::J_NULL:
174+
return true;
175+
case jsont::kindt::J_TRUE:
176+
return true;
177+
case jsont::kindt::J_FALSE:
178+
return true;
179+
case jsont::kindt::J_NUMBER:
180+
return left.value == right.value;
181+
case jsont::kindt::J_STRING:
182+
return left.value == right.value;
183+
case jsont::kindt::J_ARRAY:
184+
{
185+
const auto &left_array = static_cast<const json_arrayt &>(left);
186+
const auto &right_array = static_cast<const json_arrayt &>(right);
187+
return left_array.size() == right_array.size() &&
188+
std::equal(
189+
left_array.begin(), left_array.end(), right_array.begin());
190+
}
191+
case jsont::kindt::J_OBJECT:
192+
{
193+
const auto &left_object = static_cast<const json_objectt &>(left);
194+
const auto &right_object = static_cast<const json_objectt &>(right);
195+
if(left_object.size() != left_object.size())
196+
return false;
197+
return std::all_of(
198+
left_object.begin(),
199+
left_object.end(),
200+
[&](const std::pair<std::string, jsont> &pair) {
201+
return right_object[pair.first] == pair.second;
202+
});
203+
}
204+
}
205+
UNREACHABLE;
206+
}

src/util/json.h

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -361,6 +361,11 @@ class json_objectt:public jsont
361361
return object.find(key);
362362
}
363363

364+
std::size_t size() const
365+
{
366+
return object.size();
367+
}
368+
364369
iterator begin()
365370
{
366371
return object.begin();
@@ -458,4 +463,6 @@ inline const json_stringt &to_json_string(const jsont &json)
458463
return static_cast<const json_stringt &>(json);
459464
}
460465

466+
bool operator==(const jsont &left, const jsont &right);
467+
461468
#endif // CPROVER_UTIL_JSON_H

unit/json/json_parser.cpp

Lines changed: 107 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -133,3 +133,110 @@ SCENARIO("Loading JSON files")
133133
}
134134
}
135135
}
136+
137+
TEST_CASE("json equality", "[core][util][json]")
138+
{
139+
SECTION("null")
140+
{
141+
REQUIRE(jsont::null_json_object == jsont::null_json_object);
142+
}
143+
144+
SECTION("boolean")
145+
{
146+
REQUIRE(jsont::json_boolean(false) == jsont::json_boolean(false));
147+
REQUIRE(jsont::json_boolean(true) == jsont::json_boolean(true));
148+
REQUIRE_FALSE(jsont::json_boolean(true) == jsont::json_boolean(false));
149+
REQUIRE_FALSE(jsont::json_boolean(false) == jsont::null_json_object);
150+
}
151+
152+
SECTION("number")
153+
{
154+
REQUIRE(json_numbert("0") == json_numbert("0"));
155+
REQUIRE(json_numbert("1") == json_numbert("1"));
156+
REQUIRE(json_numbert("-1") == json_numbert("-1"));
157+
REQUIRE(json_numbert("1.578") == json_numbert("1.578"));
158+
REQUIRE_FALSE(json_numbert("0") == json_numbert("1"));
159+
REQUIRE_FALSE(json_numbert("1") == json_numbert("-1"));
160+
REQUIRE_FALSE(json_numbert("-1") == json_numbert("1"));
161+
REQUIRE_FALSE(json_numbert("1.578") == json_numbert("1.5789"));
162+
REQUIRE_FALSE(json_numbert("0") == jsont::json_boolean(false));
163+
REQUIRE_FALSE(jsont::json_boolean(false) == json_numbert("0"));
164+
REQUIRE_FALSE(json_numbert("0") == jsont::null_json_object);
165+
REQUIRE_FALSE(jsont::null_json_object == json_numbert("0"));
166+
}
167+
168+
SECTION("string")
169+
{
170+
REQUIRE(json_stringt("") == json_stringt(""));
171+
REQUIRE(json_stringt("foo") == json_stringt("foo"));
172+
REQUIRE(json_stringt("bar") == json_stringt("bar"));
173+
REQUIRE_FALSE(json_stringt("foo") == json_stringt("bar"));
174+
REQUIRE_FALSE(json_stringt("bar") == json_stringt("baz"));
175+
REQUIRE_FALSE(json_stringt("foo") == json_stringt("food"));
176+
REQUIRE_FALSE(json_stringt("1") == json_numbert("1"));
177+
REQUIRE_FALSE(json_stringt("true") == jsont::json_boolean("true"));
178+
REQUIRE_FALSE(json_stringt("") == jsont::json_boolean("false"));
179+
REQUIRE_FALSE(json_stringt("") == jsont::null_json_object);
180+
}
181+
182+
SECTION("array")
183+
{
184+
REQUIRE(json_arrayt{} == json_arrayt{});
185+
REQUIRE(
186+
json_arrayt{jsont::null_json_object} ==
187+
json_arrayt{jsont::null_json_object});
188+
REQUIRE(
189+
json_arrayt{json_numbert{"9"}, json_numbert{"6"}} ==
190+
json_arrayt{json_numbert{"9"}, json_numbert{"6"}});
191+
REQUIRE(
192+
json_arrayt{
193+
json_stringt{"foo"}, json_stringt{"bar"}, json_stringt{"baz"}} ==
194+
json_arrayt{
195+
json_stringt{"foo"}, json_stringt{"bar"}, json_stringt{"baz"}});
196+
197+
// different lengths
198+
REQUIRE_FALSE(
199+
json_arrayt{json_stringt{"foo"}, json_stringt{"bar"}} ==
200+
json_arrayt{
201+
json_stringt{"foo"}, json_stringt{"bar"}, json_stringt{"baz"}});
202+
// different elements
203+
REQUIRE_FALSE(
204+
json_arrayt{
205+
json_stringt{"foo"}, json_stringt{"bar"}, json_stringt{"foo"}} ==
206+
json_arrayt{
207+
json_stringt{"foo"}, json_stringt{"bar"}, json_stringt{"baz"}});
208+
// different kind
209+
REQUIRE_FALSE(json_arrayt{} == jsont::json_boolean(false));
210+
REQUIRE_FALSE(json_arrayt{} == jsont::null_json_object);
211+
}
212+
213+
SECTION("object")
214+
{
215+
REQUIRE(json_objectt{} == json_objectt{});
216+
REQUIRE(
217+
json_objectt{{"key", json_stringt{"value"}}} ==
218+
json_objectt{{"key", json_stringt{"value"}}});
219+
REQUIRE(
220+
json_objectt{{"key1", json_stringt{"value1"}},
221+
{"key2", json_stringt{"value2"}}} ==
222+
json_objectt{{"key1", json_stringt{"value1"}},
223+
{"key2", json_stringt{"value2"}}});
224+
225+
// Extra property
226+
REQUIRE_FALSE(
227+
json_objectt{{"key1", json_stringt{"value1"}},
228+
{"key2", json_stringt{"value2"}},
229+
{"key3", json_stringt{"value3"}}} ==
230+
json_objectt{{"key1", json_stringt{"value1"}},
231+
{"key2", json_stringt{"value2"}}});
232+
// different field values
233+
REQUIRE_FALSE(
234+
json_objectt{{"key1", json_stringt{"foo"}},
235+
{"key2", json_stringt{"bar"}}} ==
236+
json_objectt{{"key1", json_stringt{"foo"}},
237+
{"key2", json_stringt{"baz"}}});
238+
// different kind
239+
REQUIRE_FALSE(json_objectt{} == json_arrayt{});
240+
REQUIRE_FALSE(json_objectt{} == jsont::null_json_object);
241+
}
242+
}

0 commit comments

Comments
 (0)