diff --git a/data/include/join/json.hpp b/data/include/join/json.hpp index 17a73cb0..55bd79e3 100644 --- a/data/include/join/json.hpp +++ b/data/include/join/json.hpp @@ -503,7 +503,7 @@ namespace join * @return sorted object members. */ template - std::vector> members (const View& object) const + std::vector> members (const View& object) requires (Mode == JsonWriteMode::Canonical) { std::vector> sorted (object.begin (), object.end ()); @@ -517,6 +517,13 @@ namespace join return utf8::compareUtf16Order (sa.data (), sa.data () + sa.size (), sb.data (), sb.data () + sb.size ()) < 0; }); + for (size_t i = 1; i < sorted.size (); ++i) + { + if (sorted[i].get ().first.isString () && (sorted[i].get ().first == sorted[i - 1].get ().first)) + { + _duplicate = true; + } + } return sorted; } @@ -603,6 +610,15 @@ namespace join reset (); return -1; } + if constexpr (WriteMode == JsonWriteMode::Canonical) + { + if (JOIN_UNLIKELY (_duplicate)) + { + join::lastError = make_error_code (SaxErrc::DuplicateKey); + reset (); + return -1; + } + } --_depth; if (!_first) { @@ -633,6 +649,7 @@ namespace join _size = 0; _depth = 0; _first = true; + _duplicate = false; } protected: @@ -992,6 +1009,9 @@ namespace join /// is first element. bool _first = true; + + /// duplicate key found in a canonical object. + bool _duplicate = false; }; /// JSON default writer. diff --git a/data/include/join/sax_error.hpp b/data/include/join/sax_error.hpp index 99ea87f4..10c6c092 100644 --- a/data/include/join/sax_error.hpp +++ b/data/include/join/sax_error.hpp @@ -42,6 +42,7 @@ namespace join StackOverflow = 1, /**< Stack overflow. */ InvalidParent, /**< Parent not an array nor an object. */ InvalidKey, /**< Key is invalid. */ + DuplicateKey, /**< Key is duplicated. */ InvalidValue, /**< Value is invalid. */ InvalidEncoding, /**< Character encoding is invalid. */ ExtraData, /**< Extra data detected. */ diff --git a/data/src/sax_error.cpp b/data/src/sax_error.cpp index 3fa15df1..11bc31da 100644 --- a/data/src/sax_error.cpp +++ b/data/src/sax_error.cpp @@ -51,6 +51,8 @@ std::string SaxCategory::message (int code) const return "parent not an array nor an object"; case SaxErrc::InvalidKey: return "key is invalid"; + case SaxErrc::DuplicateKey: + return "key is duplicated"; case SaxErrc::InvalidValue: return "value is invalid"; case SaxErrc::InvalidEncoding: diff --git a/data/tests/json_canonicalizer_test.cpp b/data/tests/json_canonicalizer_test.cpp index a99f6ff0..d3af3aa5 100644 --- a/data/tests/json_canonicalizer_test.cpp +++ b/data/tests/json_canonicalizer_test.cpp @@ -32,6 +32,7 @@ using join::Value; using join::Array; using join::Object; using join::JsonCanonicalizer; +using join::JsonReader; using join::JsonErrc; using join::SaxErrc; @@ -63,6 +64,24 @@ TEST (JsonCanonicalizer, members) EXPECT_EQ (out.str (), "{\"a\":2,\"b\":1}"); EXPECT_EQ (value.getObject ()[0].first, "b"); EXPECT_EQ (value.getObject ()[1].first, "a"); + + Value duplicate; + ASSERT_EQ (duplicate.deserialize (std::string ("{\"a\":1,\"b\":2,\"a\":3}")), 0) + << join::lastError.message (); + out.str (""); + ASSERT_EQ (duplicate.serialize (writer), -1); + EXPECT_EQ (join::lastError, SaxErrc::DuplicateKey); + EXPECT_TRUE (out.str ().empty ()); + + Value nested; + ASSERT_EQ (nested.deserialize (std::string ("{\"x\":{\"a\":1,\"a\":2}}")), 0) + << join::lastError.message (); + ASSERT_EQ (nested.serialize (writer), -1); + EXPECT_EQ (join::lastError, SaxErrc::DuplicateKey); + EXPECT_TRUE (out.str ().empty ()); + + ASSERT_EQ (value.serialize (writer), 0) << join::lastError.message (); + EXPECT_EQ (out.str (), "{\"a\":2,\"b\":1}"); } /** diff --git a/data/tests/sax_error_test.cpp b/data/tests/sax_error_test.cpp index e55641fd..b2838d35 100644 --- a/data/tests/sax_error_test.cpp +++ b/data/tests/sax_error_test.cpp @@ -49,6 +49,7 @@ TEST (SaxCategory, message) EXPECT_STREQ (SaxCategory ().message (static_cast (SaxErrc::InvalidParent)).c_str (), "parent not an array nor an object"); EXPECT_STREQ (SaxCategory ().message (static_cast (SaxErrc::InvalidKey)).c_str (), "key is invalid"); + EXPECT_STREQ (SaxCategory ().message (static_cast (SaxErrc::DuplicateKey)).c_str (), "key is duplicated"); EXPECT_STREQ (SaxCategory ().message (static_cast (SaxErrc::InvalidValue)).c_str (), "value is invalid"); EXPECT_STREQ (SaxCategory ().message (static_cast (SaxErrc::InvalidEncoding)).c_str (), "character encoding is invalid"); @@ -65,9 +66,10 @@ TEST (SaxCategory, default_error_condition) EXPECT_EQ (SaxCategory ().default_error_condition (1).message (), "stack overflow"); EXPECT_EQ (SaxCategory ().default_error_condition (2).message (), "parent not an array nor an object"); EXPECT_EQ (SaxCategory ().default_error_condition (3).message (), "key is invalid"); - EXPECT_EQ (SaxCategory ().default_error_condition (4).message (), "value is invalid"); - EXPECT_EQ (SaxCategory ().default_error_condition (5).message (), "character encoding is invalid"); - EXPECT_EQ (SaxCategory ().default_error_condition (6).message (), "extra data detected"); + EXPECT_EQ (SaxCategory ().default_error_condition (4).message (), "key is duplicated"); + EXPECT_EQ (SaxCategory ().default_error_condition (5).message (), "value is invalid"); + EXPECT_EQ (SaxCategory ().default_error_condition (6).message (), "character encoding is invalid"); + EXPECT_EQ (SaxCategory ().default_error_condition (7).message (), "extra data detected"); } /**