mirror of
https://github.com/wpilibsuite/allwpilib
synced 2026-06-21 01:01:43 +00:00
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:
1182
src/main/native/cpp/support/json.cpp
Normal file
1182
src/main/native/cpp/support/json.cpp
Normal file
File diff suppressed because it is too large
Load Diff
1233
src/main/native/cpp/support/json_binary_reader.cpp
Normal file
1233
src/main/native/cpp/support/json_binary_reader.cpp
Normal file
File diff suppressed because it is too large
Load Diff
668
src/main/native/cpp/support/json_binary_writer.cpp
Normal file
668
src/main/native/cpp/support/json_binary_writer.cpp
Normal 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;
|
||||
}
|
||||
2095
src/main/native/cpp/support/json_parser.cpp
Normal file
2095
src/main/native/cpp/support/json_parser.cpp
Normal file
File diff suppressed because it is too large
Load Diff
540
src/main/native/cpp/support/json_pointer.cpp
Normal file
540
src/main/native/cpp/support/json_pointer.cpp
Normal 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;
|
||||
}
|
||||
|
||||
433
src/main/native/cpp/support/json_serializer.cpp
Normal file
433
src/main/native/cpp/support/json_serializer.cpp
Normal 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);
|
||||
}
|
||||
}
|
||||
120
src/main/native/cpp/support/json_serializer.h
Normal file
120
src/main/native/cpp/support/json_serializer.h
Normal 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
|
||||
Reference in New Issue
Block a user