Add json support to wpiutil.

This is a modified version of https://github.com/nlohmann/json.

It's been moved into the wpi namespace as many of the changes are not
compatible.  The amount of template code has been significantly reduced,
enabling many functions to be moved out-of-line, and for the result to
build on older compiler versions (in particular GCC 4.8).
This commit is contained in:
Peter Johnson
2017-06-15 13:22:55 -07:00
parent de9dd1180b
commit 301442ee43
35 changed files with 28635 additions and 2 deletions

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,668 @@
/*----------------------------------------------------------------------------*/
/* Modifications Copyright (c) FIRST 2017. 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. */
/*----------------------------------------------------------------------------*/
/*
__ _____ _____ _____
__| | __| | | | JSON for Modern C++
| | |__ | | | | | | version 2.1.1
|_____|_____|_____|_|___| https://github.com/nlohmann/json
Licensed under the MIT License <http://opensource.org/licenses/MIT>.
Copyright (c) 2013-2017 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
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
*/
#define WPI_JSON_IMPLEMENTATION
#include "support/json.h"
#include <array>
#include <clocale> // lconv, localeconv
#include <locale> // locale
#include <numeric> // accumulate
#include "llvm/raw_ostream.h"
#include "llvm/SmallString.h"
#include "llvm/StringExtras.h"
using namespace wpi;
/*!
@brief serialization to CBOR and MessagePack values
*/
class json::binary_writer
{
public:
/*!
@brief create a binary writer
@param[in] adapter output adapter to write to
*/
explicit binary_writer(llvm::raw_ostream& s)
: is_little_endian(little_endianess()), o(s)
{
}
/*!
@brief[in] j JSON value to serialize
*/
void write_cbor(const json& j);
/*!
@brief[in] j JSON value to serialize
*/
void write_msgpack(const json& j);
/*!
@brief determine system byte order
@return true iff system's byte order is little endian
@note from http://stackoverflow.com/a/1001328/266378
*/
static bool little_endianess() noexcept
{
int num = 1;
return (*reinterpret_cast<char*>(&num) == 1);
}
private:
/*!
@brief[in] str string to serialize
*/
void write_cbor_string(llvm::StringRef str);
/*!
@brief[in] str string to serialize
*/
void write_msgpack_string(llvm::StringRef str);
/*
@brief write a number to output input
@param[in] n number of type @a T
@tparam T the type of the number
@note This function needs to respect the system's endianess, because
bytes in CBOR and MessagePack are stored in network order (big
endian) and therefore need reordering on little endian systems.
*/
template<typename T>
void write_number(T n)
{
// step 1: write number to array of length T
std::array<uint8_t, sizeof(T)> vec;
std::memcpy(vec.data(), &n, sizeof(T));
// step 2: write array to output (with possible reordering)
for (size_t i = 0; i < sizeof(T); ++i)
{
// reverse byte order prior to conversion if necessary
if (is_little_endian)
{
o << static_cast<unsigned char>(vec[sizeof(T) - i - 1]);
}
else
{
o << static_cast<unsigned char>(vec[i]);
}
}
}
private:
/// whether we can assume little endianess
const bool is_little_endian = true;
/// the output
llvm::raw_ostream& o;
};
void json::binary_writer::write_cbor(const json& j)
{
switch (j.type())
{
case value_t::null:
{
o << static_cast<unsigned char>(0xf6);
break;
}
case value_t::boolean:
{
o << static_cast<unsigned char>(j.m_value.boolean ? 0xf5 : 0xf4);
break;
}
case value_t::number_integer:
{
if (j.m_value.number_integer >= 0)
{
// CBOR does not differentiate between positive signed
// integers and unsigned integers. Therefore, we used the
// code from the value_t::number_unsigned case here.
if (j.m_value.number_integer <= 0x17)
{
write_number(static_cast<uint8_t>(j.m_value.number_integer));
}
else if (j.m_value.number_integer <= (std::numeric_limits<uint8_t>::max)())
{
o << static_cast<unsigned char>(0x18);
write_number(static_cast<uint8_t>(j.m_value.number_integer));
}
else if (j.m_value.number_integer <= (std::numeric_limits<uint16_t>::max)())
{
o << static_cast<unsigned char>(0x19);
write_number(static_cast<uint16_t>(j.m_value.number_integer));
}
else if (j.m_value.number_integer <= (std::numeric_limits<uint32_t>::max)())
{
o << static_cast<unsigned char>(0x1a);
write_number(static_cast<uint32_t>(j.m_value.number_integer));
}
else
{
o << static_cast<unsigned char>(0x1b);
write_number(static_cast<uint64_t>(j.m_value.number_integer));
}
}
else
{
// The conversions below encode the sign in the first
// byte, and the value is converted to a positive number.
const auto positive_number = -1 - j.m_value.number_integer;
if (j.m_value.number_integer >= -24)
{
write_number(static_cast<uint8_t>(0x20 + positive_number));
}
else if (positive_number <= (std::numeric_limits<uint8_t>::max)())
{
o << static_cast<unsigned char>(0x38);
write_number(static_cast<uint8_t>(positive_number));
}
else if (positive_number <= (std::numeric_limits<uint16_t>::max)())
{
o << static_cast<unsigned char>(0x39);
write_number(static_cast<uint16_t>(positive_number));
}
else if (positive_number <= (std::numeric_limits<uint32_t>::max)())
{
o << static_cast<unsigned char>(0x3a);
write_number(static_cast<uint32_t>(positive_number));
}
else
{
o << static_cast<unsigned char>(0x3b);
write_number(static_cast<uint64_t>(positive_number));
}
}
break;
}
case value_t::number_unsigned:
{
if (j.m_value.number_unsigned <= 0x17)
{
write_number(static_cast<uint8_t>(j.m_value.number_unsigned));
}
else if (j.m_value.number_unsigned <= (std::numeric_limits<uint8_t>::max)())
{
o << static_cast<unsigned char>(0x18);
write_number(static_cast<uint8_t>(j.m_value.number_unsigned));
}
else if (j.m_value.number_unsigned <= (std::numeric_limits<uint16_t>::max)())
{
o << static_cast<unsigned char>(0x19);
write_number(static_cast<uint16_t>(j.m_value.number_unsigned));
}
else if (j.m_value.number_unsigned <= (std::numeric_limits<uint32_t>::max)())
{
o << static_cast<unsigned char>(0x1a);
write_number(static_cast<uint32_t>(j.m_value.number_unsigned));
}
else
{
o << static_cast<unsigned char>(0x1b);
write_number(static_cast<uint64_t>(j.m_value.number_unsigned));
}
break;
}
case value_t::number_float:
{
// Double-Precision Float
o << static_cast<unsigned char>(0xfb);
write_number(j.m_value.number_float);
break;
}
case value_t::string:
{
write_cbor_string(*j.m_value.string);
break;
}
case value_t::array:
{
// step 1: write control byte and the array size
const auto N = j.m_value.array->size();
if (N <= 0x17)
{
write_number(static_cast<uint8_t>(0x80 + N));
}
else if (N <= 0xff)
{
o << static_cast<unsigned char>(0x98);
write_number(static_cast<uint8_t>(N));
}
else if (N <= 0xffff)
{
o << static_cast<unsigned char>(0x99);
write_number(static_cast<uint16_t>(N));
}
else if (N <= 0xffffffff)
{
o << static_cast<unsigned char>(0x9a);
write_number(static_cast<uint32_t>(N));
}
// LCOV_EXCL_START
else if (N <= 0xffffffffffffffff)
{
o << static_cast<unsigned char>(0x9b);
write_number(static_cast<uint64_t>(N));
}
// LCOV_EXCL_STOP
// step 2: write each element
for (const auto& el : *j.m_value.array)
{
write_cbor(el);
}
break;
}
case value_t::object:
{
// step 1: write control byte and the object size
const auto N = j.m_value.object->size();
if (N <= 0x17)
{
write_number(static_cast<uint8_t>(0xa0 + N));
}
else if (N <= 0xff)
{
o << static_cast<unsigned char>(0xb8);
write_number(static_cast<uint8_t>(N));
}
else if (N <= 0xffff)
{
o << static_cast<unsigned char>(0xb9);
write_number(static_cast<uint16_t>(N));
}
else if (N <= 0xffffffff)
{
o << static_cast<unsigned char>(0xba);
write_number(static_cast<uint32_t>(N));
}
// LCOV_EXCL_START
else if (N <= 0xffffffffffffffff)
{
o << static_cast<unsigned char>(0xbb);
write_number(static_cast<uint64_t>(N));
}
// LCOV_EXCL_STOP
// step 2: write each element
for (const auto& el : *j.m_value.object)
{
write_cbor_string(el.first());
write_cbor(el.second);
}
break;
}
default:
{
break;
}
}
}
void json::binary_writer::write_cbor_string(llvm::StringRef str)
{
// step 1: write control byte and the string length
const auto N = str.size();
if (N <= 0x17)
{
write_number(static_cast<uint8_t>(0x60 + N));
}
else if (N <= 0xff)
{
o << static_cast<unsigned char>(0x78);
write_number(static_cast<uint8_t>(N));
}
else if (N <= 0xffff)
{
o << static_cast<unsigned char>(0x79);
write_number(static_cast<uint16_t>(N));
}
else if (N <= 0xffffffff)
{
o << static_cast<unsigned char>(0x7a);
write_number(static_cast<uint32_t>(N));
}
// LCOV_EXCL_START
else if (N <= 0xffffffffffffffff)
{
o << static_cast<unsigned char>(0x7b);
write_number(static_cast<uint64_t>(N));
}
// LCOV_EXCL_STOP
// step 2: write the string
o << str;
}
void json::binary_writer::write_msgpack(const json& j)
{
switch (j.type())
{
case value_t::null:
{
// nil
o << static_cast<unsigned char>(0xc0);
break;
}
case value_t::boolean:
{
// true and false
o << static_cast<unsigned char>(j.m_value.boolean ? 0xc3 : 0xc2);
break;
}
case value_t::number_integer:
{
if (j.m_value.number_integer >= 0)
{
// MessagePack does not differentiate between positive
// signed integers and unsigned integers. Therefore, we
// used the code from the value_t::number_unsigned case
// here.
if (j.m_value.number_unsigned < 128)
{
// positive fixnum
write_number(static_cast<uint8_t>(j.m_value.number_integer));
}
else if (j.m_value.number_unsigned <= (std::numeric_limits<uint8_t>::max)())
{
// uint 8
o << static_cast<unsigned char>(0xcc);
write_number(static_cast<uint8_t>(j.m_value.number_integer));
}
else if (j.m_value.number_unsigned <= (std::numeric_limits<uint16_t>::max)())
{
// uint 16
o << static_cast<unsigned char>(0xcd);
write_number(static_cast<uint16_t>(j.m_value.number_integer));
}
else if (j.m_value.number_unsigned <= (std::numeric_limits<uint32_t>::max)())
{
// uint 32
o << static_cast<unsigned char>(0xce);
write_number(static_cast<uint32_t>(j.m_value.number_integer));
}
else if (j.m_value.number_unsigned <= (std::numeric_limits<uint64_t>::max)())
{
// uint 64
o << static_cast<unsigned char>(0xcf);
write_number(static_cast<uint64_t>(j.m_value.number_integer));
}
}
else
{
if (j.m_value.number_integer >= -32)
{
// negative fixnum
write_number(static_cast<int8_t>(j.m_value.number_integer));
}
else if (j.m_value.number_integer >= (std::numeric_limits<int8_t>::min)() && j.m_value.number_integer <= (std::numeric_limits<int8_t>::max)())
{
// int 8
o << static_cast<unsigned char>(0xd0);
write_number(static_cast<int8_t>(j.m_value.number_integer));
}
else if (j.m_value.number_integer >= (std::numeric_limits<int16_t>::min)() && j.m_value.number_integer <= (std::numeric_limits<int16_t>::max)())
{
// int 16
o << static_cast<unsigned char>(0xd1);
write_number(static_cast<int16_t>(j.m_value.number_integer));
}
else if (j.m_value.number_integer >= (std::numeric_limits<int32_t>::min)() && j.m_value.number_integer <= (std::numeric_limits<int32_t>::max)())
{
// int 32
o << static_cast<unsigned char>(0xd2);
write_number(static_cast<int32_t>(j.m_value.number_integer));
}
else if (j.m_value.number_integer >= (std::numeric_limits<int64_t>::min)() && j.m_value.number_integer <= (std::numeric_limits<int64_t>::max)())
{
// int 64
o << static_cast<unsigned char>(0xd3);
write_number(static_cast<int64_t>(j.m_value.number_integer));
}
}
break;
}
case value_t::number_unsigned:
{
if (j.m_value.number_unsigned < 128)
{
// positive fixnum
write_number(static_cast<uint8_t>(j.m_value.number_integer));
}
else if (j.m_value.number_unsigned <= (std::numeric_limits<uint8_t>::max)())
{
// uint 8
o << static_cast<unsigned char>(0xcc);
write_number(static_cast<uint8_t>(j.m_value.number_integer));
}
else if (j.m_value.number_unsigned <= (std::numeric_limits<uint16_t>::max)())
{
// uint 16
o << static_cast<unsigned char>(0xcd);
write_number(static_cast<uint16_t>(j.m_value.number_integer));
}
else if (j.m_value.number_unsigned <= (std::numeric_limits<uint32_t>::max)())
{
// uint 32
o << static_cast<unsigned char>(0xce);
write_number(static_cast<uint32_t>(j.m_value.number_integer));
}
else if (j.m_value.number_unsigned <= (std::numeric_limits<uint64_t>::max)())
{
// uint 64
o << static_cast<unsigned char>(0xcf);
write_number(static_cast<uint64_t>(j.m_value.number_integer));
}
break;
}
case value_t::number_float:
{
// float 64
o << static_cast<unsigned char>(0xcb);
write_number(j.m_value.number_float);
break;
}
case value_t::string:
{
write_msgpack_string(*j.m_value.string);
break;
}
case value_t::array:
{
// step 1: write control byte and the array size
const auto N = j.m_value.array->size();
if (N <= 15)
{
// fixarray
write_number(static_cast<uint8_t>(0x90 | N));
}
else if (N <= 0xffff)
{
// array 16
o << static_cast<unsigned char>(0xdc);
write_number(static_cast<uint16_t>(N));
}
else if (N <= 0xffffffff)
{
// array 32
o << static_cast<unsigned char>(0xdd);
write_number(static_cast<uint32_t>(N));
}
// step 2: write each element
for (const auto& el : *j.m_value.array)
{
write_msgpack(el);
}
break;
}
case value_t::object:
{
// step 1: write control byte and the object size
const auto N = j.m_value.object->size();
if (N <= 15)
{
// fixmap
write_number(static_cast<uint8_t>(0x80 | (N & 0xf)));
}
else if (N <= 65535)
{
// map 16
o << static_cast<unsigned char>(0xde);
write_number(static_cast<uint16_t>(N));
}
else if (N <= 4294967295)
{
// map 32
o << static_cast<unsigned char>(0xdf);
write_number(static_cast<uint32_t>(N));
}
// step 2: write each element
for (const auto& el : *j.m_value.object)
{
write_msgpack_string(el.first());
write_msgpack(el.second);
}
break;
}
default:
{
break;
}
}
}
void json::binary_writer::write_msgpack_string(llvm::StringRef str)
{
// step 1: write control byte and the string length
const auto N = str.size();
if (N <= 31)
{
// fixstr
write_number(static_cast<uint8_t>(0xa0 | N));
}
else if (N <= 255)
{
// str 8
o << static_cast<unsigned char>(0xd9);
write_number(static_cast<uint8_t>(N));
}
else if (N <= 65535)
{
// str 16
o << static_cast<unsigned char>(0xda);
write_number(static_cast<uint16_t>(N));
}
else if (N <= 4294967295)
{
// str 32
o << static_cast<unsigned char>(0xdb);
write_number(static_cast<uint32_t>(N));
}
// step 2: write the string
o << str;
}
void json::to_cbor(llvm::raw_ostream& os, const json& j)
{
binary_writer bw(os);
bw.write_cbor(j);
}
llvm::StringRef json::to_cbor(const json& j, llvm::SmallVectorImpl<char> buf)
{
llvm::raw_svector_ostream os(buf);
binary_writer bw(os);
bw.write_cbor(j);
return os.str();
}
std::string json::to_cbor(const json& j)
{
std::string s;
llvm::raw_string_ostream os(s);
binary_writer bw(os);
bw.write_cbor(j);
os.flush();
return s;
}
void json::to_msgpack(llvm::raw_ostream& os, const json& j)
{
binary_writer bw(os);
bw.write_msgpack(j);
}
llvm::StringRef json::to_msgpack(const json& j, llvm::SmallVectorImpl<char> buf)
{
llvm::raw_svector_ostream os(buf);
binary_writer bw(os);
bw.write_msgpack(j);
return os.str();
}
std::string json::to_msgpack(const json& j)
{
std::string s;
llvm::raw_string_ostream os(s);
binary_writer bw(os);
bw.write_msgpack(j);
os.flush();
return s;
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,540 @@
/*----------------------------------------------------------------------------*/
/* Modifications Copyright (c) FIRST 2017. 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. */
/*----------------------------------------------------------------------------*/
/*
__ _____ _____ _____
__| | __| | | | JSON for Modern C++
| | |__ | | | | | | version 2.1.1
|_____|_____|_____|_|___| https://github.com/nlohmann/json
Licensed under the MIT License <http://opensource.org/licenses/MIT>.
Copyright (c) 2013-2017 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
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
*/
#define WPI_JSON_IMPLEMENTATION
#include "support/json.h"
#include <algorithm>
#include <numeric> // accumulate
using namespace wpi;
std::string json::json_pointer::to_string() const noexcept
{
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
{
pointer 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:
{
if (reference_token == "0")
{
// start a new array if reference token is 0
result = &result->operator[](0);
}
else
{
// start a new object otherwise
result = &result->operator[](reference_token);
}
break;
}
case value_t::object:
{
// create an entry in the object
result = &result->operator[](reference_token);
break;
}
case value_t::array:
{
// create an entry in the array
JSON_TRY
{
result = &result->operator[](static_cast<size_type>(std::stoi(reference_token)));
}
JSON_CATCH (std::invalid_argument&)
{
JSON_THROW(parse_error::create(109, 0, "array index '" + 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.
*/
default:
{
JSON_THROW(type_error::create(313, "invalid value to unflatten"));
}
}
}
return *result;
}
json::reference json::json_pointer::get_unchecked(pointer ptr) const
{
for (const auto& reference_token : reference_tokens)
{
// convert null values to arrays or objects before continuing
if (ptr->m_type == 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)
{
return (x >= '0' && 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;
}
}
switch (ptr->m_type)
{
case value_t::object:
{
// use unchecked object access
ptr = &ptr->operator[](reference_token);
break;
}
case value_t::array:
{
// error condition (cf. RFC 6901, Sect. 4)
if (reference_token.size() > 1 && reference_token[0] == '0')
{
JSON_THROW(parse_error::create(106, 0, "array index '" + reference_token + "' must not begin with '0'"));
}
if (reference_token == "-")
{
// explicitly treat "-" as index beyond the end
ptr = &ptr->operator[](ptr->m_value.array->size());
}
else
{
// convert array index to number; unchecked access
JSON_TRY
{
ptr = &ptr->operator[](static_cast<size_type>(std::stoi(reference_token)));
}
JSON_CATCH (std::invalid_argument&)
{
JSON_THROW(parse_error::create(109, 0, "array index '" + reference_token + "' is not a number"));
}
}
break;
}
default:
{
JSON_THROW(out_of_range::create(404, "unresolved reference token '" + reference_token + "'"));
}
}
}
return *ptr;
}
json::reference json::json_pointer::get_checked(pointer ptr) const
{
for (const auto& reference_token : reference_tokens)
{
switch (ptr->m_type)
{
case value_t::object:
{
// note: at performs range check
ptr = &ptr->at(reference_token);
break;
}
case value_t::array:
{
if (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"));
}
// error condition (cf. RFC 6901, Sect. 4)
if (reference_token.size() > 1 && reference_token[0] == '0')
{
JSON_THROW(parse_error::create(106, 0, "array index '" + 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)));
}
JSON_CATCH (std::invalid_argument&)
{
JSON_THROW(parse_error::create(109, 0, "array index '" + reference_token + "' is not a number"));
}
break;
}
default:
{
JSON_THROW(out_of_range::create(404, "unresolved reference token '" + reference_token + "'"));
}
}
}
return *ptr;
}
json::const_reference json::json_pointer::get_unchecked(const_pointer ptr) const
{
for (const auto& reference_token : reference_tokens)
{
switch (ptr->m_type)
{
case value_t::object:
{
// use unchecked object access
ptr = &ptr->operator[](reference_token);
break;
}
case value_t::array:
{
if (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"));
}
// error condition (cf. RFC 6901, Sect. 4)
if (reference_token.size() > 1 && reference_token[0] == '0')
{
JSON_THROW(parse_error::create(106, 0, "array index '" + reference_token + "' must not begin with '0'"));
}
// use unchecked array access
JSON_TRY
{
ptr = &ptr->operator[](static_cast<size_type>(std::stoi(reference_token)));
}
JSON_CATCH (std::invalid_argument&)
{
JSON_THROW(parse_error::create(109, 0, "array index '" + reference_token + "' is not a number"));
}
break;
}
default:
{
JSON_THROW(out_of_range::create(404, "unresolved reference token '" + reference_token + "'"));
}
}
}
return *ptr;
}
json::const_reference json::json_pointer::get_checked(const_pointer ptr) const
{
for (const auto& reference_token : reference_tokens)
{
switch (ptr->m_type)
{
case value_t::object:
{
// note: at performs range check
ptr = &ptr->at(reference_token);
break;
}
case value_t::array:
{
if (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"));
}
// error condition (cf. RFC 6901, Sect. 4)
if (reference_token.size() > 1 && reference_token[0] == '0')
{
JSON_THROW(parse_error::create(106, 0, "array index '" + 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)));
}
JSON_CATCH (std::invalid_argument&)
{
JSON_THROW(parse_error::create(109, 0, "array index '" + reference_token + "' is not a number"));
}
break;
}
default:
{
JSON_THROW(out_of_range::create(404, "unresolved reference token '" + reference_token + "'"));
}
}
}
return *ptr;
}
std::vector<std::string> json::json_pointer::split(const std::string& reference_string)
{
std::vector<std::string> result;
// special case: empty reference string -> no reference tokens
if (reference_string.empty())
{
return result;
}
// check if nonempty reference string begins with slash
if (reference_string[0] != '/')
{
JSON_THROW(parse_error::create(107, 1, "JSON pointer must be empty or begin with '/' - was: '" + reference_string + "'"));
}
// extract the reference tokens:
// - slash: position of the last read slash (or end of string)
// - 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),
// set the beginning of the first reference token
start = 1;
// we can stop if start == string::npos+1 = 0
start != 0;
// set the beginning of the next reference token
// (will eventually be 0 if slash == std::string::npos)
start = slash + 1,
// find next slash
slash = reference_string.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);
// check reference tokens are properly escaped
for (size_t pos = reference_token.find_first_of('~');
pos != std::string::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'))
{
JSON_THROW(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);
}
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,
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
);
}
/// escape tilde and slash
std::string json::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)
{
// 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,
const json& value,
json& result)
{
switch (value.m_type)
{
case value_t::array:
{
if (value.m_value.array->empty())
{
// flatten empty array as null
result[reference_string] = nullptr;
}
else
{
// iterate array and use index as reference string
for (size_t i = 0; i < value.m_value.array->size(); ++i)
{
flatten(reference_string + "/" + std::to_string(i),
value.m_value.array->operator[](i), result);
}
}
break;
}
case value_t::object:
{
if (value.m_value.object->empty())
{
// flatten empty object as null
result[reference_string] = 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);
}
}
break;
}
default:
{
// add primitive value with its reference string
result[reference_string] = value;
break;
}
}
}
json json::json_pointer::unflatten(const json& value)
{
if (!value.is_object())
{
JSON_THROW(type_error::create(314, "only objects can be unflattened"));
}
// we need to iterate over the object values in sorted key order
llvm::SmallVector<llvm::StringMapConstIterator<json>, 64> sorted;
for (auto i = value.m_value.object->begin(),
end = value.m_value.object->end(); i != end; ++i)
{
if (!i->second.is_primitive())
{
JSON_THROW(type_error::create(315, "values in object must be primitive"));
}
sorted.push_back(i);
}
std::sort(sorted.begin(), sorted.end(),
[](const llvm::StringMapConstIterator<json>& a,
const llvm::StringMapConstIterator<json>& b) {
return a->getKey() < b->getKey();
});
json result;
// iterate the sorted JSON object values
for (const auto& element : sorted)
{
// assign value to reference pointed to by JSON pointer; Note
// that if the JSON pointer is "" (i.e., points to the whole
// value), function get_and_create returns a reference to
// result itself. An assignment will then create a primitive
// value.
json_pointer(element->first()).get_and_create(result) = element->second;
}
return result;
}

