From 2cb925c186e623f19b61fe0ce41bb772406df462 Mon Sep 17 00:00:00 2001 From: Niels Date: Wed, 13 Apr 2016 17:41:19 +0200 Subject: [PATCH] adding support for escaped reference tokens --- src/json.hpp | 57 ++++++++++++++++++++++++++++++++++++++++++----- src/json.hpp.re2c | 57 ++++++++++++++++++++++++++++++++++++++++++----- test/unit.cpp | 53 +++++++++++++++++++++++++++++++++++++------ 3 files changed, 150 insertions(+), 17 deletions(-) diff --git a/src/json.hpp b/src/json.hpp index c8b815048..f2c3813ac 100644 --- a/src/json.hpp +++ b/src/json.hpp @@ -8861,18 +8861,18 @@ basic_json_parser_64: /// return referenced value reference get(reference j) { - reference result = j; + pointer result = &j; for (const auto& reference_token : reference_tokens) { - switch (result.m_type) + switch (result->m_type) { case value_t::object: - result = result[reference_token]; + result = &result->at(reference_token); continue; case value_t::array: - result = result[std::stoi(reference_token)]; + result = &result->at(static_cast(std::stoi(reference_token))); continue; default: @@ -8880,13 +8880,52 @@ basic_json_parser_64: } } - return result; + return *result; + } + + const_reference get(const_reference j) const + { + const_pointer result = &j; + + for (const auto& reference_token : reference_tokens) + { + switch (result->m_type) + { + case value_t::object: + result = &result->at(reference_token); + continue; + + case value_t::array: + result = &result->at(static_cast(std::stoi(reference_token))); + continue; + + default: + throw std::domain_error("unresolved reference token '" + reference_token + "'"); + } + } + + return *result; } private: /// the reference tokens std::vector reference_tokens {}; + /// replace all occurrences of a substring by another string + void replace_substring(std::string& s, + const std::string& f, + const std::string& t) + { + assert(not f.empty()); + + for ( + size_t pos = s.find(f); // find first occurrence of f + pos != std::string::npos; // make sure f was found + s.replace(pos, f.size(), t), // replace with t + pos = s.find(f, pos + t.size()) // find next occurrence of f + ); + } + /// split the string input to reference tokens void split(std::string reference_string) { @@ -8915,6 +8954,14 @@ basic_json_parser_64: { reference_tokens.push_back(""); } + + for (auto& reference_token : reference_tokens) + { + // first transform any occurrence of the sequence '~1' to '/' + replace_substring(reference_token, "~1", "/"); + // then transform any occurrence of the sequence '~0' to '~' + replace_substring(reference_token, "~0", "~"); + } } }; }; diff --git a/src/json.hpp.re2c b/src/json.hpp.re2c index 164f49628..ea62dca57 100644 --- a/src/json.hpp.re2c +++ b/src/json.hpp.re2c @@ -8140,18 +8140,18 @@ class basic_json /// return referenced value reference get(reference j) { - reference result = j; + pointer result = &j; for (const auto& reference_token : reference_tokens) { - switch (result.m_type) + switch (result->m_type) { case value_t::object: - result = result[reference_token]; + result = &result->at(reference_token); continue; case value_t::array: - result = result[std::stoi(reference_token)]; + result = &result->at(static_cast(std::stoi(reference_token))); continue; default: @@ -8159,13 +8159,52 @@ class basic_json } } - return result; + return *result; + } + + const_reference get(const_reference j) const + { + const_pointer result = &j; + + for (const auto& reference_token : reference_tokens) + { + switch (result->m_type) + { + case value_t::object: + result = &result->at(reference_token); + continue; + + case value_t::array: + result = &result->at(static_cast(std::stoi(reference_token))); + continue; + + default: + throw std::domain_error("unresolved reference token '" + reference_token + "'"); + } + } + + return *result; } private: /// the reference tokens std::vector reference_tokens {}; + /// replace all occurrences of a substring by another string + void replace_substring(std::string& s, + const std::string& f, + const std::string& t) + { + assert(not f.empty()); + + for ( + size_t pos = s.find(f); // find first occurrence of f + pos != std::string::npos; // make sure f was found + s.replace(pos, f.size(), t), // replace with t + pos = s.find(f, pos + t.size()) // find next occurrence of f + ); + } + /// split the string input to reference tokens void split(std::string reference_string) { @@ -8194,6 +8233,14 @@ class basic_json { reference_tokens.push_back(""); } + + for (auto& reference_token : reference_tokens) + { + // first transform any occurrence of the sequence '~1' to '/' + replace_substring(reference_token, "~1", "/"); + // then transform any occurrence of the sequence '~0' to '~' + replace_substring(reference_token, "~0", "~"); + } } }; }; diff --git a/test/unit.cpp b/test/unit.cpp index 6051ee3e5..d6ec00a79 100644 --- a/test/unit.cpp +++ b/test/unit.cpp @@ -12071,13 +12071,53 @@ TEST_CASE("JSON pointers") } )"_json; - json::json_pointer jp0(""); - json::json_pointer jp1("/foo"); - //json::json_pointer jp2("/foo/0"); + const json j_const = j; - auto jp0_ = jp0.get(j); - auto jp1_ = jp1.get(j); - //auto jp2_ = jp2.get(j); + SECTION("nonconst access") + { + // the whole document + CHECK(json::json_pointer().get(j) == j); + CHECK(json::json_pointer("").get(j) == j); + + // array access + CHECK(json::json_pointer("/foo").get(j) == j["foo"]); + CHECK(json::json_pointer("/foo/0").get(j) == j["foo"][0]); + CHECK(json::json_pointer("/foo/1").get(j) == j["foo"][1]); + + // empty string access + CHECK(json::json_pointer("/").get(j) == j[""]); + + // other cases + CHECK(json::json_pointer("/ ").get(j) == j[" "]); + CHECK(json::json_pointer("/c%d").get(j) == j["c%d"]); + CHECK(json::json_pointer("/e^f").get(j) == j["e^f"]); + CHECK(json::json_pointer("/g|h").get(j) == j["g|h"]); + CHECK(json::json_pointer("/i\\j").get(j) == j["i\\j"]); + CHECK(json::json_pointer("/k\"l").get(j) == j["k\"l"]); + + // escaped access + CHECK(json::json_pointer("/a~1b").get(j) == j["a/b"]); + CHECK(json::json_pointer("/m~0n").get(j) == j["m~n"]); + + // unescaped access + CHECK_THROWS_AS(json::json_pointer("/a/b").get(j), std::out_of_range); + CHECK_THROWS_WITH(json::json_pointer("/a/b").get(j), "key 'a' not found"); + // "/a/b" works for JSON {"a": {"b": 42}} + CHECK(json::json_pointer("/a/b").get({{"a", {{"b", 42}}}}) == json(42)); + } + + SECTION("const access") + { + CHECK(j_const == json::json_pointer().get(j_const)); + CHECK(j_const == json::json_pointer("").get(j_const)); + + CHECK(j_const["foo"] == json::json_pointer("/foo").get(j_const)); + CHECK(j_const["foo"][0] == json::json_pointer("/foo/0").get(j_const)); + CHECK(j_const["foo"][1] == json::json_pointer("/foo/1").get(j_const)); + + CHECK(j_const[""] == json::json_pointer("/").get(j_const)); + CHECK(j_const[" "] == json::json_pointer("/ ").get(j_const)); + } } } @@ -12437,4 +12477,3 @@ TEST_CASE("regression tests") CHECK(j3c.dump() == "1e04"); } } -