Update json from upstream version 3.1.2.

This adds support for ubjson and makes a number of bugfixes.

Binary input and output have switched from strings to uint8_t arrays.
This commit is contained in:
Peter Johnson
2018-05-14 22:53:22 -07:00
parent c274d1790f
commit f8ed48af98
20 changed files with 8915 additions and 6854 deletions

View File

@@ -1,5 +1,5 @@
/*----------------------------------------------------------------------------*/
/* Modifications Copyright (c) FIRST 2017. All Rights Reserved. */
/* Modifications Copyright (c) 2017-2018 FIRST. All Rights Reserved. */
/* Open Source Software - may be modified and shared by FRC teams. The code */
/* must be accompanied by the FIRST BSD license file in the root directory of */
/* the project. */
@@ -7,11 +7,11 @@
/*
__ _____ _____ _____
__| | __| | | | JSON for Modern C++
| | |__ | | | | | | version 2.1.1
| | |__ | | | | | | version 3.1.2
|_____|_____|_____|_|___| https://github.com/nlohmann/json
Licensed under the MIT License <http://opensource.org/licenses/MIT>.
Copyright (c) 2013-2017 Niels Lohmann <http://nlohmann.me>.
Copyright (c) 2013-2018 Niels Lohmann <http://nlohmann.me>.
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
@@ -34,32 +34,50 @@ SOFTWARE.
#define WPI_JSON_IMPLEMENTATION
#include "wpi/json.h"
#include <algorithm>
#include <numeric> // accumulate
using namespace wpi;
#include "wpi/SmallString.h"
std::string json::json_pointer::to_string() const noexcept
namespace wpi {
std::string json_pointer::to_string() const noexcept
{
return std::accumulate(reference_tokens.begin(),
reference_tokens.end(), std::string{},
return std::accumulate(reference_tokens.begin(), reference_tokens.end(),
std::string{},
[](const std::string & a, const std::string & b)
{
return a + "/" + escape(b);
});
}
json::reference json::json_pointer::get_and_create(reference j) const
int json_pointer::array_index(const Twine& s)
{
pointer result = &j;
SmallString<128> buf;
StringRef str = s.toNullTerminatedStringRef(buf);
std::size_t processed_chars = 0;
const int res = std::stoi(str, &processed_chars);
// in case no reference tokens exist, return a reference to the
// JSON value j which will be overwritten by a primitive value
// check if the string was completely read
if (JSON_UNLIKELY(processed_chars != str.size()))
{
JSON_THROW(detail::out_of_range::create(404, "unresolved reference token '" + Twine(s) + "'"));
}
return res;
}
json& json_pointer::get_and_create(json& j) const
{
using size_type = typename json::size_type;
auto result = &j;
// in case no reference tokens exist, return a reference to the JSON value
// j which will be overwritten by a primitive value
for (const auto& reference_token : reference_tokens)
{
switch (result->m_type)
{
case value_t::null:
case detail::value_t::null:
{
if (reference_token == "0")
{
@@ -74,86 +92,80 @@ json::reference json::json_pointer::get_and_create(reference j) const
break;
}
case value_t::object:
case detail::value_t::object:
{
// create an entry in the object
result = &result->operator[](reference_token);
break;
}
case value_t::array:
case detail::value_t::array:
{
// create an entry in the array
JSON_TRY
{
result = &result->operator[](static_cast<size_type>(std::stoi(reference_token)));
result = &result->operator[](static_cast<size_type>(array_index(reference_token)));
}
JSON_CATCH (std::invalid_argument&)
JSON_CATCH(std::invalid_argument&)
{
JSON_THROW(parse_error::create(109, 0, "array index '" + reference_token + "' is not a number"));
JSON_THROW(detail::parse_error::create(109, 0, "array index '" + Twine(reference_token) + "' is not a number"));
}
break;
}
/*
The following code is only reached if there exists a
reference token _and_ the current value is primitive. In
this case, we have an error situation, because primitive
values may only occur as single value; that is, with an
empty list of reference tokens.
The following code is only reached if there exists a reference
token _and_ the current value is primitive. In this case, we have
an error situation, because primitive values may only occur as
single value; that is, with an empty list of reference tokens.
*/
default:
{
JSON_THROW(type_error::create(313, "invalid value to unflatten"));
}
JSON_THROW(detail::type_error::create(313, "invalid value to unflatten"));
}
}
return *result;
}
json::reference json::json_pointer::get_unchecked(pointer ptr) const
json& json_pointer::get_unchecked(json* ptr) const
{
using size_type = typename json::size_type;
for (const auto& reference_token : reference_tokens)
{
// convert null values to arrays or objects before continuing
if (ptr->m_type == value_t::null)
if (ptr->m_type == detail::value_t::null)
{
// check if reference token is a number
const bool nums = std::all_of(reference_token.begin(),
reference_token.end(),
[](const char x)
const bool nums =
std::all_of(reference_token.begin(), reference_token.end(),
[](const char x)
{
return (x >= '0' && x <= '9');
return (x >= '0' and x <= '9');
});
// change value to array for numbers or "-" or to object
// otherwise
if (nums || reference_token == "-")
{
*ptr = value_t::array;
}
else
{
*ptr = value_t::object;
}
// change value to array for numbers or "-" or to object otherwise
*ptr = (nums or reference_token == "-")
? detail::value_t::array
: detail::value_t::object;
}
switch (ptr->m_type)
{
case value_t::object:
case detail::value_t::object:
{
// use unchecked object access
ptr = &ptr->operator[](reference_token);
break;
}
case value_t::array:
case detail::value_t::array:
{
// error condition (cf. RFC 6901, Sect. 4)
if (reference_token.size() > 1 && reference_token[0] == '0')
if (JSON_UNLIKELY(reference_token.size() > 1 and reference_token[0] == '0'))
{
JSON_THROW(parse_error::create(106, 0, "array index '" + reference_token + "' must not begin with '0'"));
JSON_THROW(detail::parse_error::create(106, 0,
"array index '" + Twine(reference_token) +
"' must not begin with '0'"));
}
if (reference_token == "-")
@@ -166,193 +178,200 @@ json::reference json::json_pointer::get_unchecked(pointer ptr) const
// convert array index to number; unchecked access
JSON_TRY
{
ptr = &ptr->operator[](static_cast<size_type>(std::stoi(reference_token)));
ptr = &ptr->operator[](
static_cast<size_type>(array_index(reference_token)));
}
JSON_CATCH (std::invalid_argument&)
JSON_CATCH(std::invalid_argument&)
{
JSON_THROW(parse_error::create(109, 0, "array index '" + reference_token + "' is not a number"));
JSON_THROW(detail::parse_error::create(109, 0, "array index '" + Twine(reference_token) + "' is not a number"));
}
}
break;
}
default:
{
JSON_THROW(out_of_range::create(404, "unresolved reference token '" + reference_token + "'"));
}
JSON_THROW(detail::out_of_range::create(404, "unresolved reference token '" + Twine(reference_token) + "'"));
}
}
return *ptr;
}
json::reference json::json_pointer::get_checked(pointer ptr) const
json& json_pointer::get_checked(json* ptr) const
{
using size_type = typename json::size_type;
for (const auto& reference_token : reference_tokens)
{
switch (ptr->m_type)
{
case value_t::object:
case detail::value_t::object:
{
// note: at performs range check
ptr = &ptr->at(reference_token);
break;
}
case value_t::array:
case detail::value_t::array:
{
if (reference_token == "-")
if (JSON_UNLIKELY(reference_token == "-"))
{
// "-" always fails the range check
JSON_THROW(out_of_range::create(402, "array index '-' (" +
std::to_string(ptr->m_value.array->size()) +
") is out of range"));
JSON_THROW(detail::out_of_range::create(402,
"array index '-' (" + Twine(ptr->m_value.array->size()) +
") is out of range"));
}
// error condition (cf. RFC 6901, Sect. 4)
if (reference_token.size() > 1 && reference_token[0] == '0')
if (JSON_UNLIKELY(reference_token.size() > 1 and reference_token[0] == '0'))
{
JSON_THROW(parse_error::create(106, 0, "array index '" + reference_token + "' must not begin with '0'"));
JSON_THROW(detail::parse_error::create(106, 0,
"array index '" + Twine(reference_token) +
"' must not begin with '0'"));
}
// note: at performs range check
JSON_TRY
{
ptr = &ptr->at(static_cast<size_type>(std::stoi(reference_token)));
ptr = &ptr->at(static_cast<size_type>(array_index(reference_token)));
}
JSON_CATCH (std::invalid_argument&)
JSON_CATCH(std::invalid_argument&)
{
JSON_THROW(parse_error::create(109, 0, "array index '" + reference_token + "' is not a number"));
JSON_THROW(detail::parse_error::create(109, 0, "array index '" + Twine(reference_token) + "' is not a number"));
}
break;
}
default:
{
JSON_THROW(out_of_range::create(404, "unresolved reference token '" + reference_token + "'"));
}
JSON_THROW(detail::out_of_range::create(404, "unresolved reference token '" + Twine(reference_token) + "'"));
}
}
return *ptr;
}
json::const_reference json::json_pointer::get_unchecked(const_pointer ptr) const
const json& json_pointer::get_unchecked(const json* ptr) const
{
using size_type = typename json::size_type;
for (const auto& reference_token : reference_tokens)
{
switch (ptr->m_type)
{
case value_t::object:
case detail::value_t::object:
{
// use unchecked object access
ptr = &ptr->operator[](reference_token);
break;
}
case value_t::array:
case detail::value_t::array:
{
if (reference_token == "-")
if (JSON_UNLIKELY(reference_token == "-"))
{
// "-" cannot be used for const access
JSON_THROW(out_of_range::create(402, "array index '-' (" +
std::to_string(ptr->m_value.array->size()) +
") is out of range"));
JSON_THROW(detail::out_of_range::create(402,
"array index '-' (" + Twine(ptr->m_value.array->size()) +
") is out of range"));
}
// error condition (cf. RFC 6901, Sect. 4)
if (reference_token.size() > 1 && reference_token[0] == '0')
if (JSON_UNLIKELY(reference_token.size() > 1 and reference_token[0] == '0'))
{
JSON_THROW(parse_error::create(106, 0, "array index '" + reference_token + "' must not begin with '0'"));
JSON_THROW(detail::parse_error::create(106, 0,
"array index '" + Twine(reference_token) +
"' must not begin with '0'"));
}
// use unchecked array access
JSON_TRY
{
ptr = &ptr->operator[](static_cast<size_type>(std::stoi(reference_token)));
ptr = &ptr->operator[](
static_cast<size_type>(array_index(reference_token)));
}
JSON_CATCH (std::invalid_argument&)
JSON_CATCH(std::invalid_argument&)
{
JSON_THROW(parse_error::create(109, 0, "array index '" + reference_token + "' is not a number"));
JSON_THROW(detail::parse_error::create(109, 0, "array index '" + Twine(reference_token) + "' is not a number"));
}
break;
}
default:
{
JSON_THROW(out_of_range::create(404, "unresolved reference token '" + reference_token + "'"));
}
JSON_THROW(detail::out_of_range::create(404, "unresolved reference token '" + Twine(reference_token) + "'"));
}
}
return *ptr;
}
json::const_reference json::json_pointer::get_checked(const_pointer ptr) const
const json& json_pointer::get_checked(const json* ptr) const
{
using size_type = typename json::size_type;
for (const auto& reference_token : reference_tokens)
{
switch (ptr->m_type)
{
case value_t::object:
case detail::value_t::object:
{
// note: at performs range check
ptr = &ptr->at(reference_token);
break;
}
case value_t::array:
case detail::value_t::array:
{
if (reference_token == "-")
if (JSON_UNLIKELY(reference_token == "-"))
{
// "-" always fails the range check
JSON_THROW(out_of_range::create(402, "array index '-' (" +
std::to_string(ptr->m_value.array->size()) +
") is out of range"));
JSON_THROW(detail::out_of_range::create(402,
"array index '-' (" + Twine(ptr->m_value.array->size()) +
") is out of range"));
}
// error condition (cf. RFC 6901, Sect. 4)
if (reference_token.size() > 1 && reference_token[0] == '0')
if (JSON_UNLIKELY(reference_token.size() > 1 and reference_token[0] == '0'))
{
JSON_THROW(parse_error::create(106, 0, "array index '" + reference_token + "' must not begin with '0'"));
JSON_THROW(detail::parse_error::create(106, 0,
"array index '" + Twine(reference_token) +
"' must not begin with '0'"));
}
// note: at performs range check
JSON_TRY
{
ptr = &ptr->at(static_cast<size_type>(std::stoi(reference_token)));
ptr = &ptr->at(static_cast<size_type>(array_index(reference_token)));
}
JSON_CATCH (std::invalid_argument&)
JSON_CATCH(std::invalid_argument&)
{
JSON_THROW(parse_error::create(109, 0, "array index '" + reference_token + "' is not a number"));
JSON_THROW(detail::parse_error::create(109, 0, "array index '" + Twine(reference_token) + "' is not a number"));
}
break;
}
default:
{
JSON_THROW(out_of_range::create(404, "unresolved reference token '" + reference_token + "'"));
}
JSON_THROW(detail::out_of_range::create(404, "unresolved reference token '" + Twine(reference_token) + "'"));
}
}
return *ptr;
}
std::vector<std::string> json::json_pointer::split(const std::string& reference_string)
std::vector<std::string> json_pointer::split(const Twine& reference_string)
{
SmallString<128> ref_str_buf;
StringRef ref_str = reference_string.toStringRef(ref_str_buf);
std::vector<std::string> result;
// special case: empty reference string -> no reference tokens
if (reference_string.empty())
if (ref_str.empty())
{
return result;
}
// check if nonempty reference string begins with slash
if (reference_string[0] != '/')
if (JSON_UNLIKELY(ref_str[0] != '/'))
{
JSON_THROW(parse_error::create(107, 1, "JSON pointer must be empty or begin with '/' - was: '" + reference_string + "'"));
JSON_THROW(detail::parse_error::create(107, 1,
"JSON pointer must be empty or begin with '/' - was: '" +
Twine(ref_str) + "'"));
}
// extract the reference tokens:
@@ -360,7 +379,7 @@ std::vector<std::string> json::json_pointer::split(const std::string& reference_
// - start: position after the previous slash
for (
// search for the first slash after the first character
size_t slash = reference_string.find_first_of('/', 1),
std::size_t slash = ref_str.find_first_of('/', 1),
// set the beginning of the first reference token
start = 1;
// we can stop if start == string::npos+1 = 0
@@ -369,120 +388,102 @@ std::vector<std::string> json::json_pointer::split(const std::string& reference_
// (will eventually be 0 if slash == std::string::npos)
start = slash + 1,
// find next slash
slash = reference_string.find_first_of('/', start))
slash = ref_str.find_first_of('/', start))
{
// use the text between the beginning of the reference token
// (start) and the last slash (slash).
auto reference_token = reference_string.substr(start, slash - start);
auto reference_token = ref_str.slice(start, slash);
// check reference tokens are properly escaped
for (size_t pos = reference_token.find_first_of('~');
pos != std::string::npos;
for (std::size_t pos = reference_token.find_first_of('~');
pos != StringRef::npos;
pos = reference_token.find_first_of('~', pos + 1))
{
assert(reference_token[pos] == '~');
// ~ must be followed by 0 or 1
if (pos == reference_token.size() - 1 ||
(reference_token[pos + 1] != '0' &&
reference_token[pos + 1] != '1'))
if (JSON_UNLIKELY(pos == reference_token.size() - 1 or
(reference_token[pos + 1] != '0' and
reference_token[pos + 1] != '1')))
{
JSON_THROW(parse_error::create(108, 0, "escape character '~' must be followed with '0' or '1'"));
JSON_THROW(detail::parse_error::create(108, 0, "escape character '~' must be followed with '0' or '1'"));
}
}
// finally, store the reference token
unescape(reference_token);
result.push_back(reference_token);
std::string ref_tok = reference_token;
unescape(ref_tok);
result.emplace_back(std::move(ref_tok));
}
return result;
}
/*!
@brief replace all occurrences of a substring by another string
@param[in,out] s the string to manipulate; changed so that all
occurrences of @a f are replaced with @a t
@param[in] f the substring to replace with @a t
@param[in] t the string to replace @a f
@pre The search string @a f must not be empty. **This precondition is
enforced with an assertion.**
@since version 2.0.0
*/
void json::json_pointer::replace_substring(std::string& s,
const std::string& f,
void json_pointer::replace_substring(std::string& s, const std::string& f,
const std::string& t)
{
assert(!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
);
assert(not f.empty());
for (auto 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, and
pos = s.find(f, pos + t.size())) // find next occurrence of f
{}
}
/// escape tilde and slash
std::string json::json_pointer::escape(std::string s)
std::string json_pointer::escape(std::string s)
{
// escape "~"" to "~0" and "/" to "~1"
replace_substring(s, "~", "~0");
replace_substring(s, "/", "~1");
return s;
}
/// unescape tilde and slash
void json::json_pointer::unescape(std::string& s)
/// unescape "~1" to tilde and "~0" to slash (order is important!)
void json_pointer::unescape(std::string& s)
{
// first transform any occurrence of the sequence '~1' to '/'
replace_substring(s, "~1", "/");
// then transform any occurrence of the sequence '~0' to '~'
replace_substring(s, "~0", "~");
}
void json::json_pointer::flatten(const std::string& reference_string,
void json_pointer::flatten(const Twine& reference_string,
const json& value,
json& result)
{
switch (value.m_type)
{
case value_t::array:
case detail::value_t::array:
{
if (value.m_value.array->empty())
{
// flatten empty array as null
result[reference_string] = nullptr;
SmallString<64> buf;
result[reference_string.toStringRef(buf)] = nullptr;
}
else
{
// iterate array and use index as reference string
for (size_t i = 0; i < value.m_value.array->size(); ++i)
for (std::size_t i = 0; i < value.m_value.array->size(); ++i)
{
flatten(reference_string + "/" + std::to_string(i),
flatten(reference_string + "/" + Twine(i),
value.m_value.array->operator[](i), result);
}
}
break;
}
case value_t::object:
case detail::value_t::object:
{
if (value.m_value.object->empty())
{
// flatten empty object as null
result[reference_string] = nullptr;
SmallString<64> buf;
result[reference_string.toStringRef(buf)] = nullptr;
}
else
{
// iterate object and use keys as reference string
for (const auto& element : *value.m_value.object)
{
flatten(reference_string + "/" + escape(element.first()),
element.second, result);
flatten(reference_string + "/" + Twine(escape(element.first())), element.second, result);
}
}
break;
@@ -491,17 +492,19 @@ void json::json_pointer::flatten(const std::string& reference_string,
default:
{
// add primitive value with its reference string
result[reference_string] = value;
SmallString<64> buf;
result[reference_string.toStringRef(buf)] = value;
break;
}
}
}
json json::json_pointer::unflatten(const json& value)
json
json_pointer::unflatten(const json& value)
{
if (!value.is_object())
if (JSON_UNLIKELY(not value.is_object()))
{
JSON_THROW(type_error::create(314, "only objects can be unflattened"));
JSON_THROW(detail::type_error::create(314, "only objects can be unflattened"));
}
// we need to iterate over the object values in sorted key order
@@ -511,7 +514,7 @@ json json::json_pointer::unflatten(const json& value)
{
if (!i->second.is_primitive())
{
JSON_THROW(type_error::create(315, "values in object must be primitive"));
JSON_THROW(detail::type_error::create(315, "values in object must be primitive"));
}
sorted.push_back(i);
}
@@ -538,3 +541,4 @@ json json::json_pointer::unflatten(const json& value)
return result;
}
} // namespace wpi