View File

@@ -0,0 +1,433 @@
/*----------------------------------------------------------------------------*/
/* Modifications Copyright (c) FIRST 2017. 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. */
/*----------------------------------------------------------------------------*/
/*
__ _____ _____ _____
__| | __| | | | JSON for Modern C++
| | |__ | | | | | | version 2.1.1
|_____|_____|_____|_|___| https://github.com/nlohmann/json
Licensed under the MIT License <http://opensource.org/licenses/MIT>.
Copyright (c) 2013-2017 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
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
*/
#define WPI_JSON_IMPLEMENTATION
#include "support/json.h"
#include "llvm/SmallString.h"
#include "llvm/StringExtras.h"
#include "json_serializer.h"
using namespace wpi;
void json::serializer::dump(const json& val,
const bool pretty_print,
const unsigned int indent_step,
const unsigned int current_indent)
{
switch (val.m_type)
{
case value_t::object:
{
if (val.m_value.object->empty())
{
o << "{}";
return;
}
if (pretty_print)
{
o << "{\n";
// variable to hold indentation for recursive calls
const auto new_indent = current_indent + indent_step;
// first n-1 elements
auto i = val.m_value.object->begin();
for (size_t cnt = 0; cnt < val.m_value.object->size() - 1; ++cnt, ++i)
{
o.indent(new_indent);
o << '\"';
dump_escaped(i->first());
o << "\": ";
dump(i->second, true, indent_step, new_indent);
o << ",\n";
}
// last element
assert(i != val.m_value.object->end());
o.indent(new_indent);
o << '\"';
dump_escaped(i->first());
o << "\": ";
dump(i->second, true, indent_step, new_indent);
o << '\n';
o.indent(current_indent);
o << '}';
}
else
{
o << '{';
// first n-1 elements
auto i = val.m_value.object->begin();
for (size_t cnt = 0; cnt < val.m_value.object->size() - 1; ++cnt, ++i)
{
o << '\"';
dump_escaped(i->first());
o << "\":";
dump(i->second, false, indent_step, current_indent);
o << ',';
}
// last element
assert(i != val.m_value.object->end());
o << '\"';
dump_escaped(i->first());
o << "\":";
dump(i->second, false, indent_step, current_indent);
o << '}';
}
return;
}
case value_t::array:
{
if (val.m_value.array->empty())
{
o << "[]";
return;
}
if (pretty_print)
{
o << "[\n";
// variable to hold indentation for recursive calls
const auto new_indent = current_indent + indent_step;
// first n-1 elements
for (auto i = val.m_value.array->cbegin(); i != val.m_value.array->cend() - 1; ++i)
{
o.indent(new_indent);
dump(*i, true, indent_step, new_indent);
o << ",\n";
}
// last element
assert(!val.m_value.array->empty());
o.indent(new_indent);
dump(val.m_value.array->back(), true, indent_step, new_indent);
o << '\n';
o.indent(current_indent);
o << ']';
}
else
{
o << '[';
// first n-1 elements
for (auto i = val.m_value.array->cbegin(); i != val.m_value.array->cend() - 1; ++i)
{
dump(*i, false, indent_step, current_indent);
o << ',';
}
// last element
assert(!val.m_value.array->empty());
dump(val.m_value.array->back(), false, indent_step, current_indent);
o << ']';
}
return;
}
case value_t::string:
{
o << '\"';
dump_escaped(*val.m_value.string);
o << '\"';
return;
}
case value_t::boolean:
{
if (val.m_value.boolean)
{
o << "true";
}
else
{
o << "false";
}
return;
}
case value_t::number_integer:
{
o << static_cast<long long>(val.m_value.number_integer);
return;
}
case value_t::number_unsigned:
{
o << static_cast<unsigned long long>(val.m_value.number_unsigned);
return;
}
case value_t::number_float:
{
dump_float(val.m_value.number_float);
return;
}
case value_t::discarded:
{
o << "<discarded>";
return;
}
case value_t::null:
{
o << "null";
return;
}
}
}
void json::serializer::dump_escaped(llvm::StringRef s) const
{
for (const auto& c : s)
{
switch (c)
{
// quotation mark (0x22)
case '"':
{
o << '\\' << '"';
break;
}
// reverse solidus (0x5c)
case '\\':
{
// nothing to change
o << '\\' << '\\';
break;
}
// backspace (0x08)
case '\b':
{
o << '\\' << 'b';
break;
}
// formfeed (0x0c)
case '\f':
{
o << '\\' << 'f';
break;
}
// newline (0x0a)
case '\n':
{
o << '\\' << 'n';
break;
}
// carriage return (0x0d)
case '\r':
{
o << '\\' << 'r';
break;
}
// horizontal tab (0x09)
case '\t':
{
o << '\\' << 't';
break;
}
case 0x00:
case 0x01:
case 0x02:
case 0x03:
case 0x04:
case 0x05:
case 0x06:
case 0x07:
case 0x0b:
case 0x0e:
case 0x0f:
case 0x10:
case 0x11:
case 0x12:
case 0x13:
case 0x14:
case 0x15:
case 0x16:
case 0x17:
case 0x18:
case 0x19:
case 0x1a:
case 0x1b:
case 0x1c:
case 0x1d:
case 0x1e:
case 0x1f:
{
// print character c as \uxxxx
o << "\\u00";
o << llvm::hexdigit((c >> 4) & 0xf, true);
o << llvm::hexdigit((c >> 0) & 0xf, true);
break;
}
default:
{
// all other characters are added as-is
o << c;
break;
}
}
}
}
void json::serializer::dump_float(double x)
{
// NaN / inf
if (!std::isfinite(x) || std::isnan(x))
{
o << "null";
return;
}
// special case for 0.0 and -0.0
if (x == 0)
{
if (std::signbit(x))
{
o << "-0.0";
}
else
{
o << "0.0";
}
return;
}
// get number of digits for a text -> float -> text round-trip
static constexpr auto d = std::numeric_limits<double>::digits10;
// the actual conversion
llvm::SmallString<64> number_buffer;
number_buffer.resize(64);
std::ptrdiff_t len = snprintf(number_buffer.data(), number_buffer.size(),
"%.*g", d, x);
// negative value indicates an error
assert(len > 0);
// check if buffer was large enough
assert(static_cast<size_t>(len) < number_buffer.size());
// erase thousands separator
if (thousands_sep != '\0')
{
const auto end = std::remove(number_buffer.begin(),
number_buffer.begin() + len,
thousands_sep);
std::fill(end, number_buffer.end(), '\0');
assert((end - number_buffer.begin()) <= len);
len = (end - number_buffer.begin());
}
// convert decimal point to '.'
if (decimal_point != '\0' && decimal_point != '.')
{
for (auto& c : number_buffer)
{
if (c == decimal_point)
{
c = '.';
break;
}
}
}
o.write(number_buffer.data(), static_cast<size_t>(len));
// determine if need to append ".0"
const bool value_is_int_like = std::none_of(number_buffer.begin(),
number_buffer.begin() + len + 1,
[](char c)
{
return c == '.' || c == 'e';
});
if (value_is_int_like)
{
o << ".0";
}
}
namespace wpi {
llvm::raw_ostream& operator<<(llvm::raw_ostream& o, const json& j)
{
j.dump(o, 0);
return o;
}
} // namespace wpi
std::string json::dump(int indent) const
{
std::string s;
llvm::raw_string_ostream os(s);
dump(os, indent);
os.flush();
return s;
}
void json::dump(llvm::raw_ostream& os, int indent) const
{
serializer s(os);
if (indent >= 0)
{
s.dump(*this, true, static_cast<unsigned int>(indent));
}
else
{
s.dump(*this, false, 0);
}
}

