diff --git a/src/json.hpp b/src/json.hpp index 8f671fb0..c8b81504 100644 --- a/src/json.hpp +++ b/src/json.hpp @@ -8844,6 +8844,79 @@ basic_json_parser_64: /// the lexer lexer m_lexer; }; + + public: + class json_pointer + { + public: + /// empty reference token + json_pointer() = default; + + /// nonempty reference token + json_pointer(const std::string& s) + { + split(s); + } + + /// return referenced value + reference get(reference j) + { + reference result = j; + + for (const auto& reference_token : reference_tokens) + { + switch (result.m_type) + { + case value_t::object: + result = result[reference_token]; + continue; + + case value_t::array: + result = result[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 {}; + + /// split the string input to reference tokens + void split(std::string reference_string) + { + // special case: empty reference string -> no reference tokens + if (reference_string.empty()) + { + return; + } + + // check if nonempty reference string begins with slash + if (reference_string[0] != '/') + { + throw std::domain_error("JSON pointer must be empty or begin with '/'"); + } + + // tokenize reference string + auto ptr = std::strtok(&reference_string[0], "/"); + while (ptr != nullptr) + { + reference_tokens.push_back(ptr); + ptr = std::strtok(NULL, "/"); + } + + // special case: reference string was just "/" + if (reference_tokens.empty()) + { + reference_tokens.push_back(""); + } + } + }; }; diff --git a/src/json.hpp.re2c b/src/json.hpp.re2c index ebf83d83..164f4962 100644 --- a/src/json.hpp.re2c +++ b/src/json.hpp.re2c @@ -8123,6 +8123,79 @@ class basic_json /// the lexer lexer m_lexer; }; + + public: + class json_pointer + { + public: + /// empty reference token + json_pointer() = default; + + /// nonempty reference token + json_pointer(const std::string& s) + { + split(s); + } + + /// return referenced value + reference get(reference j) + { + reference result = j; + + for (const auto& reference_token : reference_tokens) + { + switch (result.m_type) + { + case value_t::object: + result = result[reference_token]; + continue; + + case value_t::array: + result = result[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 {}; + + /// split the string input to reference tokens + void split(std::string reference_string) + { + // special case: empty reference string -> no reference tokens + if (reference_string.empty()) + { + return; + } + + // check if nonempty reference string begins with slash + if (reference_string[0] != '/') + { + throw std::domain_error("JSON pointer must be empty or begin with '/'"); + } + + // tokenize reference string + auto ptr = std::strtok(&reference_string[0], "/"); + while (ptr != nullptr) + { + reference_tokens.push_back(ptr); + ptr = std::strtok(NULL, "/"); + } + + // special case: reference string was just "/" + if (reference_tokens.empty()) + { + reference_tokens.push_back(""); + } + } + }; }; diff --git a/test/unit.cpp b/test/unit.cpp index ab96364c..6051ee3e 100644 --- a/test/unit.cpp +++ b/test/unit.cpp @@ -12052,6 +12052,35 @@ TEST_CASE("Unicode", "[hide]") } } +TEST_CASE("JSON pointers") +{ + SECTION("examples from RFC 6901") + { + json j = R"( + { + "foo": ["bar", "baz"], + "": 0, + "a/b": 1, + "c%d": 2, + "e^f": 3, + "g|h": 4, + "i\\j": 5, + "k\"l": 6, + " ": 7, + "m~n": 8 + } + )"_json; + + json::json_pointer jp0(""); + json::json_pointer jp1("/foo"); + //json::json_pointer jp2("/foo/0"); + + auto jp0_ = jp0.get(j); + auto jp1_ = jp1.get(j); + //auto jp2_ = jp2.get(j); + } +} + TEST_CASE("regression tests") { SECTION("issue #60 - Double quotation mark is not parsed correctly")