Support ~0 and ~1 character escape sequences in JSON Reference tokens

This commit is contained in:
Tristan Penman 2015-06-17 10:00:13 +10:00
parent 3ac87e9af2
commit 5387aaf076
2 changed files with 97 additions and 2 deletions

View File

@ -4,6 +4,7 @@
#include <stdexcept>
#include <string>
#include <boost/algorithm/string/replace.hpp>
#include <boost/lexical_cast.hpp>
#include <boost/optional.hpp>
@ -13,6 +14,42 @@ namespace valijson {
namespace internal {
namespace json_pointer {
/**
* @brief Extract and transform the token between two iterators
*
* This function is responsible for extracting a JSON Reference token from
* between two iterators, and performing any necessary transformations, before
* returning the resulting string. Its main purpose is to replace escaped
* character sequences.
*
* From the JSON Pointer specification (RFC 6901, April 2013):
*
* Evaluation of each reference token begins by decoding any escaped
* character sequence. This is performed by first transforming any
* occurrence of the sequence '~1' to '/', and then transforming any
* occurrence of the sequence '~0' to '~'. By performing the
* substitutions in this order, an implementation avoids the error of
* turning '~01' first into '~1' and then into '/', which would be
* incorrect (the string '~01' correctly becomes '~1' after
* transformation).
*
* @param begin iterator pointing to beginning of a token
* @param end iterator pointing to one character past the end of the token
*
* @return string with escaped character sequences replaced
*
*/
inline std::string extractReferenceToken(std::string::const_iterator begin,
std::string::const_iterator end)
{
std::string token(begin, end);
boost::replace_all(token, "~1", "/");
boost::replace_all(token, "~0", "~");
return token;
}
/**
* @brief Recursively locate the value referenced by a JSON Pointer
*
@ -69,7 +106,8 @@ inline AdapterType resolveJsonPointer(
std::find(jsonPointerItr + 1, jsonPointerEnd, '/');
// Extract the next reference token
const std::string referenceToken(jsonPointerItr + 1, jsonPointerNext);
const std::string referenceToken = extractReferenceToken(
jsonPointerItr + 1, jsonPointerNext);
// Empty reference tokens should be ignored
if (referenceToken.empty()) {

View File

@ -163,7 +163,7 @@ std::vector<boost::shared_ptr<JsonPointerTestCase> >
testArray.PushBack("test2", allocator);
testCase = boost::make_shared<JsonPointerTestCase>(
"Resolveing '/test/3' in object containing one member containing "
"Resolving '/test/3' in object containing one member containing "
"an array with 3 elements should throw an exception");
testCase->value.SetObject();
testCase->value.AddMember("test", testArray, allocator);
@ -172,6 +172,63 @@ std::vector<boost::shared_ptr<JsonPointerTestCase> >
testCases.push_back(testCase);
}
//
// The following tests ensure that escape sequences are handled correctly.
//
// From the JSON Pointer specification (RFC 6901, April 2013):
//
// Evaluation of each reference token begins by decoding any escaped
// character sequence. This is performed by first transforming any
// occurrence of the sequence '~1' to '/', and then transforming any
// occurrence of the sequence '~0' to '~'. By performing the
// substitutions in this order, an implementation avoids the error of
// turning '~01' first into '~1' and then into '/', which would be
// incorrect (the string '~01' correctly becomes '~1' after
// transformation).
//
{
rapidjson::Value value;
value.SetDouble(10.);
testCase = boost::make_shared<JsonPointerTestCase>(
"Resolving '/hello~1world' in object containing one member named "
"'hello/world' should return the associated value");
testCase->value.SetObject();
testCase->value.AddMember("hello/world", value, allocator);
testCase->jsonPointer = "/hello~1world";
testCase->expectedValue = &testCase->value.FindMember("hello/world")->value;
testCases.push_back(testCase);
}
{
rapidjson::Value value;
value.SetDouble(10.);
testCase = boost::make_shared<JsonPointerTestCase>(
"Resolving '/hello~0world' in object containing one member named "
"'hello~world' should return the associated value");
testCase->value.SetObject();
testCase->value.AddMember("hello~world", value, allocator);
testCase->jsonPointer = "/hello~0world";
testCase->expectedValue = &testCase->value.FindMember("hello~world")->value;
testCases.push_back(testCase);
}
{
rapidjson::Value value;
value.SetDouble(10.);
testCase = boost::make_shared<JsonPointerTestCase>(
"Resolving '/hello~01world' in object containing one member named "
"'hello~1world' should return the associated value");
testCase->value.SetObject();
testCase->value.AddMember("hello~1world", value, allocator);
testCase->jsonPointer = "/hello~01world";
testCase->expectedValue = &testCase->value.FindMember("hello~1world")->value;
testCases.push_back(testCase);
}
return testCases;
}