View File

@@ -0,0 +1,120 @@
/*----------------------------------------------------------------------------*/
/* Modifications Copyright (c) FIRST 2017. 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. */
/*----------------------------------------------------------------------------*/
/*
__ _____ _____ _____
__| | __| | | | JSON for Modern C++
| | |__ | | | | | | version 2.1.1
|_____|_____|_____|_|___| https://github.com/nlohmann/json
Licensed under the MIT License <http://opensource.org/licenses/MIT>.
Copyright (c) 2013-2017 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
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
*/
#include "support/json.h"
#include <clocale> // lconv, localeconv
#include <locale> // locale
#include "llvm/raw_ostream.h"
namespace wpi {
/*!
@brief wrapper around the serialization functions
*/
class json::serializer
{
public:
serializer(const serializer&) = delete;
serializer& operator=(const serializer&) = delete;
/*!
@param[in] s output stream to serialize to
@param[in] ichar indentation character to use
*/
explicit serializer(llvm::raw_ostream& s)
: o(s), loc(std::localeconv()),
thousands_sep(!loc->thousands_sep ? '\0' : loc->thousands_sep[0]),
decimal_point(!loc->decimal_point ? '\0' : loc->decimal_point[0])
{}
/*!
@brief internal implementation of the serialization function
This function is called by the public member function dump and
organizes the serialization internally. The indentation level is
propagated as additional parameter. In case of arrays and objects, the
function is called recursively.
- strings and object keys are escaped using `escape_string()`
- integer numbers are converted implicitly via `operator<<`
- floating-point numbers are converted to a string using `"%g"` format
@param[in] val value to serialize
@param[in] pretty_print whether the output shall be pretty-printed
@param[in] indent_step the indent level
@param[in] current_indent the current indent level (only used internally)
*/
void dump(const json& val,
const bool pretty_print,
const unsigned int indent_step,
const unsigned int current_indent = 0);
/*!
@brief dump escaped string
Escape a string by replacing certain special characters by a sequence
of an escape character (backslash) and another character and other
control characters by a sequence of "\u" followed by a four-digit hex
representation. The escaped string is written to output stream @a o.
@param[in] s the string to escape
@complexity Linear in the length of string @a s.
*/
void dump_escaped(llvm::StringRef s) const;
/*!
@brief dump a floating-point number
Dump a given floating-point number to output stream @a o. Works
internally with @a number_buffer.
@param[in] x floating-point number to dump
*/
void dump_float(double x);
private:
/// the output of the serializer
llvm::raw_ostream& o;
/// the locale
const std::lconv* loc = nullptr;
/// the locale's thousand separator character
const char thousands_sep = '\0';
/// the locale's decimal point character
const char decimal_point = '\0';
};
} // namespace wpi