Skip to content

Commit

Permalink
Merge branch 'feature/json_pointer_contains' into develop
Browse files Browse the repository at this point in the history
  • Loading branch information
nlohmann committed Jul 9, 2019
2 parents 7a23aa1 + 258fa79 commit 104c5c1
Show file tree
Hide file tree
Showing 7 changed files with 338 additions and 23 deletions.
45 changes: 45 additions & 0 deletions doc/examples/contains_json_pointer.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
#include <iostream>
#include <nlohmann/json.hpp>

using json = nlohmann::json;

int main()
{
// create a JSON value
json j =
{
{"number", 1}, {"string", "foo"}, {"array", {1, 2}}
};

std::cout << std::boolalpha
<< j.contains("/number"_json_pointer) << '\n'
<< j.contains("/string"_json_pointer) << '\n'
<< j.contains("/string"_json_pointer) << '\n'
<< j.contains("/array"_json_pointer) << '\n'
<< j.contains("/array/1"_json_pointer) << '\n'
<< j.contains("/array/-"_json_pointer) << '\n'
<< j.contains("/array/4"_json_pointer) << '\n'
<< j.contains("/baz"_json_pointer) << std::endl;

// out_of_range.106
try
{
// try to use an array index with leading '0'
j.contains("/array/01"_json_pointer);
}
catch (json::parse_error& e)
{
std::cout << e.what() << '\n';
}

// out_of_range.109
try
{
// try to use an array index that is not a number
j.contains("/array/one"_json_pointer);
}
catch (json::parse_error& e)
{
std::cout << e.what() << '\n';
}
}
1 change: 1 addition & 0 deletions doc/examples/contains_json_pointer.link
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
<a target="_blank" href="https://wandbox.org/permlink/3TJ79OzHP4vmN1Nb"><b>online</b></a>
10 changes: 10 additions & 0 deletions doc/examples/contains_json_pointer.output
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
true
true
true
true
true
false
false
false
[json.exception.parse_error.106] parse error: array index '01' must not begin with '0'
[json.exception.parse_error.109] parse error: array index 'one' is not a number
90 changes: 81 additions & 9 deletions include/nlohmann/detail/json_pointer.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

#include <algorithm> // all_of
#include <cassert> // assert
#include <cctype> // isdigit
#include <numeric> // accumulate
#include <string> // string
#include <utility> // move
Expand Down Expand Up @@ -369,7 +370,7 @@ class json_pointer
// j which will be overwritten by a primitive value
for (const auto& reference_token : reference_tokens)
{
switch (result->m_type)
switch (result->type())
{
case detail::value_t::null:
{
Expand Down Expand Up @@ -446,14 +447,14 @@ class json_pointer
for (const auto& reference_token : reference_tokens)
{
// convert null values to arrays or objects before continuing
if (ptr->m_type == detail::value_t::null)
if (ptr->is_null())
{
// check if reference token is a number
const bool nums =
std::all_of(reference_token.begin(), reference_token.end(),
[](const char x)
[](const unsigned char x)
{
return x >= '0' and x <= '9';
return std::isdigit(x);
});

// change value to array for numbers or "-" or to object otherwise
Expand All @@ -462,7 +463,7 @@ class json_pointer
: detail::value_t::object;
}

switch (ptr->m_type)
switch (ptr->type())
{
case detail::value_t::object:
{
Expand Down Expand Up @@ -521,7 +522,7 @@ class json_pointer
using size_type = typename BasicJsonType::size_type;
for (const auto& reference_token : reference_tokens)
{
switch (ptr->m_type)
switch (ptr->type())
{
case detail::value_t::object:
{
Expand Down Expand Up @@ -586,7 +587,7 @@ class json_pointer
using size_type = typename BasicJsonType::size_type;
for (const auto& reference_token : reference_tokens)
{
switch (ptr->m_type)
switch (ptr->type())
{
case detail::value_t::object:
{
Expand Down Expand Up @@ -645,7 +646,7 @@ class json_pointer
using size_type = typename BasicJsonType::size_type;
for (const auto& reference_token : reference_tokens)
{
switch (ptr->m_type)
switch (ptr->type())
{
case detail::value_t::object:
{
Expand Down Expand Up @@ -692,6 +693,77 @@ class json_pointer
return *ptr;
}

/*!
@throw parse_error.106 if an array index begins with '0'
@throw parse_error.109 if an array index was not a number
*/
bool contains(const BasicJsonType* ptr) const
{
using size_type = typename BasicJsonType::size_type;
for (const auto& reference_token : reference_tokens)
{
switch (ptr->type())
{
case detail::value_t::object:
{
if (not ptr->contains(reference_token))
{
// we did not find the key in the object
return false;
}

ptr = &ptr->operator[](reference_token);
break;
}

case detail::value_t::array:
{
if (JSON_UNLIKELY(reference_token == "-"))
{
// "-" always fails the range check
return false;
}

// error condition (cf. RFC 6901, Sect. 4)
if (JSON_UNLIKELY(reference_token.size() > 1 and reference_token[0] == '0'))
{
JSON_THROW(detail::parse_error::create(106, 0,
"array index '" + reference_token +
"' must not begin with '0'"));
}

JSON_TRY
{
const auto idx = static_cast<size_type>(array_index(reference_token));
if (idx >= ptr->size())
{
// index out of range
return false;
}

ptr = &ptr->operator[](idx);
break;
}
JSON_CATCH(std::invalid_argument&)
{
JSON_THROW(detail::parse_error::create(109, 0, "array index '" + reference_token + "' is not a number"));
}
break;
}

default:
{
// we do not expect primitive values if there is still a
// reference token to process
return false;
}
}
}

// no reference token left means we found a primitive value
return true;
}

/*!
@brief split the string input to reference tokens
Expand Down Expand Up @@ -813,7 +885,7 @@ class json_pointer
const BasicJsonType& value,
BasicJsonType& result)
{
switch (value.m_type)
switch (value.type())
{
case detail::value_t::array:
{
Expand Down
37 changes: 35 additions & 2 deletions include/nlohmann/json.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -4001,15 +4001,48 @@ class basic_json
@liveexample{The following code shows an example for `contains()`.,contains}
@sa @ref find(KeyT&&) -- returns an iterator to an object element
@sa @ref contains(const json_pointer&) const -- checks the existence for a JSON pointer
@since version 3.6.0
*/
template<typename KeyT>
bool contains(KeyT&& key) const
template<typename KeyT, typename std::enable_if<
not std::is_same<KeyT, json_pointer>::value, int>::type = 0>
bool contains(KeyT && key) const
{
return is_object() and m_value.object->find(std::forward<KeyT>(key)) != m_value.object->end();
}

/*!
@brief check the existence of an element in a JSON object given a JSON pointer
Check wehther the given JSON pointer @a ptr can be resolved in the current
JSON value.
@note This method can be executed on any JSON value type.
@param[in] ptr JSON pointer to check its existence.
@return true if the JSON pointer can be resolved to a stored value, false
otherwise.
@post If `j.contains(ptr)` returns true, it is safe to call `j[ptr]`.
@throw parse_error.106 if an array index begins with '0'
@throw parse_error.109 if an array index was not a number
@complexity Logarithmic in the size of the JSON object.
@liveexample{The following code shows an example for `contains()`.,contains_json_pointer}
@sa @ref contains(KeyT &&) const -- checks the existence of a key
@since version 3.7.0
*/
bool contains(const json_pointer& ptr) const
{
return ptr.contains(this);
}

/// @}


Expand Down
Loading

0 comments on commit 104c5c1

Please sign in to comment.