diff --git a/include/rapidjson/document.h b/include/rapidjson/document.h index 77250a3..16167ff 100644 --- a/include/rapidjson/document.h +++ b/include/rapidjson/document.h @@ -556,6 +556,71 @@ public: GenericValue& Move() { return *this; } //@} + //!@name Equal-to and not-equal-to operators + //@{ + //! Equal-to operator + bool operator==(const GenericValue& rhs) const { + if (GetType() != rhs.GetType()) + return false; + + switch (GetType()) { + case kObjectType: // Warning: O(n^2) inner-loop + if (data_.o.size != rhs.data_.o.size) + return false; + for (ConstMemberIterator lhsMemberItr = MemberBegin(); lhsMemberItr != MemberEnd(); ++lhsMemberItr) { + ConstMemberIterator rhsMemberItr = rhs.FindMember(lhsMemberItr->name); + if (rhsMemberItr == rhs.MemberEnd() || lhsMemberItr->value != rhsMemberItr->value) + return false; + } + return true; + + + case kArrayType: + if (data_.a.size != rhs.data_.a.size) + return false; + for (size_t i = 0; i < data_.a.size; i++) + if ((*this)[i] != rhs[i]) + return false; + return true; + + case kStringType: + return StringEqual(rhs); + + case kNumberType: + if (IsDouble() || rhs.GetDouble()) + return GetDouble() == rhs.GetDouble(); // May convert one operand from integer to double. + else + return data_.n.u64 == rhs.data_.n.u64; + + default: // kTrueType, kFalseType, kNullType + return true; + } + } + + //! Not-equal-to operator + bool operator!=(const GenericValue& rhs) const { return !(*this == rhs); } + + //! (Not-)Equal-to operator with const C-string pointer. + friend bool operator==(const GenericValue& lhs, const Ch* rhs) { return lhs == GenericValue(StringRef(rhs)); } + friend bool operator!=(const GenericValue& lhs, const Ch* rhs) { return !(lhs == rhs); } + friend bool operator==(const Ch* lhs, const GenericValue& rhs) { return GenericValue(StringRef(lhs)) == rhs; } + friend bool operator!=(const Ch* lhs, const GenericValue& rhs) { return !(lhs == rhs); } + + //! (Not-)Equal-to operator with non-const C-string pointer. + friend bool operator==(const GenericValue& lhs, Ch* rhs) { return lhs == GenericValue(StringRef(rhs)); } + friend bool operator!=(const GenericValue& lhs, Ch* rhs) { return !(lhs == rhs); } + friend bool operator==(Ch* lhs, const GenericValue& rhs) { return GenericValue(StringRef(lhs)) == rhs; } + friend bool operator!=(Ch* lhs, const GenericValue& rhs) { return !(lhs == rhs); } + + //! (Not-)Equal-to operator with primitive types. + /*! \tparam T Either \ref Type, \c int, \c unsigned, \c int64_t, \c uint64_t, \c double, \c true, \c false + */ + template friend bool operator==(const GenericValue& lhs, const T& rhs) { return lhs == GenericValue(rhs); } + template friend bool operator!=(const GenericValue& lhs, const T& rhs) { return !(lhs == rhs); } + template friend bool operator==(const T& lhs, const GenericValue& rhs) { return GenericValue(lhs) == rhs; } + template friend bool operator!=(const T& lhs, const GenericValue& rhs) { return !(lhs == rhs); } + //@} + //!@name Type //@{ @@ -672,10 +737,9 @@ public: MemberIterator FindMember(const GenericValue& name) { RAPIDJSON_ASSERT(IsObject()); RAPIDJSON_ASSERT(name.IsString()); - SizeType len = name.data_.s.length; MemberIterator member = MemberBegin(); for ( ; member != MemberEnd(); ++member) - if (member->name.data_.s.length == len && memcmp(member->name.data_.s.str, name.data_.s.str, len * sizeof(Ch)) == 0) + if (name.StringEqual(member->name)) break; return member; } @@ -1214,6 +1278,13 @@ private: rhs.flags_ = kNullFlag; } + bool StringEqual(const GenericValue& rhs) const { + RAPIDJSON_ASSERT(IsString()); + RAPIDJSON_ASSERT(rhs.IsString()); + return data_.s.str == rhs.data_.s.str || // fast path for constant string + ((data_.s.length == rhs.data_.s.length) && memcmp(data_.s.str, rhs.data_.s.str, sizeof(Ch) * data_.s.length) == 0); + } + Data data_; unsigned flags_; }; diff --git a/test/unittest/valuetest.cpp b/test/unittest/valuetest.cpp index 8eaf84a..3bd0afc 100644 --- a/test/unittest/valuetest.cpp +++ b/test/unittest/valuetest.cpp @@ -44,6 +44,58 @@ TEST(Value, assignment_operator) { EXPECT_EQ(y.GetString(),mstr); } +template +void TestEqual(const A& a, const B& b) { + EXPECT_TRUE (a == b); + EXPECT_FALSE(a != b); + EXPECT_TRUE (b == a); + EXPECT_FALSE(b != a); +} + +template +void TestUnequal(const A& a, const B& b) { + EXPECT_FALSE(a == b); + EXPECT_TRUE (a != b); + EXPECT_FALSE(b == a); + EXPECT_TRUE (b != a); +} + +TEST(Value, equalto_operator) { + Value::AllocatorType allocator; + Value x(kObjectType); + x.AddMember("hello", "world", allocator) + .AddMember("t", Value(true).Move(), allocator) + .AddMember("f", Value(false).Move(), allocator) + .AddMember("n", Value(kNullType).Move(), allocator) + .AddMember("i", 123, allocator) + .AddMember("pi", 3.14, allocator) + .AddMember("a", Value(kArrayType).Move().PushBack(1, allocator).PushBack(2, allocator).PushBack(3, allocator), allocator); + + // Test templated operator==() and operator!=() + TestEqual(x["hello"], "world"); + const char* cc = "world"; + TestEqual(x["hello"], cc); + char* c = strdup("world"); + TestEqual(x["hello"], c); + free(c); + + TestEqual(x["t"], true); + TestEqual(x["f"], false); + TestEqual(x["i"], 123); + TestEqual(x["pi"], 3.14); + + // Test operator==() + Value y; + y.CopyFrom(x, allocator); + TestEqual(x, y); + + // Swapping member order should be fine. + y.RemoveMember("t"); + TestUnequal(x, y); + y.AddMember("t", Value(true).Move(), allocator); + TestEqual(x, y); +} + template void TestCopyFrom() { typename Value::AllocatorType a;