diff --git a/wpiutil/src/main/native/cpp/json.cpp b/wpiutil/src/main/native/cpp/json.cpp index 6be69a12a8..ef25164357 100644 --- a/wpiutil/src/main/native/cpp/json.cpp +++ b/wpiutil/src/main/native/cpp/json.cpp @@ -1,5 +1,5 @@ /*----------------------------------------------------------------------------*/ -/* Modifications Copyright (c) FIRST 2017. All Rights Reserved. */ +/* Modifications Copyright (c) 2017-2018 FIRST. All Rights Reserved. */ /* Open Source Software - may be modified and shared by FRC teams. The code */ /* must be accompanied by the FIRST BSD license file in the root directory of */ /* the project. */ @@ -7,11 +7,11 @@ /* __ _____ _____ _____ __| | __| | | | JSON for Modern C++ -| | |__ | | | | | | version 2.1.1 +| | |__ | | | | | | version 3.1.2 |_____|_____|_____|_|___| https://github.com/nlohmann/json Licensed under the MIT License . -Copyright (c) 2013-2017 Niels Lohmann . +Copyright (c) 2013-2018 Niels Lohmann . Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal @@ -34,50 +34,42 @@ SOFTWARE. #define WPI_JSON_IMPLEMENTATION #include "wpi/json.h" -using namespace wpi; +#include "wpi/raw_ostream.h" namespace wpi { namespace detail { -std::string exception::name(const std::string& ename, int id) +exception::exception(int id_, const Twine& what_arg) + : id(id_), m(what_arg.str()) {} + +parse_error parse_error::create(int id_, std::size_t byte_, const Twine& what_arg) { - return "[json.exception." + ename + "." + std::to_string(id) + "] "; + return parse_error(id_, byte_, "[json.exception.parse_error." + Twine(id_) + "] parse error" + + (byte_ != 0 ? (" at " + Twine(byte_)) : Twine("")) + + ": " + what_arg); } -parse_error parse_error::create(int id, size_t byte_, const std::string& what_arg) +invalid_iterator invalid_iterator::create(int id_, const Twine& what_arg) { - std::string w = exception::name("parse_error", id) + "parse error" + - (byte_ != 0 ? (" at " + std::to_string(byte_)) : "") + - ": " + what_arg; - return parse_error(id, byte_, w.c_str()); + return invalid_iterator(id_, "[json.exception.invalid_iterator." + Twine(id_) + "] " + what_arg); } -invalid_iterator invalid_iterator::create(int id, const std::string& what_arg) +type_error type_error::create(int id_, const Twine& what_arg) { - std::string w = exception::name("invalid_iterator", id) + what_arg; - return invalid_iterator(id, w.c_str()); + return type_error(id_, "[json.exception.type_error." + Twine(id_) + "] " + what_arg); } -type_error type_error::create(int id, const std::string& what_arg) +out_of_range out_of_range::create(int id_, const Twine& what_arg) { - std::string w = exception::name("type_error", id) + what_arg; - return type_error(id, w.c_str()); + return out_of_range(id_, "[json.exception.out_of_range." + Twine(id_) + "] " + what_arg); } -out_of_range out_of_range::create(int id, const std::string& what_arg) +other_error other_error::create(int id_, const Twine& what_arg) { - std::string w = exception::name("out_of_range", id) + what_arg; - return out_of_range(id, w.c_str()); -} - -other_error other_error::create(int id, const std::string& what_arg) -{ - std::string w = exception::name("other_error", id) + what_arg; - return other_error(id, w.c_str()); + return other_error(id_, "[json.exception.other_error." + Twine(id_) + "] " + what_arg); } } // namespace detail -} // namespace wpi json::json_value::json_value(value_t t) { @@ -115,7 +107,7 @@ json::json_value::json_value(value_t t) case value_t::number_unsigned: { - number_unsigned = 0; + number_unsigned = 0u; break; } @@ -127,54 +119,71 @@ json::json_value::json_value(value_t t) case value_t::null: { + object = nullptr; // silence warning, see #821 break; } default: { + object = nullptr; // silence warning, see #821 if (JSON_UNLIKELY(t == value_t::null)) { - JSON_THROW(other_error::create(500, "961c151d2e87f2686a955a9be24d316f1362bf21 2.1.1")); // LCOV_EXCL_LINE + JSON_THROW(other_error::create(500, "961c151d2e87f2686a955a9be24d316f1362bf21 3.1.2")); // LCOV_EXCL_LINE } break; } } } -json::json_value::json_value(StringRef value) +void json::json_value::destroy(value_t t) noexcept { - string = create(value); + switch (t) + { + case value_t::object: + { + std::allocator alloc; + alloc.destroy(object); + alloc.deallocate(object, 1); + break; + } + + case value_t::array: + { + std::allocator alloc; + alloc.destroy(array); + alloc.deallocate(array, 1); + break; + } + + case value_t::string: + { + std::allocator alloc; + alloc.destroy(string); + alloc.deallocate(string, 1); + break; + } + + default: + { + break; + } + } } -json::json_value::json_value(const std::string& value) -{ - string = create(value); -} - -json::json_value::json_value(const object_t& value) -{ - object = create(value); -} - -json::json_value::json_value(const array_t& value) -{ - array = create(value); -} - -json::json(std::initializer_list init, +json::json(initializer_list_t init, bool type_deduction, value_t manual_type) { // check if each element is an array with two elements whose first // element is a string bool is_an_object = std::all_of(init.begin(), init.end(), - [](const json & element) + [](const detail::json_ref& element_ref) { - return element.is_array() && element.size() == 2 && element[0].is_string(); + return (element_ref->is_array() and element_ref->size() == 2 and (*element_ref)[0].is_string()); }); // adjust type if type deduction is not wanted - if (!type_deduction) + if (not type_deduction) { // if array is wanted, do not create an object though possible if (manual_type == value_t::array) @@ -183,7 +192,7 @@ json::json(std::initializer_list init, } // if object is wanted but impossible, throw an exception - if (manual_type == value_t::object && !is_an_object) + if (JSON_UNLIKELY(manual_type == value_t::object and not is_an_object)) { JSON_THROW(type_error::create(301, "cannot create object from initializer list")); } @@ -195,16 +204,19 @@ json::json(std::initializer_list init, m_type = value_t::object; m_value = value_t::object; - std::for_each(init.begin(), init.end(), [this](const json & element) + std::for_each(init.begin(), init.end(), [this](const detail::json_ref& element_ref) { - m_value.object->emplace_second(*(element[0].m_value.string), element[1]); + auto element = element_ref.moved_or_copied(); + m_value.object->emplace_second( + *((*element.m_value.array)[0].m_value.string), + std::move((*element.m_value.array)[1])); }); } else { // the initializer list describes an array -> create array m_type = value_t::array; - m_value.array = create(init); + m_value.array = create(init.begin(), init.end()); } assert_invariant(); @@ -268,63 +280,26 @@ json::json(const json& other) } default: - { break; - } } assert_invariant(); } -json::~json() -{ - assert_invariant(); - - switch (m_type) - { - case value_t::object: - { - std::allocator alloc; - alloc.destroy(m_value.object); - alloc.deallocate(m_value.object, 1); - break; - } - - case value_t::array: - { - std::allocator alloc; - alloc.destroy(m_value.array); - alloc.deallocate(m_value.array, 1); - break; - } - - case value_t::string: - { - std::allocator alloc; - alloc.destroy(m_value.string); - alloc.deallocate(m_value.string, 1); - break; - } - - default: - { - // all other types need no specific destructor - break; - } - } -} - json json::meta() { json result; - result["copyright"] = "(C) 2013-2017 Niels Lohmann, (C) 2017 FIRST"; + result["copyright"] = "(C) 2013-2017 Niels Lohmann, (C) 2017-2018 FIRST"; result["name"] = "WPI version of JSON for Modern C++"; - result["url"] = "https://github.com/wpilibsuite/wpiutil"; - result["version"] = - { - {"string", "2.1.1"}, {"major", 2}, {"minor", 1}, {"patch", 1} - }; + result["url"] = "https://github.com/wpilibsuite/allwpilib"; + result["version"]["string"] = + std::to_string(NLOHMANN_JSON_VERSION_MAJOR) + "." + + std::to_string(NLOHMANN_JSON_VERSION_MINOR) + "." + + std::to_string(NLOHMANN_JSON_VERSION_PATCH); + result["version"]["major"] = NLOHMANN_JSON_VERSION_MAJOR; + result["version"]["minor"] = NLOHMANN_JSON_VERSION_MINOR; + result["version"]["patch"] = NLOHMANN_JSON_VERSION_PATCH; #ifdef _WIN32 result["platform"] = "win32"; @@ -338,10 +313,10 @@ json json::meta() result["platform"] = "unknown"; #endif -#if defined(__clang__) - result["compiler"] = {{"family", "clang"}, {"version", __clang_version__}}; -#elif defined(__ICC) || defined(__INTEL_COMPILER) +#if defined(__ICC) || defined(__INTEL_COMPILER) result["compiler"] = {{"family", "icc"}, {"version", __INTEL_COMPILER}}; +#elif defined(__clang__) + result["compiler"] = {{"family", "clang"}, {"version", __clang_version__}}; #elif defined(__GNUC__) || defined(__GNUG__) result["compiler"] = {{"family", "gcc"}, {"version", std::to_string(__GNUC__) + "." + std::to_string(__GNUC_MINOR__) + "." + std::to_string(__GNUC_PATCHLEVEL__)}}; #elif defined(__HP_cc) || defined(__HP_aCC) @@ -366,186 +341,212 @@ json json::meta() return result; } -namespace wpi { - -bool operator==(json::const_reference lhs, json::const_reference rhs) noexcept +json::reference json::at(size_type idx) { - using value_t = json::value_t; - const auto lhs_type = lhs.type(); - const auto rhs_type = rhs.type(); - - if (lhs_type == rhs_type) + // at only works for arrays + if (JSON_LIKELY(is_array())) { - switch (lhs_type) + JSON_TRY { - case value_t::array: - { - return *lhs.m_value.array == *rhs.m_value.array; - } - case value_t::object: - { - return *lhs.m_value.object == *rhs.m_value.object; - } - case value_t::null: - { - return true; - } - case value_t::string: - { - return *lhs.m_value.string == *rhs.m_value.string; - } - case value_t::boolean: - { - return lhs.m_value.boolean == rhs.m_value.boolean; - } - case value_t::number_integer: - { - return lhs.m_value.number_integer == rhs.m_value.number_integer; - } - case value_t::number_unsigned: - { - return lhs.m_value.number_unsigned == rhs.m_value.number_unsigned; - } - case value_t::number_float: - { - return lhs.m_value.number_float == rhs.m_value.number_float; - } - default: - { - return false; - } + return m_value.array->at(idx); + } + JSON_CATCH (std::out_of_range&) + { + // create better exception explanation + JSON_THROW(out_of_range::create(401, "array index " + Twine(idx) + " is out of range")); } } - else if (lhs_type == value_t::number_integer && rhs_type == value_t::number_float) + else { - return static_cast(lhs.m_value.number_integer) == rhs.m_value.number_float; + JSON_THROW(type_error::create(304, "cannot use at() with " + Twine(type_name()))); } - else if (lhs_type == value_t::number_float && rhs_type == value_t::number_integer) - { - return lhs.m_value.number_float == static_cast(rhs.m_value.number_integer); - } - else if (lhs_type == value_t::number_unsigned && rhs_type == value_t::number_float) - { - return static_cast(lhs.m_value.number_unsigned) == rhs.m_value.number_float; - } - else if (lhs_type == value_t::number_float && rhs_type == value_t::number_unsigned) - { - return lhs.m_value.number_float == static_cast(rhs.m_value.number_unsigned); - } - else if (lhs_type == value_t::number_unsigned && rhs_type == value_t::number_integer) - { - return static_cast(lhs.m_value.number_unsigned) == rhs.m_value.number_integer; - } - else if (lhs_type == value_t::number_integer && rhs_type == value_t::number_unsigned) - { - return lhs.m_value.number_integer == static_cast(rhs.m_value.number_unsigned); - } - - return false; } -bool operator<(json::const_reference lhs, json::const_reference rhs) noexcept +json::const_reference json::at(size_type idx) const { - using value_t = json::value_t; - const auto lhs_type = lhs.type(); - const auto rhs_type = rhs.type(); - - if (lhs_type == rhs_type) + // at only works for arrays + if (JSON_LIKELY(is_array())) { - switch (lhs_type) + JSON_TRY { - case value_t::array: - { - return (*lhs.m_value.array) < (*rhs.m_value.array); - } - case value_t::object: - { - return *lhs.m_value.object < *rhs.m_value.object; - } - case value_t::null: - { - return false; - } - case value_t::string: - { - return *lhs.m_value.string < *rhs.m_value.string; - } - case value_t::boolean: - { - return lhs.m_value.boolean < rhs.m_value.boolean; - } - case value_t::number_integer: - { - return lhs.m_value.number_integer < rhs.m_value.number_integer; - } - case value_t::number_unsigned: - { - return lhs.m_value.number_unsigned < rhs.m_value.number_unsigned; - } - case value_t::number_float: - { - return lhs.m_value.number_float < rhs.m_value.number_float; - } - default: - { - return false; - } + return m_value.array->at(idx); + } + JSON_CATCH (std::out_of_range&) + { + // create better exception explanation + JSON_THROW(out_of_range::create(401, "array index " + Twine(idx) + " is out of range")); } } - else if (lhs_type == value_t::number_integer && rhs_type == value_t::number_float) + else { - return static_cast(lhs.m_value.number_integer) < rhs.m_value.number_float; + JSON_THROW(type_error::create(304, "cannot use at() with " + Twine(type_name()))); } - else if (lhs_type == value_t::number_float && rhs_type == value_t::number_integer) - { - return lhs.m_value.number_float < static_cast(rhs.m_value.number_integer); - } - else if (lhs_type == value_t::number_unsigned && rhs_type == value_t::number_float) - { - return static_cast(lhs.m_value.number_unsigned) < rhs.m_value.number_float; - } - else if (lhs_type == value_t::number_float && rhs_type == value_t::number_unsigned) - { - return lhs.m_value.number_float < static_cast(rhs.m_value.number_unsigned); - } - else if (lhs_type == value_t::number_integer && rhs_type == value_t::number_unsigned) - { - return lhs.m_value.number_integer < static_cast(rhs.m_value.number_unsigned); - } - else if (lhs_type == value_t::number_unsigned && rhs_type == value_t::number_integer) - { - return static_cast(lhs.m_value.number_unsigned) < rhs.m_value.number_integer; - } - - // We only reach this line if we cannot compare values. In that case, - // we compare types. Note we have to call the operator explicitly, - // because MSVC has problems otherwise. - return operator<(lhs_type, rhs_type); } -} // namespace wpi - -std::string json::type_name() const +json::reference json::at(StringRef key) { + // at only works for objects + if (JSON_LIKELY(is_object())) { - switch (m_type) + auto it = m_value.object->find(key); + if (it == m_value.object->end()) { - case value_t::null: - return "null"; - case value_t::object: - return "object"; - case value_t::array: - return "array"; - case value_t::string: - return "string"; - case value_t::boolean: - return "boolean"; - case value_t::discarded: - return "discarded"; - default: - return "number"; + // create better exception explanation + JSON_THROW(out_of_range::create(403, "key '" + Twine(key) + "' not found")); } + return it->second; } + else + { + JSON_THROW(type_error::create(304, "cannot use at() with " + Twine(type_name()))); + } +} + +json::const_reference json::at(StringRef key) const +{ + // at only works for objects + if (JSON_LIKELY(is_object())) + { + auto it = m_value.object->find(key); + if (it == m_value.object->end()) + { + // create better exception explanation + JSON_THROW(out_of_range::create(403, "key '" + Twine(key) + "' not found")); + } + return it->second; + } + else + { + JSON_THROW(type_error::create(304, "cannot use at() with " + Twine(type_name()))); + } +} + +json::reference json::operator[](size_type idx) +{ + // implicitly convert null value to an empty array + if (is_null()) + { + m_type = value_t::array; + m_value.array = create(); + assert_invariant(); + } + + // operator[] only works for arrays + if (JSON_LIKELY(is_array())) + { + // fill up array with null values if given idx is outside range + if (idx >= m_value.array->size()) + { + m_value.array->insert(m_value.array->end(), + idx - m_value.array->size() + 1, + json()); + } + + return m_value.array->operator[](idx); + } + + JSON_THROW(type_error::create(305, "cannot use operator[] with " + Twine(type_name()))); +} + +json::const_reference json::operator[](size_type idx) const +{ + // const operator[] only works for arrays + if (JSON_LIKELY(is_array())) + { + return m_value.array->operator[](idx); + } + + JSON_THROW(type_error::create(305, "cannot use operator[] with " + Twine(type_name()))); +} + +json::reference json::operator[](StringRef key) +{ + // implicitly convert null value to an empty object + if (is_null()) + { + m_type = value_t::object; + m_value.object = create(); + assert_invariant(); + } + + // operator[] only works for objects + if (JSON_LIKELY(is_object())) + { + return m_value.object->operator[](key); + } + + JSON_THROW(type_error::create(305, "cannot use operator[] with " + Twine(type_name()))); +} + +json::const_reference json::operator[](StringRef key) const +{ + // const operator[] only works for objects + if (JSON_LIKELY(is_object())) + { + assert(m_value.object->find(key) != m_value.object->end()); + return m_value.object->find(key)->second; + } + + JSON_THROW(type_error::create(305, "cannot use operator[] with " + Twine(type_name()))); +} + +json::size_type json::erase(StringRef key) +{ + // this erase only works for objects + if (JSON_LIKELY(is_object())) + { + return m_value.object->erase(key); + } + + JSON_THROW(type_error::create(307, "cannot use erase() with " + Twine(type_name()))); +} + +void json::erase(const size_type idx) +{ + // this erase only works for arrays + if (JSON_LIKELY(is_array())) + { + if (JSON_UNLIKELY(idx >= size())) + { + JSON_THROW(out_of_range::create(401, "array index " + Twine(idx) + " is out of range")); + } + + m_value.array->erase(m_value.array->begin() + static_cast(idx)); + } + else + { + JSON_THROW(type_error::create(307, "cannot use erase() with " + Twine(type_name()))); + } +} + +json::iterator json::find(StringRef key) +{ + auto result = end(); + + if (is_object()) + { + result.m_it.object_iterator = m_value.object->find(key); + } + + return result; +} + +json::const_iterator json::find(StringRef key) const +{ + auto result = cend(); + + if (is_object()) + { + result.m_it.object_iterator = m_value.object->find(key); + } + + return result; +} + +json::size_type json::count(StringRef key) const +{ + // return 0 for all nonobject types + return is_object() ? m_value.object->count(key) : 0; } bool json::empty() const noexcept @@ -679,18 +680,16 @@ void json::clear() noexcept } default: - { break; - } } } void json::push_back(json&& val) { // push_back only works for null objects or arrays - if (!(is_null() || is_array())) + if (JSON_UNLIKELY(not(is_null() or is_array()))) { - JSON_THROW(type_error::create(308, "cannot use push_back() with " + type_name())); + JSON_THROW(type_error::create(308, "cannot use push_back() with " + Twine(type_name()))); } // transform null object into an array @@ -710,9 +709,9 @@ void json::push_back(json&& val) void json::push_back(const json& val) { // push_back only works for null objects or arrays - if (!(is_null() || is_array())) + if (JSON_UNLIKELY(not(is_null() or is_array()))) { - JSON_THROW(type_error::create(308, "cannot use push_back() with " + type_name())); + JSON_THROW(type_error::create(308, "cannot use push_back() with " + Twine(type_name()))); } // transform null object into an array @@ -727,32 +726,12 @@ void json::push_back(const json& val) m_value.array->push_back(val); } -void json::push_back(const std::pair& val) +void json::push_back(initializer_list_t init) { - // push_back only works for null objects or objects - if (!(is_null() || is_object())) + if (is_object() and init.size() == 2 and (*init.begin())->is_string()) { - JSON_THROW(type_error::create(308, "cannot use push_back() with " + type_name())); - } - - // transform null object into an object - if (is_null()) - { - m_type = value_t::object; - m_value = value_t::object; - assert_invariant(); - } - - // add element to array - m_value.object->insert(val); -} - -void json::push_back(std::initializer_list init) -{ - if (is_object() && init.size() == 2 && init.begin()->is_string()) - { - const std::string key = *init.begin(); - push_back(std::pair(key, *(init.begin() + 1))); + std::string key = init.begin()->moved_or_copied(); + push_back(std::pair(key, (init.begin() + 1)->moved_or_copied())); } else { @@ -760,266 +739,13 @@ void json::push_back(std::initializer_list init) } } -json::reference json::at(size_type idx) -{ - // at only works for arrays - if (is_array()) - { - JSON_TRY - { - return m_value.array->at(idx); - } - JSON_CATCH (std::out_of_range&) - { - // create better exception explanation - JSON_THROW(out_of_range::create(401, "array index " + std::to_string(idx) + " is out of range")); - } - } - else - { - JSON_THROW(type_error::create(304, "cannot use at() with " + type_name())); - } -} - -json::const_reference json::at(size_type idx) const -{ - // at only works for arrays - if (is_array()) - { - JSON_TRY - { - return m_value.array->at(idx); - } - JSON_CATCH (std::out_of_range&) - { - // create better exception explanation - JSON_THROW(out_of_range::create(401, "array index " + std::to_string(idx) + " is out of range")); - } - } - else - { - JSON_THROW(type_error::create(304, "cannot use at() with " + type_name())); - } -} - -json::reference json::at(StringRef key) -{ - // at only works for objects - if (is_object()) - { - auto it = m_value.object->find(key); - if (it == m_value.object->end()) - { - JSON_THROW(out_of_range::create(403, "key '" + key.str() + "' not found")); - } - return it->second; - } - else - { - JSON_THROW(type_error::create(304, "cannot use at() with " + type_name())); - } -} - -json::const_reference json::at(StringRef key) const -{ - // at only works for objects - if (is_object()) - { - auto it = m_value.object->find(key); - if (it == m_value.object->end()) - { - // create better exception explanation - JSON_THROW(out_of_range::create(403, "key '" + key.str() + "' not found")); - } - return it->second; - } - else - { - JSON_THROW(type_error::create(304, "cannot use at() with " + type_name())); - } -} - -json::reference json::operator[](size_type idx) -{ - // implicitly convert null value to an empty array - if (is_null()) - { - m_type = value_t::array; - m_value.array = create(); - assert_invariant(); - } - - // operator[] only works for arrays - if (is_array()) - { - // fill up array with null values if given idx is outside range - if (idx >= m_value.array->size()) - { - m_value.array->insert(m_value.array->end(), - idx - m_value.array->size() + 1, - json()); - } - - return m_value.array->operator[](idx); - } - - JSON_THROW(type_error::create(305, "cannot use operator[] with " + type_name())); -} - -json::const_reference json::operator[](size_type idx) const -{ - // const operator[] only works for arrays - if (is_array()) - { - return m_value.array->operator[](idx); - } - - JSON_THROW(type_error::create(305, "cannot use operator[] with " + type_name())); -} - -json::reference json::operator[](StringRef key) -{ - // implicitly convert null value to an empty object - if (is_null()) - { - m_type = value_t::object; - m_value.object = create(); - assert_invariant(); - } - - // operator[] only works for objects - if (is_object()) - { - return m_value.object->operator[](key); - } - - JSON_THROW(type_error::create(305, "cannot use operator[] with " + type_name())); -} - -json::const_reference json::operator[](StringRef key) const -{ - // const operator[] only works for objects - if (is_object()) - { - assert(m_value.object->find(key) != m_value.object->end()); - return m_value.object->find(key)->second; - } - - JSON_THROW(type_error::create(305, "cannot use operator[] with " + type_name())); -} - -json::reference json::back() -{ - switch (m_type) - { - case value_t::object: - { - JSON_THROW(type_error::create(305, "cannot use back() with " + type_name())); - } - - case value_t::array: - { - return m_value.array->back(); - } - - case value_t::null: - { - JSON_THROW(invalid_iterator::create(214, "cannot get value")); - } - - default: - { - return *this; - } - } -} - -json::const_reference json::back() const -{ - switch (m_type) - { - case value_t::object: - { - JSON_THROW(type_error::create(305, "cannot use back() with " + type_name())); - } - - case value_t::array: - { - return m_value.array->back(); - } - - case value_t::null: - { - JSON_THROW(invalid_iterator::create(214, "cannot get value")); - } - - default: - { - return *this; - } - } -} - -json::size_type json::erase(StringRef key) -{ - // this erase only works for objects - if (is_object()) - { - return m_value.object->erase(key); - } - - JSON_THROW(type_error::create(307, "cannot use erase() with " + type_name())); -} - -void json::erase(const size_type idx) -{ - // this erase only works for arrays - if (is_array()) - { - if (idx >= size()) - { - JSON_THROW(out_of_range::create(401, "array index " + std::to_string(idx) + " is out of range")); - } - - m_value.array->erase(m_value.array->begin() + static_cast(idx)); - } - else - { - JSON_THROW(type_error::create(307, "cannot use erase() with " + type_name())); - } -} - -json::iterator json::find(StringRef key) -{ - auto result = end(); - - if (is_object()) - { - result.m_it.object_iterator = m_value.object->find(key); - } - - return result; -} - -json::const_iterator json::find(StringRef key) const -{ - auto result = cend(); - - if (is_object()) - { - result.m_it.object_iterator = m_value.object->find(key); - } - - return result; -} - json::iterator json::insert(const_iterator pos, const json& val) { // insert only works for arrays - if (is_array()) + if (JSON_LIKELY(is_array())) { // check if iterator pos fits to this JSON value - if (pos.m_object != this) + if (JSON_UNLIKELY(pos.m_object != this)) { JSON_THROW(invalid_iterator::create(202, "iterator does not fit current value")); } @@ -1030,133 +756,99 @@ json::iterator json::insert(const_iterator pos, const json& val) return result; } - JSON_THROW(type_error::create(309, "cannot use insert() with " + type_name())); + JSON_THROW(type_error::create(309, "cannot use insert() with " + Twine(type_name()))); } json::iterator json::insert(const_iterator pos, size_type cnt, const json& val) { // insert only works for arrays - if (is_array()) + if (JSON_LIKELY(is_array())) { // check if iterator pos fits to this JSON value - if (pos.m_object != this) + if (JSON_UNLIKELY(pos.m_object != this)) { JSON_THROW(invalid_iterator::create(202, "iterator does not fit current value")); } // insert to array and return iterator iterator result(this); -#if defined(__GNUC__) && (__GNUC__ * 10000 + __GNUC_MINOR__ * 100 + __GNUC_PATCHLEVEL__) < 40900 - // handle C++11 noncompliance: insert(it, cnt, val) returns void - if (cnt == 0) { - result.m_it.array_iterator = pos.m_it.array_iterator; - } else { - size_t ipos = pos.m_it.array_iterator - m_value.array->begin(); - m_value.array->insert(pos.m_it.array_iterator, cnt, val); - result.m_it.array_iterator = m_value.array->begin() + ipos; - } -#else result.m_it.array_iterator = m_value.array->insert(pos.m_it.array_iterator, cnt, val); -#endif return result; } - JSON_THROW(type_error::create(309, "cannot use insert() with " + type_name())); + JSON_THROW(type_error::create(309, "cannot use insert() with " + Twine(type_name()))); } json::iterator json::insert(const_iterator pos, const_iterator first, const_iterator last) { // insert only works for arrays - if (!is_array()) + if (JSON_UNLIKELY(not is_array())) { - JSON_THROW(type_error::create(309, "cannot use insert() with " + type_name())); + JSON_THROW(type_error::create(309, "cannot use insert() with " + Twine(type_name()))); } // check if iterator pos fits to this JSON value - if (pos.m_object != this) + if (JSON_UNLIKELY(pos.m_object != this)) { JSON_THROW(invalid_iterator::create(202, "iterator does not fit current value")); } // check if range iterators belong to the same JSON object - if (first.m_object != last.m_object) + if (JSON_UNLIKELY(first.m_object != last.m_object)) { JSON_THROW(invalid_iterator::create(210, "iterators do not fit")); } - if (first.m_object == this || last.m_object == this) + if (JSON_UNLIKELY(first.m_object == this)) { JSON_THROW(invalid_iterator::create(211, "passed iterators may not belong to container")); } // insert to array and return iterator iterator result(this); -#if defined(__GNUC__) && (__GNUC__ * 10000 + __GNUC_MINOR__ * 100 + __GNUC_PATCHLEVEL__) < 40900 - // handle C++11 noncompliance: insert(it, first, last) returns void - if (first == last) { - result.m_it.array_iterator = pos.m_it.array_iterator; - } else { - size_t ipos = pos.m_it.array_iterator - m_value.array->begin(); - m_value.array->insert(pos.m_it.array_iterator, first.m_it.array_iterator, - last.m_it.array_iterator); - result.m_it.array_iterator = m_value.array->begin() + ipos; - } -#else result.m_it.array_iterator = m_value.array->insert( pos.m_it.array_iterator, first.m_it.array_iterator, last.m_it.array_iterator); -#endif return result; } -json::iterator json::insert(const_iterator pos, std::initializer_list ilist) +json::iterator json::insert(const_iterator pos, initializer_list_t ilist) { // insert only works for arrays - if (!is_array()) + if (JSON_UNLIKELY(not is_array())) { - JSON_THROW(type_error::create(309, "cannot use insert() with " + type_name())); + JSON_THROW(type_error::create(309, "cannot use insert() with " + Twine(type_name()))); } // check if iterator pos fits to this JSON value - if (pos.m_object != this) + if (JSON_UNLIKELY(pos.m_object != this)) { JSON_THROW(invalid_iterator::create(202, "iterator does not fit current value")); } // insert to array and return iterator iterator result(this); -#if defined(__GNUC__) && (__GNUC__ * 10000 + __GNUC_MINOR__ * 100 + __GNUC_PATCHLEVEL__) < 40900 - // handle C++11 noncompliance: insert(it, ilist) returns void - if (ilist.size() == 0) { - result.m_it.array_iterator = pos.m_it.array_iterator; - } else { - size_t ipos = pos.m_it.array_iterator - m_value.array->begin(); - m_value.array->insert(pos.m_it.array_iterator, ilist); - result.m_it.array_iterator = m_value.array->begin() + ipos; - } -#else - result.m_it.array_iterator = m_value.array->insert(pos.m_it.array_iterator, ilist); -#endif + result.m_it.array_iterator = m_value.array->insert(pos.m_it.array_iterator, ilist.begin(), ilist.end()); return result; } void json::insert(const_iterator first, const_iterator last) { // insert only works for objects - if (!is_object()) + if (JSON_UNLIKELY(not is_object())) { - JSON_THROW(type_error::create(309, "cannot use insert() with " + type_name())); + JSON_THROW(type_error::create(309, "cannot use insert() with " + Twine(type_name()))); } // check if range iterators belong to the same JSON object - if (first.m_object != last.m_object) + if (JSON_UNLIKELY(first.m_object != last.m_object)) { JSON_THROW(invalid_iterator::create(210, "iterators do not fit")); } // passed iterators must belong to objects - if (!first.m_object->is_object() || !first.m_object->is_object()) + if (JSON_UNLIKELY(not first.m_object->is_object())) { JSON_THROW(invalid_iterator::create(202, "iterators first and last must point to objects")); } @@ -1167,16 +859,635 @@ void json::insert(const_iterator first, const_iterator last) } } -namespace std { - -std::size_t hash::operator()(const wpi::json& j) const +void json::update(const_reference j) { - // a naive hashing via the string representation - const auto& h = hash(); - SmallVector buf; - raw_svector_ostream os(buf); - j.dump(os); - return h(os.str()); + // implicitly convert null value to an empty object + if (is_null()) + { + m_type = value_t::object; + m_value.object = create(); + assert_invariant(); + } + + if (JSON_UNLIKELY(not is_object())) + { + JSON_THROW(type_error::create(312, "cannot use update() with " + Twine(type_name()))); + } + if (JSON_UNLIKELY(not j.is_object())) + { + JSON_THROW(type_error::create(312, "cannot use update() with " + Twine(j.type_name()))); + } + + for (auto it = j.cbegin(); it != j.cend(); ++it) + { + m_value.object->operator[](it.key()) = it.value(); + } } -} // namespace std +void json::update(const_iterator first, const_iterator last) +{ + // implicitly convert null value to an empty object + if (is_null()) + { + m_type = value_t::object; + m_value.object = create(); + assert_invariant(); + } + + if (JSON_UNLIKELY(not is_object())) + { + JSON_THROW(type_error::create(312, "cannot use update() with " + Twine(type_name()))); + } + + // check if range iterators belong to the same JSON object + if (JSON_UNLIKELY(first.m_object != last.m_object)) + { + JSON_THROW(invalid_iterator::create(210, "iterators do not fit")); + } + + // passed iterators must belong to objects + if (JSON_UNLIKELY(not first.m_object->is_object() + or not last.m_object->is_object())) + { + JSON_THROW(invalid_iterator::create(202, "iterators first and last must point to objects")); + } + + for (auto it = first; it != last; ++it) + { + m_value.object->operator[](it.key()) = it.value(); + } +} + +bool operator==(json::const_reference lhs, json::const_reference rhs) noexcept +{ + const auto lhs_type = lhs.type(); + const auto rhs_type = rhs.type(); + + if (lhs_type == rhs_type) + { + switch (lhs_type) + { + case json::value_t::array: + return (*lhs.m_value.array == *rhs.m_value.array); + + case json::value_t::object: + return (*lhs.m_value.object == *rhs.m_value.object); + + case json::value_t::null: + return true; + + case json::value_t::string: + return (*lhs.m_value.string == *rhs.m_value.string); + + case json::value_t::boolean: + return (lhs.m_value.boolean == rhs.m_value.boolean); + + case json::value_t::number_integer: + return (lhs.m_value.number_integer == rhs.m_value.number_integer); + + case json::value_t::number_unsigned: + return (lhs.m_value.number_unsigned == rhs.m_value.number_unsigned); + + case json::value_t::number_float: + return (lhs.m_value.number_float == rhs.m_value.number_float); + + default: + return false; + } + } + else if (lhs_type == json::value_t::number_integer and rhs_type == json::value_t::number_float) + { + return (static_cast(lhs.m_value.number_integer) == rhs.m_value.number_float); + } + else if (lhs_type == json::value_t::number_float and rhs_type == json::value_t::number_integer) + { + return (lhs.m_value.number_float == static_cast(rhs.m_value.number_integer)); + } + else if (lhs_type == json::value_t::number_unsigned and rhs_type == json::value_t::number_float) + { + return (static_cast(lhs.m_value.number_unsigned) == rhs.m_value.number_float); + } + else if (lhs_type == json::value_t::number_float and rhs_type == json::value_t::number_unsigned) + { + return (lhs.m_value.number_float == static_cast(rhs.m_value.number_unsigned)); + } + else if (lhs_type == json::value_t::number_unsigned and rhs_type == json::value_t::number_integer) + { + return (static_cast(lhs.m_value.number_unsigned) == rhs.m_value.number_integer); + } + else if (lhs_type == json::value_t::number_integer and rhs_type == json::value_t::number_unsigned) + { + return (lhs.m_value.number_integer == static_cast(rhs.m_value.number_unsigned)); + } + + return false; +} + +bool operator<(json::const_reference lhs, json::const_reference rhs) noexcept +{ + const auto lhs_type = lhs.type(); + const auto rhs_type = rhs.type(); + + if (lhs_type == rhs_type) + { + switch (lhs_type) + { + case json::value_t::array: + return (*lhs.m_value.array) < (*rhs.m_value.array); + + case json::value_t::object: + return *lhs.m_value.object < *rhs.m_value.object; + + case json::value_t::null: + return false; + + case json::value_t::string: + return *lhs.m_value.string < *rhs.m_value.string; + + case json::value_t::boolean: + return lhs.m_value.boolean < rhs.m_value.boolean; + + case json::value_t::number_integer: + return lhs.m_value.number_integer < rhs.m_value.number_integer; + + case json::value_t::number_unsigned: + return lhs.m_value.number_unsigned < rhs.m_value.number_unsigned; + + case json::value_t::number_float: + return lhs.m_value.number_float < rhs.m_value.number_float; + + default: + return false; + } + } + else if (lhs_type == json::value_t::number_integer and rhs_type == json::value_t::number_float) + { + return static_cast(lhs.m_value.number_integer) < rhs.m_value.number_float; + } + else if (lhs_type == json::value_t::number_float and rhs_type == json::value_t::number_integer) + { + return lhs.m_value.number_float < static_cast(rhs.m_value.number_integer); + } + else if (lhs_type == json::value_t::number_unsigned and rhs_type == json::value_t::number_float) + { + return static_cast(lhs.m_value.number_unsigned) < rhs.m_value.number_float; + } + else if (lhs_type == json::value_t::number_float and rhs_type == json::value_t::number_unsigned) + { + return lhs.m_value.number_float < static_cast(rhs.m_value.number_unsigned); + } + else if (lhs_type == json::value_t::number_integer and rhs_type == json::value_t::number_unsigned) + { + return lhs.m_value.number_integer < static_cast(rhs.m_value.number_unsigned); + } + else if (lhs_type == json::value_t::number_unsigned and rhs_type == json::value_t::number_integer) + { + return static_cast(lhs.m_value.number_unsigned) < rhs.m_value.number_integer; + } + + // We only reach this line if we cannot compare values. In that case, + // we compare types. Note we have to call the operator explicitly, + // because MSVC has problems otherwise. + return operator<(lhs_type, rhs_type); +} + +const char* json::type_name() const noexcept +{ + { + switch (m_type) + { + case value_t::null: + return "null"; + case value_t::object: + return "object"; + case value_t::array: + return "array"; + case value_t::string: + return "string"; + case value_t::boolean: + return "boolean"; + case value_t::discarded: + return "discarded"; + default: + return "number"; + } + } +} + +json json::patch(const json& json_patch) const +{ + // make a working copy to apply the patch to + json result = *this; + + // the valid JSON Patch operations + enum class patch_operations {add, remove, replace, move, copy, test, invalid}; + + const auto get_op = [](const std::string & op) + { + if (op == "add") + { + return patch_operations::add; + } + if (op == "remove") + { + return patch_operations::remove; + } + if (op == "replace") + { + return patch_operations::replace; + } + if (op == "move") + { + return patch_operations::move; + } + if (op == "copy") + { + return patch_operations::copy; + } + if (op == "test") + { + return patch_operations::test; + } + + return patch_operations::invalid; + }; + + // wrapper for "add" operation; add value at ptr + const auto operation_add = [&result](json_pointer & ptr, json val) + { + // adding to the root of the target document means replacing it + if (ptr.is_root()) + { + result = val; + } + else + { + // make sure the top element of the pointer exists + json_pointer top_pointer = ptr.top(); + if (top_pointer != ptr) + { + result.at(top_pointer); + } + + // get reference to parent of JSON pointer ptr + const auto last_path = ptr.pop_back(); + json& parent = result[ptr]; + + switch (parent.m_type) + { + case value_t::null: + case value_t::object: + { + // use operator[] to add value + parent[last_path] = val; + break; + } + + case value_t::array: + { + if (last_path == "-") + { + // special case: append to back + parent.push_back(val); + } + else + { + const auto idx = json_pointer::array_index(last_path); + if (JSON_UNLIKELY(static_cast(idx) > parent.size())) + { + // avoid undefined behavior + JSON_THROW(out_of_range::create(401, "array index " + Twine(idx) + " is out of range")); + } + else + { + // default case: insert add offset + parent.insert(parent.begin() + static_cast(idx), val); + } + } + break; + } + + default: + { + // if there exists a parent it cannot be primitive + assert(false); // LCOV_EXCL_LINE + } + } + } + }; + + // wrapper for "remove" operation; remove value at ptr + const auto operation_remove = [&result](json_pointer & ptr) + { + // get reference to parent of JSON pointer ptr + const auto last_path = ptr.pop_back(); + json& parent = result.at(ptr); + + // remove child + if (parent.is_object()) + { + // perform range check + auto it = parent.find(last_path); + if (JSON_LIKELY(it != parent.end())) + { + parent.erase(it); + } + else + { + JSON_THROW(out_of_range::create(403, "key '" + Twine(last_path) + "' not found")); + } + } + else if (parent.is_array()) + { + // note erase performs range check + parent.erase(static_cast(json_pointer::array_index(last_path))); + } + }; + + // type check: top level value must be an array + if (JSON_UNLIKELY(not json_patch.is_array())) + { + JSON_THROW(parse_error::create(104, 0, "JSON patch must be an array of objects")); + } + + // iterate and apply the operations + for (const auto& val : json_patch) + { + // wrapper to get a value for an operation + const auto get_value = [&val](const std::string & op, + const std::string & member, + bool string_type) -> json & + { + // find value + auto it = val.m_value.object->find(member); + + // context-sensitive error message + const auto error_msg = (op == "op") ? "operation" : "operation '" + op + "'"; + + // check if desired value is present + if (JSON_UNLIKELY(it == val.m_value.object->end())) + { + JSON_THROW(parse_error::create(105, 0, Twine(error_msg) + " must have member '" + Twine(member) + "'")); + } + + // check if result is of type string + if (JSON_UNLIKELY(string_type and not it->second.is_string())) + { + JSON_THROW(parse_error::create(105, 0, Twine(error_msg) + " must have string member '" + Twine(member) + "'")); + } + + // no error: return value + return it->second; + }; + + // type check: every element of the array must be an object + if (JSON_UNLIKELY(not val.is_object())) + { + JSON_THROW(parse_error::create(104, 0, "JSON patch must be an array of objects")); + } + + // collect mandatory members + const std::string op = get_value("op", "op", true); + const std::string path = get_value(op, "path", true); + json_pointer ptr(path); + + switch (get_op(op)) + { + case patch_operations::add: + { + operation_add(ptr, get_value("add", "value", false)); + break; + } + + case patch_operations::remove: + { + operation_remove(ptr); + break; + } + + case patch_operations::replace: + { + // the "path" location must exist - use at() + result.at(ptr) = get_value("replace", "value", false); + break; + } + + case patch_operations::move: + { + const std::string from_path = get_value("move", "from", true); + json_pointer from_ptr(from_path); + + // the "from" location must exist - use at() + json v = result.at(from_ptr); + + // The move operation is functionally identical to a + // "remove" operation on the "from" location, followed + // immediately by an "add" operation at the target + // location with the value that was just removed. + operation_remove(from_ptr); + operation_add(ptr, v); + break; + } + + case patch_operations::copy: + { + const std::string from_path = get_value("copy", "from", true); + const json_pointer from_ptr(from_path); + + // the "from" location must exist - use at() + json v = result.at(from_ptr); + + // The copy is functionally identical to an "add" + // operation at the target location using the value + // specified in the "from" member. + operation_add(ptr, v); + break; + } + + case patch_operations::test: + { + bool success = false; + JSON_TRY + { + // check if "value" matches the one at "path" + // the "path" location must exist - use at() + success = (result.at(ptr) == get_value("test", "value", false)); + } + JSON_CATCH (out_of_range&) + { + // ignore out of range errors: success remains false + } + + // throw an exception if test fails + if (JSON_UNLIKELY(not success)) + { + JSON_THROW(other_error::create(501, "unsuccessful: " + Twine(val.dump()))); + } + + break; + } + + case patch_operations::invalid: + { + // op must be "add", "remove", "replace", "move", "copy", or + // "test" + JSON_THROW(parse_error::create(105, 0, "operation value '" + Twine(op) + "' is invalid")); + } + } + } + + return result; +} + +json json::diff(const json& source, const json& target, + const std::string& path) +{ + // the patch + json result(value_t::array); + + // if the values are the same, return empty patch + if (source == target) + { + return result; + } + + if (source.type() != target.type()) + { + // different types: replace value + result.push_back( + { + {"op", "replace"}, {"path", path}, {"value", target} + }); + } + else + { + switch (source.type()) + { + case value_t::array: + { + // first pass: traverse common elements + std::size_t i = 0; + while (i < source.size() and i < target.size()) + { + // recursive call to compare array values at index i + auto temp_diff = diff(source[i], target[i], path + "/" + std::to_string(i)); + result.insert(result.end(), temp_diff.begin(), temp_diff.end()); + ++i; + } + + // i now reached the end of at least one array + // in a second pass, traverse the remaining elements + + // remove my remaining elements + const auto end_index = static_cast(result.size()); + while (i < source.size()) + { + // add operations in reverse order to avoid invalid + // indices + result.insert(result.begin() + end_index, object( + { + {"op", "remove"}, + {"path", path + "/" + std::to_string(i)} + })); + ++i; + } + + // add other remaining elements + while (i < target.size()) + { + result.push_back( + { + {"op", "add"}, + {"path", path + "/" + std::to_string(i)}, + {"value", target[i]} + }); + ++i; + } + + break; + } + + case value_t::object: + { + // first pass: traverse this object's elements + for (auto it = source.cbegin(); it != source.cend(); ++it) + { + // escape the key name to be used in a JSON patch + const auto key = json_pointer::escape(it.key()); + + if (target.find(it.key()) != target.end()) + { + // recursive call to compare object values at key it + auto temp_diff = diff(it.value(), target[it.key()], path + "/" + key); + result.insert(result.end(), temp_diff.begin(), temp_diff.end()); + } + else + { + // found a key that is not in o -> remove it + result.push_back(object( + { + {"op", "remove"}, {"path", path + "/" + key} + })); + } + } + + // second pass: traverse other object's elements + for (auto it = target.cbegin(); it != target.cend(); ++it) + { + if (source.find(it.key()) == source.end()) + { + // found a key that is not in this -> add it + const auto key = json_pointer::escape(it.key()); + result.push_back( + { + {"op", "add"}, {"path", path + "/" + key}, + {"value", it.value()} + }); + } + } + + break; + } + + default: + { + // both primitive type: replace value + result.push_back( + { + {"op", "replace"}, {"path", path}, {"value", target} + }); + break; + } + } + } + + return result; +} + +void json::merge_patch(const json& patch) +{ + if (patch.is_object()) + { + if (not is_object()) + { + *this = object(); + } + for (auto it = patch.begin(); it != patch.end(); ++it) + { + if (it.value().is_null()) + { + erase(it.key()); + } + else + { + operator[](it.key()).merge_patch(it.value()); + } + } + } + else + { + *this = patch; + } +} + +} // namespace wpi diff --git a/wpiutil/src/main/native/cpp/json_binary_reader.cpp b/wpiutil/src/main/native/cpp/json_binary_reader.cpp index 480f46a354..9298e924f0 100644 --- a/wpiutil/src/main/native/cpp/json_binary_reader.cpp +++ b/wpiutil/src/main/native/cpp/json_binary_reader.cpp @@ -1,5 +1,5 @@ /*----------------------------------------------------------------------------*/ -/* Modifications Copyright (c) FIRST 2017. All Rights Reserved. */ +/* Modifications Copyright (c) 2017-2018 FIRST. All Rights Reserved. */ /* Open Source Software - may be modified and shared by FRC teams. The code */ /* must be accompanied by the FIRST BSD license file in the root directory of */ /* the project. */ @@ -7,11 +7,11 @@ /* __ _____ _____ _____ __| | __| | | | JSON for Modern C++ -| | |__ | | | | | | version 2.1.1 +| | |__ | | | | | | version 3.1.2 |_____|_____|_____|_|___| https://github.com/nlohmann/json Licensed under the MIT License . -Copyright (c) 2013-2017 Niels Lohmann . +Copyright (c) 2013-2018 Niels Lohmann . Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal @@ -34,77 +34,127 @@ SOFTWARE. #define WPI_JSON_IMPLEMENTATION #include "wpi/json.h" -#include +#include // ldexp -#include "wpi/Format.h" #include "wpi/raw_istream.h" -#include "wpi/raw_ostream.h" -using namespace wpi; - -namespace { +namespace wpi { /*! @brief deserialization of CBOR and MessagePack values */ -class binary_reader +class json::binary_reader { - using value_t = json::value_t; - public: /*! @brief create a binary reader @param[in] adapter input adapter to read from */ - explicit binary_reader(wpi::raw_istream& s) - : is(s), is_little_endian(little_endianess()) + explicit binary_reader(raw_istream& s) : is(s) { } /*! @brief create a JSON value from CBOR input - @param[in] get_char whether a new character should be retrieved from - the input (true, default) or whether the last - read character should be considered instead - + @param[in] strict whether to expect the input to be consumed completed @return JSON value created from CBOR input - @throw parse_error.110 if input ended unexpectedly + @throw parse_error.110 if input ended unexpectedly or the end of file was + not reached when @a strict was set to true @throw parse_error.112 if unsupported byte was read */ - json parse_cbor(bool get_char = true); + json parse_cbor(const bool strict) + { + const auto res = parse_cbor_internal(); + if (strict) + { + get(); + expect_eof(); + } + return res; + } /*! @brief create a JSON value from MessagePack input + @param[in] strict whether to expect the input to be consumed completed @return JSON value created from MessagePack input - @throw parse_error.110 if input ended unexpectedly + @throw parse_error.110 if input ended unexpectedly or the end of file was + not reached when @a strict was set to true @throw parse_error.112 if unsupported byte was read */ - json parse_msgpack(); + json parse_msgpack(const bool strict) + { + const auto res = parse_msgpack_internal(); + if (strict) + { + get(); + expect_eof(); + } + return res; + } + + /*! + @brief create a JSON value from UBJSON input + + @param[in] strict whether to expect the input to be consumed completed + @return JSON value created from UBJSON input + + @throw parse_error.110 if input ended unexpectedly or the end of file was + not reached when @a strict was set to true + @throw parse_error.112 if unsupported byte was read + */ + json parse_ubjson(const bool strict) + { + const auto res = parse_ubjson_internal(); + if (strict) + { + get_ignore_noop(); + expect_eof(); + } + return res; + } /*! @brief determine system byte order - @return true iff system's byte order is little endian + @return true if and only if system's byte order is little endian @note from http://stackoverflow.com/a/1001328/266378 */ - static bool little_endianess() noexcept + static bool little_endianess(int num = 1) noexcept { - int num = 1; return (*reinterpret_cast(&num) == 1); } private: + /*! + @param[in] get_char whether a new character should be retrieved from the + input (true, default) or whether the last read + character should be considered instead + */ + json parse_cbor_internal(const bool get_char = true); + + json parse_msgpack_internal(); + + /*! + @param[in] get_char whether a new character should be retrieved from the + input (true, default) or whether the last read + character should be considered instead + */ + json parse_ubjson_internal(const bool get_char = true) + { + return get_ubjson_value(get_char ? get_ignore_noop() : current); + } + /*! @brief get next character from the input This function provides the interface to the used input adapter. It does - not throw in case the input reached EOF, but returns + not throw in case the input reached EOF, but returns a -'ve valued `std::char_traits::eof()` in that case. @return character read from the input @@ -125,43 +175,56 @@ class binary_reader return current; } + /*! + @return character read from the input after ignoring all 'N' entries + */ + int get_ignore_noop() + { + do + { + get(); + } + while (current == 'N'); + + return current; + } + /* @brief read a number from the input - @tparam T the type of the number + @tparam NumberType the type of the number - @return number of type @a T + @return number of type @a NumberType @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. - @throw parse_error.110 if input has less than `sizeof(T)` bytes + @throw parse_error.110 if input has less than `sizeof(NumberType)` bytes */ - template - T get_number() + template NumberType get_number() { // step 1: read input into array with system's byte order - std::array vec; - for (size_t i = 0; i < sizeof(T); ++i) + std::array vec; + for (std::size_t i = 0; i < sizeof(NumberType); ++i) { get(); - check_eof(); + unexpect_eof(); // reverse byte order prior to conversion if necessary if (is_little_endian) { - vec[sizeof(T) - i - 1] = static_cast(current); + vec[sizeof(NumberType) - i - 1] = static_cast(current); } else { - vec[i] = static_cast(current); + vec[i] = static_cast(current); // LCOV_EXCL_LINE } } // step 2: convert array into number of type T and return - T result; - std::memcpy(&result, vec.data(), sizeof(T)); + NumberType result; + std::memcpy(&result, vec.data(), sizeof(NumberType)); return result; } @@ -170,11 +233,26 @@ class binary_reader @param[in] len number of bytes to read + @note We can not reserve @a len bytes for the result, because @a len + may be too large. Usually, @ref unexpect_eof() detects the end of + the input before we run out of string memory. + @return string created by reading @a len bytes @throw parse_error.110 if input has less than @a len bytes */ - std::string get_string(const size_t len); + template + std::string get_string(const NumberType len) + { + std::string result; + std::generate_n(std::back_inserter(result), len, [this]() + { + get(); + unexpect_eof(); + return static_cast(current); + }); + return result; + } /*! @brief reads a CBOR string @@ -186,10 +264,34 @@ class binary_reader @return string @throw parse_error.110 if input ended - @throw parse_error.113 if an unexpexted byte is read + @throw parse_error.113 if an unexpected byte is read */ std::string get_cbor_string(); + template + json get_cbor_array(const NumberType len) + { + json result = value_t::array; + std::generate_n(std::back_inserter(*result.m_value.array), len, [this]() + { + return parse_cbor_internal(); + }); + return result; + } + + template + json get_cbor_object(const NumberType len) + { + json result = value_t::object; + for (NumberType i = 0; i < len; ++i) + { + get(); + auto key = get_cbor_string(); + (*result.m_value.object)[key] = parse_cbor_internal(); + } + return result; + } + /*! @brief reads a MessagePack string @@ -199,47 +301,113 @@ class binary_reader @return string @throw parse_error.110 if input ended - @throw parse_error.113 if an unexpexted byte is read + @throw parse_error.113 if an unexpected byte is read */ std::string get_msgpack_string(); + template + json get_msgpack_array(const NumberType len) + { + json result = value_t::array; + std::generate_n(std::back_inserter(*result.m_value.array), len, [this]() + { + return parse_msgpack_internal(); + }); + return result; + } + + template + json get_msgpack_object(const NumberType len) + { + json result = value_t::object; + for (NumberType i = 0; i < len; ++i) + { + get(); + auto key = get_msgpack_string(); + (*result.m_value.object)[key] = parse_msgpack_internal(); + } + return result; + } + /*! - @brief check if input ended + @brief reads a UBJSON string + + This function is either called after reading the 'S' byte explicitly + indicating a string, or in case of an object key where the 'S' byte can be + left out. + + @param[in] get_char whether a new character should be retrieved from the + input (true, default) or whether the last read + character should be considered instead + + @return string + + @throw parse_error.110 if input ended + @throw parse_error.113 if an unexpected byte is read + */ + std::string get_ubjson_string(const bool get_char = true); + + /*! + @brief determine the type and size for a container + + In the optimized UBJSON format, a type and a size can be provided to allow + for a more compact representation. + + @return pair of the size and the type + */ + std::pair get_ubjson_size_type(); + + json get_ubjson_value(const int prefix); + + json get_ubjson_array(); + + json get_ubjson_object(); + + /*! + @brief throw if end of input is not reached + @throw parse_error.110 if input not ended + */ + void expect_eof() const + { + if (JSON_UNLIKELY(current != std::char_traits::eof())) + { + JSON_THROW(parse_error::create(110, chars_read, "expected end of input")); + } + } + + /*! + @briefthrow if end of input is reached @throw parse_error.110 if input ended */ - void check_eof() const + void unexpect_eof() const { if (JSON_UNLIKELY(current == std::char_traits::eof())) { - JSON_THROW(json::parse_error::create(110, chars_read, "unexpected end of input")); + JSON_THROW(parse_error::create(110, chars_read, "unexpected end of input")); } } private: /// input adapter - wpi::raw_istream& is; + raw_istream& is; /// the current character int current = std::char_traits::eof(); /// the number of characters read - size_t chars_read = 0; + std::size_t chars_read = 0; /// whether we can assume little endianess - const bool is_little_endian = true; + const bool is_little_endian = little_endianess(); }; -} // anonymous namespace - -json binary_reader::parse_cbor(bool get_char) +json json::binary_reader::parse_cbor_internal(const bool get_char) { switch (get_char ? get() : current) { // EOF case std::char_traits::eof(): - { - JSON_THROW(json::parse_error::create(110, chars_read, "unexpected end of input")); - } + JSON_THROW(parse_error::create(110, chars_read, "unexpected end of input")); // Integer 0x00..0x17 (0..23) case 0x00: @@ -252,12 +420,12 @@ json binary_reader::parse_cbor(bool get_char) case 0x07: case 0x08: case 0x09: - case 0x0a: - case 0x0b: - case 0x0c: - case 0x0d: - case 0x0e: - case 0x0f: + case 0x0A: + case 0x0B: + case 0x0C: + case 0x0D: + case 0x0E: + case 0x0F: case 0x10: case 0x11: case 0x12: @@ -266,29 +434,19 @@ json binary_reader::parse_cbor(bool get_char) case 0x15: case 0x16: case 0x17: - { - return static_cast(current); - } + return static_cast(current); case 0x18: // Unsigned integer (one-byte uint8_t follows) - { return get_number(); - } case 0x19: // Unsigned integer (two-byte uint16_t follows) - { return get_number(); - } - case 0x1a: // Unsigned integer (four-byte uint32_t follows) - { + case 0x1A: // Unsigned integer (four-byte uint32_t follows) return get_number(); - } - case 0x1b: // Unsigned integer (eight-byte uint64_t follows) - { + case 0x1B: // Unsigned integer (eight-byte uint64_t follows) return get_number(); - } // Negative integer -1-0x00..-1-0x17 (-1..-24) case 0x20: @@ -301,12 +459,12 @@ json binary_reader::parse_cbor(bool get_char) case 0x27: case 0x28: case 0x29: - case 0x2a: - case 0x2b: - case 0x2c: - case 0x2d: - case 0x2e: - case 0x2f: + case 0x2A: + case 0x2B: + case 0x2C: + case 0x2D: + case 0x2E: + case 0x2F: case 0x30: case 0x31: case 0x32: @@ -315,29 +473,27 @@ json binary_reader::parse_cbor(bool get_char) case 0x35: case 0x36: case 0x37: - { return static_cast(0x20 - 1 - current); - } case 0x38: // Negative integer (one-byte uint8_t follows) { - // must be uint8_t ! - return static_cast(-1) - get_number(); + return static_cast(-1) - get_number(); } case 0x39: // Negative integer -1-n (two-byte uint16_t follows) { - return static_cast(-1) - get_number(); + return static_cast(-1) - get_number(); } - case 0x3a: // Negative integer -1-n (four-byte uint32_t follows) + case 0x3A: // Negative integer -1-n (four-byte uint32_t follows) { - return static_cast(-1) - get_number(); + return static_cast(-1) - get_number(); } - case 0x3b: // Negative integer -1-n (eight-byte uint64_t follows) + case 0x3B: // Negative integer -1-n (eight-byte uint64_t follows) { - return static_cast(-1) - static_cast(get_number()); + return static_cast(-1) - + static_cast(get_number()); } // UTF-8 string (0x00..0x17 bytes follow) @@ -351,12 +507,12 @@ json binary_reader::parse_cbor(bool get_char) case 0x67: case 0x68: case 0x69: - case 0x6a: - case 0x6b: - case 0x6c: - case 0x6d: - case 0x6e: - case 0x6f: + case 0x6A: + case 0x6B: + case 0x6C: + case 0x6D: + case 0x6E: + case 0x6F: case 0x70: case 0x71: case 0x72: @@ -367,9 +523,9 @@ json binary_reader::parse_cbor(bool get_char) case 0x77: case 0x78: // UTF-8 string (one-byte uint8_t for n follows) case 0x79: // UTF-8 string (two-byte uint16_t for n follow) - case 0x7a: // UTF-8 string (four-byte uint32_t for n follow) - case 0x7b: // UTF-8 string (eight-byte uint64_t for n follow) - case 0x7f: // UTF-8 string (indefinite length) + case 0x7A: // UTF-8 string (four-byte uint32_t for n follow) + case 0x7B: // UTF-8 string (eight-byte uint64_t for n follow) + case 0x7F: // UTF-8 string (indefinite length) { return get_cbor_string(); } @@ -385,12 +541,12 @@ json binary_reader::parse_cbor(bool get_char) case 0x87: case 0x88: case 0x89: - case 0x8a: - case 0x8b: - case 0x8c: - case 0x8d: - case 0x8e: - case 0x8f: + case 0x8A: + case 0x8B: + case 0x8C: + case 0x8D: + case 0x8E: + case 0x8F: case 0x90: case 0x91: case 0x92: @@ -400,190 +556,120 @@ json binary_reader::parse_cbor(bool get_char) case 0x96: case 0x97: { - json result = value_t::array; - const auto len = static_cast(current & 0x1f); - for (size_t i = 0; i < len; ++i) - { - result.push_back(parse_cbor()); - } - return result; + return get_cbor_array(current & 0x1F); } case 0x98: // array (one-byte uint8_t for n follows) { - json result = value_t::array; - const auto len = static_cast(get_number()); - for (size_t i = 0; i < len; ++i) - { - result.push_back(parse_cbor()); - } - return result; + return get_cbor_array(get_number()); } case 0x99: // array (two-byte uint16_t for n follow) { - json result = value_t::array; - const auto len = static_cast(get_number()); - for (size_t i = 0; i < len; ++i) - { - result.push_back(parse_cbor()); - } - return result; + return get_cbor_array(get_number()); } - case 0x9a: // array (four-byte uint32_t for n follow) + case 0x9A: // array (four-byte uint32_t for n follow) { - json result = value_t::array; - const auto len = static_cast(get_number()); - for (size_t i = 0; i < len; ++i) - { - result.push_back(parse_cbor()); - } - return result; + return get_cbor_array(get_number()); } - case 0x9b: // array (eight-byte uint64_t for n follow) + case 0x9B: // array (eight-byte uint64_t for n follow) { - json result = value_t::array; - const auto len = static_cast(get_number()); - for (size_t i = 0; i < len; ++i) - { - result.push_back(parse_cbor()); - } - return result; + return get_cbor_array(get_number()); } - case 0x9f: // array (indefinite length) + case 0x9F: // array (indefinite length) { json result = value_t::array; - while (get() != 0xff) + while (get() != 0xFF) { - result.push_back(parse_cbor(false)); + result.push_back(parse_cbor_internal(false)); } return result; } // map (0x00..0x17 pairs of data items follow) - case 0xa0: - case 0xa1: - case 0xa2: - case 0xa3: - case 0xa4: - case 0xa5: - case 0xa6: - case 0xa7: - case 0xa8: - case 0xa9: - case 0xaa: - case 0xab: - case 0xac: - case 0xad: - case 0xae: - case 0xaf: - case 0xb0: - case 0xb1: - case 0xb2: - case 0xb3: - case 0xb4: - case 0xb5: - case 0xb6: - case 0xb7: + case 0xA0: + case 0xA1: + case 0xA2: + case 0xA3: + case 0xA4: + case 0xA5: + case 0xA6: + case 0xA7: + case 0xA8: + case 0xA9: + case 0xAA: + case 0xAB: + case 0xAC: + case 0xAD: + case 0xAE: + case 0xAF: + case 0xB0: + case 0xB1: + case 0xB2: + case 0xB3: + case 0xB4: + case 0xB5: + case 0xB6: + case 0xB7: + { + return get_cbor_object(current & 0x1F); + } + + case 0xB8: // map (one-byte uint8_t for n follows) + { + return get_cbor_object(get_number()); + } + + case 0xB9: // map (two-byte uint16_t for n follow) + { + return get_cbor_object(get_number()); + } + + case 0xBA: // map (four-byte uint32_t for n follow) + { + return get_cbor_object(get_number()); + } + + case 0xBB: // map (eight-byte uint64_t for n follow) + { + return get_cbor_object(get_number()); + } + + case 0xBF: // map (indefinite length) { json result = value_t::object; - const auto len = static_cast(current & 0x1f); - for (size_t i = 0; i < len; ++i) + while (get() != 0xFF) { - get(); auto key = get_cbor_string(); - result[key] = parse_cbor(); + result[key] = parse_cbor_internal(); } return result; } - case 0xb8: // map (one-byte uint8_t for n follows) - { - json result = value_t::object; - const auto len = static_cast(get_number()); - for (size_t i = 0; i < len; ++i) - { - get(); - auto key = get_cbor_string(); - result[key] = parse_cbor(); - } - return result; - } - - case 0xb9: // map (two-byte uint16_t for n follow) - { - json result = value_t::object; - const auto len = static_cast(get_number()); - for (size_t i = 0; i < len; ++i) - { - get(); - auto key = get_cbor_string(); - result[key] = parse_cbor(); - } - return result; - } - - case 0xba: // map (four-byte uint32_t for n follow) - { - json result = value_t::object; - const auto len = static_cast(get_number()); - for (size_t i = 0; i < len; ++i) - { - get(); - auto key = get_cbor_string(); - result[key] = parse_cbor(); - } - return result; - } - - case 0xbb: // map (eight-byte uint64_t for n follow) - { - json result = value_t::object; - const auto len = static_cast(get_number()); - for (size_t i = 0; i < len; ++i) - { - get(); - auto key = get_cbor_string(); - result[key] = parse_cbor(); - } - return result; - } - - case 0xbf: // map (indefinite length) - { - json result = value_t::object; - while (get() != 0xff) - { - auto key = get_cbor_string(); - result[key] = parse_cbor(); - } - return result; - } - - case 0xf4: // false + case 0xF4: // false { return false; } - case 0xf5: // true + case 0xF5: // true { return true; } - case 0xf6: // null + case 0xF6: // null { return value_t::null; } - case 0xf9: // Half-Precision Float (two-byte IEEE 754) + case 0xF9: // Half-Precision Float (two-byte IEEE 754) { const int byte1 = get(); - check_eof(); + unexpect_eof(); const int byte2 = get(); - check_eof(); + unexpect_eof(); // code from RFC 7049, Appendix D, Figure 3: // As half-precision floating-point numbers were only added @@ -594,8 +680,8 @@ json binary_reader::parse_cbor(bool get_char) // half-precision floating-point numbers in the C language // is shown in Fig. 3. const int half = (byte1 << 8) + byte2; - const int exp = (half >> 10) & 0x1f; - const int mant = half & 0x3ff; + const int exp = (half >> 10) & 0x1F; + const int mant = half & 0x3FF; double val; if (exp == 0) { @@ -607,37 +693,355 @@ json binary_reader::parse_cbor(bool get_char) } else { - val = mant == 0 - ? std::numeric_limits::infinity() + val = (mant == 0) ? std::numeric_limits::infinity() : std::numeric_limits::quiet_NaN(); } return (half & 0x8000) != 0 ? -val : val; } - case 0xfa: // Single-Precision Float (four-byte IEEE 754) + case 0xFA: // Single-Precision Float (four-byte IEEE 754) { return get_number(); } - case 0xfb: // Double-Precision Float (eight-byte IEEE 754) + case 0xFB: // Double-Precision Float (eight-byte IEEE 754) { return get_number(); } default: // anything else (0xFF is handled inside the other types) { - std::string s; - raw_string_ostream ss(s); - ss << "error reading CBOR; last byte: "; - ss << format_hex(current, 2); - JSON_THROW(json::parse_error::create(112, chars_read, ss.str())); + JSON_THROW(parse_error::create(112, chars_read, "error reading CBOR; last byte: 0x" + Twine::utohexstr(current))); } } } -std::string binary_reader::get_cbor_string() +json json::binary_reader::parse_msgpack_internal() { - check_eof(); + switch (get()) + { + // EOF + case std::char_traits::eof(): + JSON_THROW(parse_error::create(110, chars_read, "unexpected end of input")); + + // positive fixint + case 0x00: + case 0x01: + case 0x02: + case 0x03: + case 0x04: + case 0x05: + case 0x06: + case 0x07: + case 0x08: + case 0x09: + case 0x0A: + case 0x0B: + case 0x0C: + case 0x0D: + 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: + case 0x20: + case 0x21: + case 0x22: + case 0x23: + case 0x24: + case 0x25: + case 0x26: + case 0x27: + case 0x28: + case 0x29: + case 0x2A: + case 0x2B: + case 0x2C: + case 0x2D: + case 0x2E: + case 0x2F: + case 0x30: + case 0x31: + case 0x32: + case 0x33: + case 0x34: + case 0x35: + case 0x36: + case 0x37: + case 0x38: + case 0x39: + case 0x3A: + case 0x3B: + case 0x3C: + case 0x3D: + case 0x3E: + case 0x3F: + case 0x40: + case 0x41: + case 0x42: + case 0x43: + case 0x44: + case 0x45: + case 0x46: + case 0x47: + case 0x48: + case 0x49: + case 0x4A: + case 0x4B: + case 0x4C: + case 0x4D: + case 0x4E: + case 0x4F: + case 0x50: + case 0x51: + case 0x52: + case 0x53: + case 0x54: + case 0x55: + case 0x56: + case 0x57: + case 0x58: + case 0x59: + case 0x5A: + case 0x5B: + case 0x5C: + case 0x5D: + case 0x5E: + case 0x5F: + case 0x60: + case 0x61: + case 0x62: + case 0x63: + case 0x64: + case 0x65: + case 0x66: + case 0x67: + case 0x68: + case 0x69: + case 0x6A: + case 0x6B: + case 0x6C: + case 0x6D: + case 0x6E: + case 0x6F: + case 0x70: + case 0x71: + case 0x72: + case 0x73: + case 0x74: + case 0x75: + case 0x76: + case 0x77: + case 0x78: + case 0x79: + case 0x7A: + case 0x7B: + case 0x7C: + case 0x7D: + case 0x7E: + case 0x7F: + return static_cast(current); + + // fixmap + case 0x80: + case 0x81: + case 0x82: + case 0x83: + case 0x84: + case 0x85: + case 0x86: + case 0x87: + case 0x88: + case 0x89: + case 0x8A: + case 0x8B: + case 0x8C: + case 0x8D: + case 0x8E: + case 0x8F: + { + return get_msgpack_object(current & 0x0F); + } + + // fixarray + case 0x90: + case 0x91: + case 0x92: + case 0x93: + case 0x94: + case 0x95: + case 0x96: + case 0x97: + case 0x98: + case 0x99: + case 0x9A: + case 0x9B: + case 0x9C: + case 0x9D: + case 0x9E: + case 0x9F: + { + return get_msgpack_array(current & 0x0F); + } + + // fixstr + case 0xA0: + case 0xA1: + case 0xA2: + case 0xA3: + case 0xA4: + case 0xA5: + case 0xA6: + case 0xA7: + case 0xA8: + case 0xA9: + case 0xAA: + case 0xAB: + case 0xAC: + case 0xAD: + case 0xAE: + case 0xAF: + case 0xB0: + case 0xB1: + case 0xB2: + case 0xB3: + case 0xB4: + case 0xB5: + case 0xB6: + case 0xB7: + case 0xB8: + case 0xB9: + case 0xBA: + case 0xBB: + case 0xBC: + case 0xBD: + case 0xBE: + case 0xBF: + return get_msgpack_string(); + + case 0xC0: // nil + return value_t::null; + + case 0xC2: // false + return false; + + case 0xC3: // true + return true; + + case 0xCA: // float 32 + return get_number(); + + case 0xCB: // float 64 + return get_number(); + + case 0xCC: // uint 8 + return get_number(); + + case 0xCD: // uint 16 + return get_number(); + + case 0xCE: // uint 32 + return get_number(); + + case 0xCF: // uint 64 + return get_number(); + + case 0xD0: // int 8 + return get_number(); + + case 0xD1: // int 16 + return get_number(); + + case 0xD2: // int 32 + return get_number(); + + case 0xD3: // int 64 + return get_number(); + + case 0xD9: // str 8 + case 0xDA: // str 16 + case 0xDB: // str 32 + return get_msgpack_string(); + + case 0xDC: // array 16 + { + return get_msgpack_array(get_number()); + } + + case 0xDD: // array 32 + { + return get_msgpack_array(get_number()); + } + + case 0xDE: // map 16 + { + return get_msgpack_object(get_number()); + } + + case 0xDF: // map 32 + { + return get_msgpack_object(get_number()); + } + + // positive fixint + case 0xE0: + case 0xE1: + case 0xE2: + case 0xE3: + case 0xE4: + case 0xE5: + case 0xE6: + case 0xE7: + case 0xE8: + case 0xE9: + case 0xEA: + case 0xEB: + case 0xEC: + case 0xED: + case 0xEE: + case 0xEF: + case 0xF0: + case 0xF1: + case 0xF2: + case 0xF3: + case 0xF4: + case 0xF5: + case 0xF6: + case 0xF7: + case 0xF8: + case 0xF9: + case 0xFA: + case 0xFB: + case 0xFC: + case 0xFD: + case 0xFE: + case 0xFF: + return static_cast(current); + + default: // anything else + { + JSON_THROW(parse_error::create(112, chars_read, + "error reading MessagePack; last byte: 0x" + Twine::utohexstr(current))); + } + } +} + +std::string json::binary_reader::get_cbor_string() +{ + unexpect_eof(); switch (current) { @@ -652,12 +1056,12 @@ std::string binary_reader::get_cbor_string() case 0x67: case 0x68: case 0x69: - case 0x6a: - case 0x6b: - case 0x6c: - case 0x6d: - case 0x6e: - case 0x6f: + case 0x6A: + case 0x6B: + case 0x6C: + case 0x6D: + case 0x6E: + case 0x6F: case 0x70: case 0x71: case 0x72: @@ -667,567 +1071,343 @@ std::string binary_reader::get_cbor_string() case 0x76: case 0x77: { - const auto len = static_cast(current & 0x1f); - return get_string(len); + return get_string(current & 0x1F); } case 0x78: // UTF-8 string (one-byte uint8_t for n follows) { - const auto len = static_cast(get_number()); - return get_string(len); + return get_string(get_number()); } case 0x79: // UTF-8 string (two-byte uint16_t for n follow) { - const auto len = static_cast(get_number()); - return get_string(len); + return get_string(get_number()); } - case 0x7a: // UTF-8 string (four-byte uint32_t for n follow) + case 0x7A: // UTF-8 string (four-byte uint32_t for n follow) { - const auto len = static_cast(get_number()); - return get_string(len); + return get_string(get_number()); } - case 0x7b: // UTF-8 string (eight-byte uint64_t for n follow) + case 0x7B: // UTF-8 string (eight-byte uint64_t for n follow) { - const auto len = static_cast(get_number()); - return get_string(len); + return get_string(get_number()); } - case 0x7f: // UTF-8 string (indefinite length) + case 0x7F: // UTF-8 string (indefinite length) { std::string result; - while (get() != 0xff) + while (get() != 0xFF) { - check_eof(); - result.append(1, static_cast(current)); + result.append(get_cbor_string()); } return result; } default: { - std::string s; - raw_string_ostream ss(s); - ss << "expected a CBOR string; last byte: "; - ss << format_hex(current, 2); - JSON_THROW(json::parse_error::create(113, chars_read, ss.str())); + JSON_THROW(parse_error::create(113, chars_read, "expected a CBOR string; last byte: 0x" + Twine::utohexstr(current))); } } } -json binary_reader::parse_msgpack() +std::string json::binary_reader::get_msgpack_string() { - switch (get()) - { - // EOF - case std::char_traits::eof(): - { - JSON_THROW(json::parse_error::create(110, chars_read, "unexpected end of input")); - } - - // positive fixint - case 0x00: - case 0x01: - case 0x02: - case 0x03: - case 0x04: - case 0x05: - case 0x06: - case 0x07: - case 0x08: - case 0x09: - case 0x0a: - case 0x0b: - case 0x0c: - case 0x0d: - 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: - case 0x20: - case 0x21: - case 0x22: - case 0x23: - case 0x24: - case 0x25: - case 0x26: - case 0x27: - case 0x28: - case 0x29: - case 0x2a: - case 0x2b: - case 0x2c: - case 0x2d: - case 0x2e: - case 0x2f: - case 0x30: - case 0x31: - case 0x32: - case 0x33: - case 0x34: - case 0x35: - case 0x36: - case 0x37: - case 0x38: - case 0x39: - case 0x3a: - case 0x3b: - case 0x3c: - case 0x3d: - case 0x3e: - case 0x3f: - case 0x40: - case 0x41: - case 0x42: - case 0x43: - case 0x44: - case 0x45: - case 0x46: - case 0x47: - case 0x48: - case 0x49: - case 0x4a: - case 0x4b: - case 0x4c: - case 0x4d: - case 0x4e: - case 0x4f: - case 0x50: - case 0x51: - case 0x52: - case 0x53: - case 0x54: - case 0x55: - case 0x56: - case 0x57: - case 0x58: - case 0x59: - case 0x5a: - case 0x5b: - case 0x5c: - case 0x5d: - case 0x5e: - case 0x5f: - case 0x60: - case 0x61: - case 0x62: - case 0x63: - case 0x64: - case 0x65: - case 0x66: - case 0x67: - case 0x68: - case 0x69: - case 0x6a: - case 0x6b: - case 0x6c: - case 0x6d: - case 0x6e: - case 0x6f: - case 0x70: - case 0x71: - case 0x72: - case 0x73: - case 0x74: - case 0x75: - case 0x76: - case 0x77: - case 0x78: - case 0x79: - case 0x7a: - case 0x7b: - case 0x7c: - case 0x7d: - case 0x7e: - case 0x7f: - { - return static_cast(current); - } - - // fixmap - case 0x80: - case 0x81: - case 0x82: - case 0x83: - case 0x84: - case 0x85: - case 0x86: - case 0x87: - case 0x88: - case 0x89: - case 0x8a: - case 0x8b: - case 0x8c: - case 0x8d: - case 0x8e: - case 0x8f: - { - json result = value_t::object; - const auto len = static_cast(current & 0x0f); - for (size_t i = 0; i < len; ++i) - { - get(); - auto key = get_msgpack_string(); - result[key] = parse_msgpack(); - } - return result; - } - - // fixarray - case 0x90: - case 0x91: - case 0x92: - case 0x93: - case 0x94: - case 0x95: - case 0x96: - case 0x97: - case 0x98: - case 0x99: - case 0x9a: - case 0x9b: - case 0x9c: - case 0x9d: - case 0x9e: - case 0x9f: - { - json result = value_t::array; - const auto len = static_cast(current & 0x0f); - for (size_t i = 0; i < len; ++i) - { - result.push_back(parse_msgpack()); - } - return result; - } - - // fixstr - case 0xa0: - case 0xa1: - case 0xa2: - case 0xa3: - case 0xa4: - case 0xa5: - case 0xa6: - case 0xa7: - case 0xa8: - case 0xa9: - case 0xaa: - case 0xab: - case 0xac: - case 0xad: - case 0xae: - case 0xaf: - case 0xb0: - case 0xb1: - case 0xb2: - case 0xb3: - case 0xb4: - case 0xb5: - case 0xb6: - case 0xb7: - case 0xb8: - case 0xb9: - case 0xba: - case 0xbb: - case 0xbc: - case 0xbd: - case 0xbe: - case 0xbf: - { - return get_msgpack_string(); - } - - case 0xc0: // nil - { - return value_t::null; - } - - case 0xc2: // false - { - return false; - } - - case 0xc3: // true - { - return true; - } - - case 0xca: // float 32 - { - return get_number(); - } - - case 0xcb: // float 64 - { - return get_number(); - } - - case 0xcc: // uint 8 - { - return get_number(); - } - - case 0xcd: // uint 16 - { - return get_number(); - } - - case 0xce: // uint 32 - { - return get_number(); - } - - case 0xcf: // uint 64 - { - return get_number(); - } - - case 0xd0: // int 8 - { - return get_number(); - } - - case 0xd1: // int 16 - { - return get_number(); - } - - case 0xd2: // int 32 - { - return get_number(); - } - - case 0xd3: // int 64 - { - return get_number(); - } - - case 0xd9: // str 8 - case 0xda: // str 16 - case 0xdb: // str 32 - { - return get_msgpack_string(); - } - - case 0xdc: // array 16 - { - json result = value_t::array; - const auto len = static_cast(get_number()); - for (size_t i = 0; i < len; ++i) - { - result.push_back(parse_msgpack()); - } - return result; - } - - case 0xdd: // array 32 - { - json result = value_t::array; - const auto len = static_cast(get_number()); - for (size_t i = 0; i < len; ++i) - { - result.push_back(parse_msgpack()); - } - return result; - } - - case 0xde: // map 16 - { - json result = value_t::object; - const auto len = static_cast(get_number()); - for (size_t i = 0; i < len; ++i) - { - get(); - auto key = get_msgpack_string(); - result[key] = parse_msgpack(); - } - return result; - } - - case 0xdf: // map 32 - { - json result = value_t::object; - const auto len = static_cast(get_number()); - for (size_t i = 0; i < len; ++i) - { - get(); - auto key = get_msgpack_string(); - result[key] = parse_msgpack(); - } - return result; - } - - // positive fixint - case 0xe0: - case 0xe1: - case 0xe2: - case 0xe3: - case 0xe4: - case 0xe5: - case 0xe6: - case 0xe7: - case 0xe8: - case 0xe9: - case 0xea: - case 0xeb: - case 0xec: - case 0xed: - case 0xee: - case 0xef: - case 0xf0: - case 0xf1: - case 0xf2: - case 0xf3: - case 0xf4: - case 0xf5: - case 0xf6: - case 0xf7: - case 0xf8: - case 0xf9: - case 0xfa: - case 0xfb: - case 0xfc: - case 0xfd: - case 0xfe: - case 0xff: - { - return static_cast(current); - } - - default: // anything else - { - std::string s; - raw_string_ostream ss(s); - ss << "error reading MessagePack; last byte: "; - ss << format_hex(current, 2); - JSON_THROW(json::parse_error::create(112, chars_read, ss.str())); - } - } -} - -std::string binary_reader::get_msgpack_string() -{ - check_eof(); + unexpect_eof(); switch (current) { // fixstr - case 0xa0: - case 0xa1: - case 0xa2: - case 0xa3: - case 0xa4: - case 0xa5: - case 0xa6: - case 0xa7: - case 0xa8: - case 0xa9: - case 0xaa: - case 0xab: - case 0xac: - case 0xad: - case 0xae: - case 0xaf: - case 0xb0: - case 0xb1: - case 0xb2: - case 0xb3: - case 0xb4: - case 0xb5: - case 0xb6: - case 0xb7: - case 0xb8: - case 0xb9: - case 0xba: - case 0xbb: - case 0xbc: - case 0xbd: - case 0xbe: - case 0xbf: + case 0xA0: + case 0xA1: + case 0xA2: + case 0xA3: + case 0xA4: + case 0xA5: + case 0xA6: + case 0xA7: + case 0xA8: + case 0xA9: + case 0xAA: + case 0xAB: + case 0xAC: + case 0xAD: + case 0xAE: + case 0xAF: + case 0xB0: + case 0xB1: + case 0xB2: + case 0xB3: + case 0xB4: + case 0xB5: + case 0xB6: + case 0xB7: + case 0xB8: + case 0xB9: + case 0xBA: + case 0xBB: + case 0xBC: + case 0xBD: + case 0xBE: + case 0xBF: { - const auto len = static_cast(current & 0x1f); - return get_string(len); + return get_string(current & 0x1F); } - case 0xd9: // str 8 + case 0xD9: // str 8 { - const auto len = static_cast(get_number()); - return get_string(len); + return get_string(get_number()); } - case 0xda: // str 16 + case 0xDA: // str 16 { - const auto len = static_cast(get_number()); - return get_string(len); + return get_string(get_number()); } - case 0xdb: // str 32 + case 0xDB: // str 32 { - const auto len = static_cast(get_number()); - return get_string(len); + return get_string(get_number()); } default: { - std::string s; - raw_string_ostream ss(s); - ss << "expected a MessagePack string; last byte: "; - ss << format_hex(current, 2); - JSON_THROW(json::parse_error::create(113, chars_read, ss.str())); + JSON_THROW(parse_error::create(113, chars_read, + "expected a MessagePack string; last byte: 0x" + Twine::utohexstr(current))); } } } -std::string binary_reader::get_string(const size_t len) +std::string json::binary_reader::get_ubjson_string(const bool get_char) { - std::string result; - for (size_t i = 0; i < len; ++i) + if (get_char) { - get(); - check_eof(); - result.append(1, static_cast(current)); + get(); // TODO: may we ignore N here? } + + unexpect_eof(); + + switch (current) + { + case 'U': + return get_string(get_number()); + case 'i': + return get_string(get_number()); + case 'I': + return get_string(get_number()); + case 'l': + return get_string(get_number()); + case 'L': + return get_string(get_number()); + default: + JSON_THROW(parse_error::create(113, chars_read, + "expected a UBJSON string; last byte: 0x" + Twine::utohexstr(current))); + } +} + +std::pair json::binary_reader::get_ubjson_size_type() +{ + std::size_t sz = std::string::npos; + int tc = 0; + + get_ignore_noop(); + + if (current == '$') + { + tc = get(); // must not ignore 'N', because 'N' maybe the type + unexpect_eof(); + + get_ignore_noop(); + if (current != '#') + { + JSON_THROW(parse_error::create(112, chars_read, + "expected '#' after UBJSON type information; last byte: 0x" + Twine::utohexstr(current))); + } + sz = parse_ubjson_internal(); + } + else if (current == '#') + { + sz = parse_ubjson_internal(); + } + + return std::make_pair(sz, tc); +} + +json json::binary_reader::get_ubjson_value(const int prefix) +{ + switch (prefix) + { + case std::char_traits::eof(): // EOF + JSON_THROW(parse_error::create(110, chars_read, "unexpected end of input")); + + case 'T': // true + return true; + case 'F': // false + return false; + + case 'Z': // null + return nullptr; + + case 'U': + return get_number(); + case 'i': + return get_number(); + case 'I': + return get_number(); + case 'l': + return get_number(); + case 'L': + return get_number(); + case 'd': + return get_number(); + case 'D': + return get_number(); + + case 'C': // char + { + get(); + unexpect_eof(); + if (JSON_UNLIKELY(current > 127)) + { + JSON_THROW(parse_error::create(113, chars_read, + "byte after 'C' must be in range 0x00..0x7F; last byte: 0x" + Twine::utohexstr(current))); + } + return std::string(1, static_cast(current)); + } + + case 'S': // string + return get_ubjson_string(); + + case '[': // array + return get_ubjson_array(); + + case '{': // object + return get_ubjson_object(); + + default: // anything else + JSON_THROW(parse_error::create(112, chars_read, + "error reading UBJSON; last byte: 0x" + Twine::utohexstr(current))); + } +} + +json json::binary_reader::get_ubjson_array() +{ + json result = value_t::array; + const auto size_and_type = get_ubjson_size_type(); + + if (size_and_type.first != std::string::npos) + { + if (JSON_UNLIKELY(size_and_type.first > result.max_size())) + { + JSON_THROW(out_of_range::create(408, + "excessive array size: " + Twine(size_and_type.first))); + } + + if (size_and_type.second != 0) + { + if (size_and_type.second != 'N') + { + std::generate_n(std::back_inserter(*result.m_value.array), + size_and_type.first, [this, size_and_type]() + { + return get_ubjson_value(size_and_type.second); + }); + } + } + else + { + std::generate_n(std::back_inserter(*result.m_value.array), + size_and_type.first, [this]() + { + return parse_ubjson_internal(); + }); + } + } + else + { + while (current != ']') + { + result.push_back(parse_ubjson_internal(false)); + get_ignore_noop(); + } + } + return result; } -json json::from_cbor(wpi::raw_istream& is) +json json::binary_reader::get_ubjson_object() { - binary_reader br(is); - return br.parse_cbor(); + json result = value_t::object; + const auto size_and_type = get_ubjson_size_type(); + + if (size_and_type.first != std::string::npos) + { + if (JSON_UNLIKELY(size_and_type.first > result.max_size())) + { + JSON_THROW(out_of_range::create(408, + "excessive object size: " + Twine(size_and_type.first))); + } + + if (size_and_type.second != 0) + { + for (size_t i = 0; i < size_and_type.first; ++i) + { + auto key = get_ubjson_string(); + (*result.m_value.object)[key] = get_ubjson_value(size_and_type.second); + } + } + else + { + for (size_t i = 0; i < size_and_type.first; ++i) + { + auto key = get_ubjson_string(); + (*result.m_value.object)[key] = parse_ubjson_internal(); + } + } + } + else + { + while (current != '}') + { + auto key = get_ubjson_string(false); + result[std::move(key)] = parse_ubjson_internal(); + get_ignore_noop(); + } + } + + return result; } -json json::from_cbor(StringRef s) +json json::from_cbor(raw_istream& is, const bool strict) { - wpi::raw_mem_istream is(s.data(), s.size()); - binary_reader br(is); - return br.parse_cbor(); + return binary_reader(is).parse_cbor(strict); } -json json::from_msgpack(wpi::raw_istream& is) +json json::from_cbor(ArrayRef arr, const bool strict) { - binary_reader br(is); - return br.parse_msgpack(); + raw_mem_istream is(arr); + return from_cbor(is, strict); } -json json::from_msgpack(StringRef s) +json json::from_msgpack(raw_istream& is, const bool strict) { - wpi::raw_mem_istream is(s.data(), s.size()); - binary_reader br(is); - return br.parse_msgpack(); + return binary_reader(is).parse_msgpack(strict); } + +json json::from_msgpack(ArrayRef arr, const bool strict) +{ + raw_mem_istream is(arr); + return from_msgpack(is, strict); +} + +json json::from_ubjson(raw_istream& is, const bool strict) +{ + return binary_reader(is).parse_ubjson(strict); +} + +json json::from_ubjson(ArrayRef arr, const bool strict) +{ + raw_mem_istream is(arr); + return from_ubjson(is, strict); +} + +} // namespace wpi diff --git a/wpiutil/src/main/native/cpp/json_binary_writer.cpp b/wpiutil/src/main/native/cpp/json_binary_writer.cpp index aeacac123d..75c39aa763 100644 --- a/wpiutil/src/main/native/cpp/json_binary_writer.cpp +++ b/wpiutil/src/main/native/cpp/json_binary_writer.cpp @@ -1,5 +1,5 @@ /*----------------------------------------------------------------------------*/ -/* Modifications Copyright (c) FIRST 2017. All Rights Reserved. */ +/* Modifications Copyright (c) 2017-2018 FIRST. All Rights Reserved. */ /* Open Source Software - may be modified and shared by FRC teams. The code */ /* must be accompanied by the FIRST BSD license file in the root directory of */ /* the project. */ @@ -7,11 +7,11 @@ /* __ _____ _____ _____ __| | __| | | | JSON for Modern C++ -| | |__ | | | | | | version 2.1.1 +| | |__ | | | | | | version 3.1.2 |_____|_____|_____|_|___| https://github.com/nlohmann/json Licensed under the MIT License . -Copyright (c) 2013-2017 Niels Lohmann . +Copyright (c) 2013-2018 Niels Lohmann . Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal @@ -34,30 +34,24 @@ SOFTWARE. #define WPI_JSON_IMPLEMENTATION #include "wpi/json.h" -#include -#include // lconv, localeconv -#include // locale -#include // accumulate - -#include "wpi/SmallString.h" -#include "wpi/StringExtras.h" #include "wpi/raw_ostream.h" -using namespace wpi; +namespace wpi { /*! @brief serialization to CBOR and MessagePack values */ class json::binary_writer { + using CharType = unsigned char; + public: /*! @brief create a binary writer @param[in] adapter output adapter to write to */ - explicit binary_writer(raw_ostream& s) - : is_little_endian(little_endianess()), o(s) + explicit binary_writer(raw_ostream& s) : o(s) { } @@ -72,64 +66,107 @@ class json::binary_writer 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 + @param[in] j JSON value to serialize + @param[in] use_count whether to use '#' prefixes (optimized format) + @param[in] use_type whether to use '$' prefixes (optimized format) + @param[in] add_prefix whether prefixes need to be used for this value */ - static bool little_endianess() noexcept - { - int num = 1; - return (*reinterpret_cast(&num) == 1); - } + void write_ubjson(const json& j, const bool use_count, + const bool use_type, const bool add_prefix = true); private: - /*! - @brief[in] str string to serialize - */ void write_cbor_string(StringRef str); - /*! - @brief[in] str string to serialize - */ void write_msgpack_string(StringRef str); /* @brief write a number to output input - @param[in] n number of type @a T - @tparam T the type of the number + @param[in] n number of type @a NumberType + @tparam NumberType 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 + @note This function needs to respect the system's endianess, because bytes + in CBOR, MessagePack, and UBJSON are stored in network order (big endian) and therefore need reordering on little endian systems. */ - template - void write_number(T n) - { - // step 1: write number to array of length T - std::array vec; - std::memcpy(vec.data(), &n, sizeof(T)); + template + void write_number(const NumberType n); - // step 2: write array to output (with possible reordering) - for (size_t i = 0; i < sizeof(T); ++i) + // UBJSON: write number (floating point) + template::value, int>::type = 0> + void write_number_with_ubjson_prefix(const NumberType n, + const bool add_prefix) + { + if (add_prefix) { - // reverse byte order prior to conversion if necessary - if (is_little_endian) - { - o << static_cast(vec[sizeof(T) - i - 1]); - } - else - { - o << static_cast(vec[i]); - } + o << get_ubjson_float_prefix(n); } + write_number(n); + } + + // UBJSON: write number (unsigned integer) + template::value, int>::type = 0> + void write_number_with_ubjson_prefix(const NumberType n, + const bool add_prefix); + + // UBJSON: write number (signed integer) + template::value and + not std::is_floating_point::value, int>::type = 0> + void write_number_with_ubjson_prefix(const NumberType n, + const bool add_prefix); + + /*! + @brief determine the type prefix of container values + + @note This function does not need to be 100% accurate when it comes to + integer limits. In case a number exceeds the limits of int64_t, + this will be detected by a later call to function + write_number_with_ubjson_prefix. Therefore, we return 'L' for any + value that does not fit the previous limits. + */ + CharType ubjson_prefix(const json& j) const noexcept; + + static constexpr CharType get_cbor_float_prefix(float) + { + return static_cast(0xFA); // Single-Precision Float + } + + static constexpr CharType get_cbor_float_prefix(double) + { + return static_cast(0xFB); // Double-Precision Float + } + + static constexpr CharType get_msgpack_float_prefix(float) + { + return static_cast(0xCA); // float 32 + } + + static constexpr CharType get_msgpack_float_prefix(double) + { + return static_cast(0xCB); // float 64 + } + + static constexpr CharType get_ubjson_float_prefix(float) + { + return 'd'; // float 32 + } + + static constexpr CharType get_ubjson_float_prefix(double) + { + return 'D'; // float 64 } private: + static bool little_endianess(int num = 1) noexcept + { + return (*reinterpret_cast(&num) == 1); + } + /// whether we can assume little endianess - const bool is_little_endian = true; + const bool is_little_endian = little_endianess(); /// the output raw_ostream& o; @@ -141,13 +178,13 @@ void json::binary_writer::write_cbor(const json& j) { case value_t::null: { - o << static_cast(0xf6); + o << static_cast(0xF6); break; } case value_t::boolean: { - o << static_cast(j.m_value.boolean ? 0xf5 : 0xf4); + o << static_cast(j.m_value.boolean ? 0xF5 : 0xF4); break; } @@ -164,22 +201,22 @@ void json::binary_writer::write_cbor(const json& j) } else if (j.m_value.number_integer <= (std::numeric_limits::max)()) { - o << static_cast(0x18); + o << static_cast(0x18); write_number(static_cast(j.m_value.number_integer)); } else if (j.m_value.number_integer <= (std::numeric_limits::max)()) { - o << static_cast(0x19); + o << static_cast(0x19); write_number(static_cast(j.m_value.number_integer)); } else if (j.m_value.number_integer <= (std::numeric_limits::max)()) { - o << static_cast(0x1a); + o << static_cast(0x1A); write_number(static_cast(j.m_value.number_integer)); } else { - o << static_cast(0x1b); + o << static_cast(0x1B); write_number(static_cast(j.m_value.number_integer)); } } @@ -194,22 +231,22 @@ void json::binary_writer::write_cbor(const json& j) } else if (positive_number <= (std::numeric_limits::max)()) { - o << static_cast(0x38); + o << static_cast(0x38); write_number(static_cast(positive_number)); } else if (positive_number <= (std::numeric_limits::max)()) { - o << static_cast(0x39); + o << static_cast(0x39); write_number(static_cast(positive_number)); } else if (positive_number <= (std::numeric_limits::max)()) { - o << static_cast(0x3a); + o << static_cast(0x3A); write_number(static_cast(positive_number)); } else { - o << static_cast(0x3b); + o << static_cast(0x3B); write_number(static_cast(positive_number)); } } @@ -224,22 +261,22 @@ void json::binary_writer::write_cbor(const json& j) } else if (j.m_value.number_unsigned <= (std::numeric_limits::max)()) { - o << static_cast(0x18); + o << static_cast(0x18); write_number(static_cast(j.m_value.number_unsigned)); } else if (j.m_value.number_unsigned <= (std::numeric_limits::max)()) { - o << static_cast(0x19); + o << static_cast(0x19); write_number(static_cast(j.m_value.number_unsigned)); } else if (j.m_value.number_unsigned <= (std::numeric_limits::max)()) { - o << static_cast(0x1a); + o << static_cast(0x1A); write_number(static_cast(j.m_value.number_unsigned)); } else { - o << static_cast(0x1b); + o << static_cast(0x1B); write_number(static_cast(j.m_value.number_unsigned)); } break; @@ -247,8 +284,7 @@ void json::binary_writer::write_cbor(const json& j) case value_t::number_float: { - // Double-Precision Float - o << static_cast(0xfb); + o << get_cbor_float_prefix(j.m_value.number_float); write_number(j.m_value.number_float); break; } @@ -267,25 +303,25 @@ void json::binary_writer::write_cbor(const json& j) { write_number(static_cast(0x80 + N)); } - else if (N <= 0xff) + else if (N <= (std::numeric_limits::max)()) { - o << static_cast(0x98); + o << static_cast(0x98); write_number(static_cast(N)); } - else if (N <= 0xffff) + else if (N <= (std::numeric_limits::max)()) { - o << static_cast(0x99); + o << static_cast(0x99); write_number(static_cast(N)); } - else if (N <= 0xffffffff) + else if (N <= (std::numeric_limits::max)()) { - o << static_cast(0x9a); + o << static_cast(0x9A); write_number(static_cast(N)); } // LCOV_EXCL_START - else if (N <= 0xffffffffffffffff) + else if (N <= (std::numeric_limits::max)()) { - o << static_cast(0x9b); + o << static_cast(0x9B); write_number(static_cast(N)); } // LCOV_EXCL_STOP @@ -304,27 +340,27 @@ void json::binary_writer::write_cbor(const json& j) const auto N = j.m_value.object->size(); if (N <= 0x17) { - write_number(static_cast(0xa0 + N)); + write_number(static_cast(0xA0 + N)); } - else if (N <= 0xff) + else if (N <= (std::numeric_limits::max)()) { - o << static_cast(0xb8); + o << static_cast(0xB8); write_number(static_cast(N)); } - else if (N <= 0xffff) + else if (N <= (std::numeric_limits::max)()) { - o << static_cast(0xb9); + o << static_cast(0xB9); write_number(static_cast(N)); } - else if (N <= 0xffffffff) + else if (N <= (std::numeric_limits::max)()) { - o << static_cast(0xba); + o << static_cast(0xBA); write_number(static_cast(N)); } // LCOV_EXCL_START - else if (N <= 0xffffffffffffffff) + else /*if (N <= (std::numeric_limits::max)())*/ { - o << static_cast(0xbb); + o << static_cast(0xBB); write_number(static_cast(N)); } // LCOV_EXCL_STOP @@ -339,62 +375,23 @@ void json::binary_writer::write_cbor(const json& j) } default: - { break; - } } } -void json::binary_writer::write_cbor_string(StringRef str) -{ - // step 1: write control byte and the string length - const auto N = str.size(); - if (N <= 0x17) - { - write_number(static_cast(0x60 + N)); - } - else if (N <= 0xff) - { - o << static_cast(0x78); - write_number(static_cast(N)); - } - else if (N <= 0xffff) - { - o << static_cast(0x79); - write_number(static_cast(N)); - } - else if (N <= 0xffffffff) - { - o << static_cast(0x7a); - write_number(static_cast(N)); - } - // LCOV_EXCL_START - else if (N <= 0xffffffffffffffff) - { - o << static_cast(0x7b); - write_number(static_cast(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: + case value_t::null: // nil { - // nil - o << static_cast(0xc0); + o << static_cast(0xC0); break; } - case value_t::boolean: + case value_t::boolean: // true and false { - // true and false - o << static_cast(j.m_value.boolean ? 0xc3 : 0xc2); + o << static_cast(j.m_value.boolean ? 0xC3 : 0xC2); break; } @@ -403,9 +400,8 @@ void json::binary_writer::write_msgpack(const json& j) 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. + // 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 @@ -414,25 +410,25 @@ void json::binary_writer::write_msgpack(const json& j) else if (j.m_value.number_unsigned <= (std::numeric_limits::max)()) { // uint 8 - o << static_cast(0xcc); + o << static_cast(0xCC); write_number(static_cast(j.m_value.number_integer)); } else if (j.m_value.number_unsigned <= (std::numeric_limits::max)()) { // uint 16 - o << static_cast(0xcd); + o << static_cast(0xCD); write_number(static_cast(j.m_value.number_integer)); } else if (j.m_value.number_unsigned <= (std::numeric_limits::max)()) { // uint 32 - o << static_cast(0xce); + o << static_cast(0xCE); write_number(static_cast(j.m_value.number_integer)); } else if (j.m_value.number_unsigned <= (std::numeric_limits::max)()) { // uint 64 - o << static_cast(0xcf); + o << static_cast(0xCF); write_number(static_cast(j.m_value.number_integer)); } } @@ -443,28 +439,32 @@ void json::binary_writer::write_msgpack(const json& j) // negative fixnum write_number(static_cast(j.m_value.number_integer)); } - else if (j.m_value.number_integer >= (std::numeric_limits::min)() && j.m_value.number_integer <= (std::numeric_limits::max)()) + else if (j.m_value.number_integer >= (std::numeric_limits::min)() and + j.m_value.number_integer <= (std::numeric_limits::max)()) { // int 8 - o << static_cast(0xd0); + o << static_cast(0xD0); write_number(static_cast(j.m_value.number_integer)); } - else if (j.m_value.number_integer >= (std::numeric_limits::min)() && j.m_value.number_integer <= (std::numeric_limits::max)()) + else if (j.m_value.number_integer >= (std::numeric_limits::min)() and + j.m_value.number_integer <= (std::numeric_limits::max)()) { // int 16 - o << static_cast(0xd1); + o << static_cast(0xD1); write_number(static_cast(j.m_value.number_integer)); } - else if (j.m_value.number_integer >= (std::numeric_limits::min)() && j.m_value.number_integer <= (std::numeric_limits::max)()) + else if (j.m_value.number_integer >= (std::numeric_limits::min)() and + j.m_value.number_integer <= (std::numeric_limits::max)()) { // int 32 - o << static_cast(0xd2); + o << static_cast(0xD2); write_number(static_cast(j.m_value.number_integer)); } - else if (j.m_value.number_integer >= (std::numeric_limits::min)() && j.m_value.number_integer <= (std::numeric_limits::max)()) + else if (j.m_value.number_integer >= (std::numeric_limits::min)() and + j.m_value.number_integer <= (std::numeric_limits::max)()) { // int 64 - o << static_cast(0xd3); + o << static_cast(0xD3); write_number(static_cast(j.m_value.number_integer)); } } @@ -481,25 +481,25 @@ void json::binary_writer::write_msgpack(const json& j) else if (j.m_value.number_unsigned <= (std::numeric_limits::max)()) { // uint 8 - o << static_cast(0xcc); + o << static_cast(0xCC); write_number(static_cast(j.m_value.number_integer)); } else if (j.m_value.number_unsigned <= (std::numeric_limits::max)()) { // uint 16 - o << static_cast(0xcd); + o << static_cast(0xCD); write_number(static_cast(j.m_value.number_integer)); } else if (j.m_value.number_unsigned <= (std::numeric_limits::max)()) { // uint 32 - o << static_cast(0xce); + o << static_cast(0xCE); write_number(static_cast(j.m_value.number_integer)); } else if (j.m_value.number_unsigned <= (std::numeric_limits::max)()) { // uint 64 - o << static_cast(0xcf); + o << static_cast(0xCF); write_number(static_cast(j.m_value.number_integer)); } break; @@ -507,8 +507,7 @@ void json::binary_writer::write_msgpack(const json& j) case value_t::number_float: { - // float 64 - o << static_cast(0xcb); + o << get_msgpack_float_prefix(j.m_value.number_float); write_number(j.m_value.number_float); break; } @@ -528,16 +527,16 @@ void json::binary_writer::write_msgpack(const json& j) // fixarray write_number(static_cast(0x90 | N)); } - else if (N <= 0xffff) + else if (N <= (std::numeric_limits::max)()) { // array 16 - o << static_cast(0xdc); + o << static_cast(0xDC); write_number(static_cast(N)); } - else if (N <= 0xffffffff) + else if (N <= (std::numeric_limits::max)()) { // array 32 - o << static_cast(0xdd); + o << static_cast(0xDD); write_number(static_cast(N)); } @@ -556,18 +555,18 @@ void json::binary_writer::write_msgpack(const json& j) if (N <= 15) { // fixmap - write_number(static_cast(0x80 | (N & 0xf))); + write_number(static_cast(0x80 | (N & 0xF))); } - else if (N <= 65535) + else if (N <= (std::numeric_limits::max)()) { // map 16 - o << static_cast(0xde); + o << static_cast(0xDE); write_number(static_cast(N)); } - else if (N <= 4294967295) + else if (N <= (std::numeric_limits::max)()) { // map 32 - o << static_cast(0xdf); + o << static_cast(0xDF); write_number(static_cast(N)); } @@ -581,12 +580,192 @@ void json::binary_writer::write_msgpack(const json& j) } default: + break; + } +} + +void json::binary_writer::write_ubjson(const json& j, const bool use_count, + const bool use_type, const bool add_prefix) +{ + switch (j.type()) + { + case value_t::null: { + if (add_prefix) + { + o << static_cast('Z'); + } break; } + + case value_t::boolean: + { + if (add_prefix) + o << static_cast(j.m_value.boolean ? 'T' : 'F'); + break; + } + + case value_t::number_integer: + { + write_number_with_ubjson_prefix(j.m_value.number_integer, add_prefix); + break; + } + + case value_t::number_unsigned: + { + write_number_with_ubjson_prefix(j.m_value.number_unsigned, add_prefix); + break; + } + + case value_t::number_float: + { + write_number_with_ubjson_prefix(j.m_value.number_float, add_prefix); + break; + } + + case value_t::string: + { + if (add_prefix) + { + o << static_cast('S'); + } + write_number_with_ubjson_prefix(j.m_value.string->size(), true); + o << j.m_value.string; + break; + } + + case value_t::array: + { + if (add_prefix) + { + o << static_cast('['); + } + + bool prefix_required = true; + if (use_type and not j.m_value.array->empty()) + { + assert(use_count); + const CharType first_prefix = ubjson_prefix(j.front()); + const bool same_prefix = std::all_of(j.begin() + 1, j.end(), + [this, first_prefix](const json & v) + { + return ubjson_prefix(v) == first_prefix; + }); + + if (same_prefix) + { + prefix_required = false; + o << static_cast('$'); + o << first_prefix; + } + } + + if (use_count) + { + o << static_cast('#'); + write_number_with_ubjson_prefix(j.m_value.array->size(), true); + } + + for (const auto& el : *j.m_value.array) + { + write_ubjson(el, use_count, use_type, prefix_required); + } + + if (not use_count) + { + o << static_cast(']'); + } + + break; + } + + case value_t::object: + { + if (add_prefix) + { + o << static_cast('{'); + } + + bool prefix_required = true; + if (use_type and not j.m_value.object->empty()) + { + assert(use_count); + const CharType first_prefix = ubjson_prefix(j.front()); + const bool same_prefix = std::all_of(j.begin(), j.end(), + [this, first_prefix](const json & v) + { + return ubjson_prefix(v) == first_prefix; + }); + + if (same_prefix) + { + prefix_required = false; + o << static_cast('$'); + o << first_prefix; + } + } + + if (use_count) + { + o << static_cast('#'); + write_number_with_ubjson_prefix(j.m_value.object->size(), true); + } + + for (const auto& el : *j.m_value.object) + { + write_number_with_ubjson_prefix(el.first().size(), true); + o << el.first(); + write_ubjson(el.second, use_count, use_type, prefix_required); + } + + if (not use_count) + { + o << static_cast('}'); + } + + break; + } + + default: + break; } } +void json::binary_writer::write_cbor_string(StringRef str) +{ + // step 1: write control byte and the string length + const auto N = str.size(); + if (N <= 0x17) + { + write_number(static_cast(0x60 + N)); + } + else if (N <= (std::numeric_limits::max)()) + { + o << static_cast(0x78); + write_number(static_cast(N)); + } + else if (N <= (std::numeric_limits::max)()) + { + o << static_cast(0x79); + write_number(static_cast(N)); + } + else if (N <= (std::numeric_limits::max)()) + { + o << static_cast(0x7A); + write_number(static_cast(N)); + } + // LCOV_EXCL_START + else if (N <= (std::numeric_limits::max)()) + { + o << static_cast(0x7B); + write_number(static_cast(N)); + } + // LCOV_EXCL_STOP + + // step 2: write the string + o << str; +} + void json::binary_writer::write_msgpack_string(StringRef str) { // step 1: write control byte and the string length @@ -594,24 +773,24 @@ void json::binary_writer::write_msgpack_string(StringRef str) if (N <= 31) { // fixstr - write_number(static_cast(0xa0 | N)); + write_number(static_cast(0xA0 | N)); } - else if (N <= 255) + else if (N <= (std::numeric_limits::max)()) { // str 8 - o << static_cast(0xd9); + o << static_cast(0xD9); write_number(static_cast(N)); } - else if (N <= 65535) + else if (N <= (std::numeric_limits::max)()) { // str 16 - o << static_cast(0xda); + o << static_cast(0xDA); write_number(static_cast(N)); } - else if (N <= 4294967295) + else if (N <= (std::numeric_limits::max)()) { // str 32 - o << static_cast(0xdb); + o << static_cast(0xDB); write_number(static_cast(N)); } @@ -619,50 +798,293 @@ void json::binary_writer::write_msgpack_string(StringRef str) o << str; } +template +void json::binary_writer::write_number(const NumberType n) +{ + // step 1: write number to array of length NumberType + std::array vec; + std::memcpy(vec.data(), &n, sizeof(NumberType)); + + // step 2: write array to output (with possible reordering) + if (is_little_endian) + { + // reverse byte order prior to conversion if necessary + std::reverse(vec.begin(), vec.end()); + } + + o << ArrayRef(vec.data(), sizeof(NumberType)); +} + +template::value, int>::type> +void json::binary_writer::write_number_with_ubjson_prefix(const NumberType n, + const bool add_prefix) +{ + if (n <= static_cast((std::numeric_limits::max)())) + { + if (add_prefix) + { + o << static_cast('i'); // int8 + } + write_number(static_cast(n)); + } + else if (n <= (std::numeric_limits::max)()) + { + if (add_prefix) + { + o << static_cast('U'); // uint8 + } + write_number(static_cast(n)); + } + else if (n <= static_cast((std::numeric_limits::max)())) + { + if (add_prefix) + { + o << static_cast('I'); // int16 + } + write_number(static_cast(n)); + } + else if (n <= static_cast((std::numeric_limits::max)())) + { + if (add_prefix) + { + o << static_cast('l'); // int32 + } + write_number(static_cast(n)); + } + else if (n <= static_cast((std::numeric_limits::max)())) + { + if (add_prefix) + { + o << static_cast('L'); // int64 + } + write_number(static_cast(n)); + } + else + { + JSON_THROW(out_of_range::create(407, "number overflow serializing " + Twine(n))); + } +} + +template::value and + not std::is_floating_point::value, int>::type> +void json::binary_writer::write_number_with_ubjson_prefix(const NumberType n, + const bool add_prefix) +{ + if ((std::numeric_limits::min)() <= n and n <= (std::numeric_limits::max)()) + { + if (add_prefix) + { + o << static_cast('i'); // int8 + } + write_number(static_cast(n)); + } + else if (static_cast((std::numeric_limits::min)()) <= n and n <= static_cast((std::numeric_limits::max)())) + { + if (add_prefix) + { + o << static_cast('U'); // uint8 + } + write_number(static_cast(n)); + } + else if ((std::numeric_limits::min)() <= n and n <= (std::numeric_limits::max)()) + { + if (add_prefix) + { + o << static_cast('I'); // int16 + } + write_number(static_cast(n)); + } + else if ((std::numeric_limits::min)() <= n and n <= (std::numeric_limits::max)()) + { + if (add_prefix) + { + o << static_cast('l'); // int32 + } + write_number(static_cast(n)); + } + else if ((std::numeric_limits::min)() <= n and n <= (std::numeric_limits::max)()) + { + if (add_prefix) + { + o << static_cast('L'); // int64 + } + write_number(static_cast(n)); + } + // LCOV_EXCL_START + else + { + JSON_THROW(out_of_range::create(407, "number overflow serializing " + Twine(n))); + } + // LCOV_EXCL_STOP +} + +json::binary_writer::CharType json::binary_writer::ubjson_prefix(const json& j) const noexcept +{ + switch (j.type()) + { + case value_t::null: + return 'Z'; + + case value_t::boolean: + return j.m_value.boolean ? 'T' : 'F'; + + case value_t::number_integer: + { + if ((std::numeric_limits::min)() <= j.m_value.number_integer and j.m_value.number_integer <= (std::numeric_limits::max)()) + { + return 'i'; + } + else if ((std::numeric_limits::min)() <= j.m_value.number_integer and j.m_value.number_integer <= (std::numeric_limits::max)()) + { + return 'U'; + } + else if ((std::numeric_limits::min)() <= j.m_value.number_integer and j.m_value.number_integer <= (std::numeric_limits::max)()) + { + return 'I'; + } + else if ((std::numeric_limits::min)() <= j.m_value.number_integer and j.m_value.number_integer <= (std::numeric_limits::max)()) + { + return 'l'; + } + else // no check and assume int64_t (see note above) + { + return 'L'; + } + } + + case value_t::number_unsigned: + { + if (j.m_value.number_unsigned <= (std::numeric_limits::max)()) + { + return 'i'; + } + else if (j.m_value.number_unsigned <= (std::numeric_limits::max)()) + { + return 'U'; + } + else if (j.m_value.number_unsigned <= (std::numeric_limits::max)()) + { + return 'I'; + } + else if (j.m_value.number_unsigned <= (std::numeric_limits::max)()) + { + return 'l'; + } + else // no check and assume int64_t (see note above) + { + return 'L'; + } + } + + case value_t::number_float: + return get_ubjson_float_prefix(j.m_value.number_float); + + case value_t::string: + return 'S'; + + case value_t::array: + return '['; + + case value_t::object: + return '{'; + + default: // discarded values + return 'N'; + } +} + +std::vector json::to_cbor(const json& j) +{ + std::vector result; + raw_uvector_ostream os(result); + to_cbor(os, j); + return result; +} + +ArrayRef json::to_cbor(const json& j, std::vector& buf) +{ + buf.clear(); + raw_uvector_ostream os(buf); + to_cbor(os, j); + return os.array(); +} + +ArrayRef json::to_cbor(const json& j, SmallVectorImpl& buf) +{ + buf.clear(); + raw_usvector_ostream os(buf); + to_cbor(os, j); + return os.array(); +} + void json::to_cbor(raw_ostream& os, const json& j) { - binary_writer bw(os); - bw.write_cbor(j); + binary_writer(os).write_cbor(j); } -StringRef json::to_cbor(const json& j, SmallVectorImpl buf) +std::vector json::to_msgpack(const json& j) { - raw_svector_ostream os(buf); - binary_writer bw(os); - bw.write_cbor(j); - return os.str(); + std::vector result; + raw_uvector_ostream os(result); + to_msgpack(os, j); + return result; } -std::string json::to_cbor(const json& j) +ArrayRef json::to_msgpack(const json& j, std::vector& buf) { - std::string s; - raw_string_ostream os(s); - binary_writer bw(os); - bw.write_cbor(j); - os.flush(); - return s; + buf.clear(); + raw_uvector_ostream os(buf); + to_msgpack(os, j); + return os.array(); +} + +ArrayRef json::to_msgpack(const json& j, SmallVectorImpl& buf) +{ + buf.clear(); + raw_usvector_ostream os(buf); + to_msgpack(os, j); + return os.array(); } void json::to_msgpack(raw_ostream& os, const json& j) { - binary_writer bw(os); - bw.write_msgpack(j); + binary_writer(os).write_msgpack(j); } -StringRef json::to_msgpack(const json& j, SmallVectorImpl buf) +std::vector json::to_ubjson(const json& j, + const bool use_size, + const bool use_type) { - raw_svector_ostream os(buf); - binary_writer bw(os); - bw.write_msgpack(j); - return os.str(); + std::vector result; + raw_uvector_ostream os(result); + to_ubjson(os, j, use_size, use_type); + return result; } -std::string json::to_msgpack(const json& j) +ArrayRef json::to_ubjson(const json& j, std::vector& buf, + const bool use_size, const bool use_type) { - std::string s; - raw_string_ostream os(s); - binary_writer bw(os); - bw.write_msgpack(j); - os.flush(); - return s; + buf.clear(); + raw_uvector_ostream os(buf); + to_ubjson(os, j, use_size, use_type); + return os.array(); } + +ArrayRef json::to_ubjson(const json& j, SmallVectorImpl& buf, + const bool use_size, const bool use_type) +{ + buf.clear(); + raw_usvector_ostream os(buf); + to_ubjson(os, j, use_size, use_type); + return os.array(); +} + +void json::to_ubjson(raw_ostream& os, const json& j, + const bool use_size, const bool use_type) +{ + binary_writer(os).write_ubjson(j, use_size, use_type); +} + +} // namespace wpi diff --git a/wpiutil/src/main/native/cpp/json_parser.cpp b/wpiutil/src/main/native/cpp/json_parser.cpp index 4327b406ca..7c8b622fdd 100644 --- a/wpiutil/src/main/native/cpp/json_parser.cpp +++ b/wpiutil/src/main/native/cpp/json_parser.cpp @@ -1,5 +1,5 @@ /*----------------------------------------------------------------------------*/ -/* Modifications Copyright (c) FIRST 2017. All Rights Reserved. */ +/* Modifications Copyright (c) 2017-2018 FIRST. All Rights Reserved. */ /* Open Source Software - may be modified and shared by FRC teams. The code */ /* must be accompanied by the FIRST BSD license file in the root directory of */ /* the project. */ @@ -7,11 +7,11 @@ /* __ _____ _____ _____ __| | __| | | | JSON for Modern C++ -| | |__ | | | | | | version 2.1.1 +| | |__ | | | | | | version 3.1.2 |_____|_____|_____|_|___| https://github.com/nlohmann/json Licensed under the MIT License . -Copyright (c) 2013-2017 Niels Lohmann . +Copyright (c) 2013-2018 Niels Lohmann . Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal @@ -34,58 +34,54 @@ SOFTWARE. #define WPI_JSON_IMPLEMENTATION #include "wpi/json.h" -#include -#include // lconv, localeconv -#include // locale +#include +#include +#include #include "wpi/Format.h" -#include "wpi/SmallString.h" #include "wpi/raw_istream.h" #include "wpi/raw_ostream.h" -using namespace wpi; - -namespace { - -////////////////////// -// lexer and parser // -////////////////////// +namespace wpi { /*! @brief lexical analysis This class organizes the lexical analysis during JSON deserialization. */ -class lexer +class json::lexer { public: /// token types for the parser enum class token_type { - uninitialized, ///< indicating the scanner is uninitialized - literal_true, ///< the `true` literal - literal_false, ///< the `false` literal - literal_null, ///< the `null` literal - value_string, ///< a string -- use get_string() for actual value - value_unsigned, ///< an unsigned integer -- use get_number_unsigned() for actual value - value_integer, ///< a signed integer -- use get_number_integer() for actual value - value_float, ///< an floating point number -- use get_number_float() for actual value - begin_array, ///< the character for array begin `[` - begin_object, ///< the character for object begin `{` - end_array, ///< the character for array end `]` - end_object, ///< the character for object end `}` - name_separator, ///< the name separator `:` - value_separator, ///< the value separator `,` - parse_error, ///< indicating a parse error - end_of_input ///< indicating the end of the input buffer + uninitialized, ///< indicating the scanner is uninitialized + literal_true, ///< the `true` literal + literal_false, ///< the `false` literal + literal_null, ///< the `null` literal + value_string, ///< a string -- use get_string() for actual value + value_unsigned, ///< an unsigned integer -- use get_number_unsigned() for actual value + value_integer, ///< a signed integer -- use get_number_integer() for actual value + value_float, ///< an floating point number -- use get_number_float() for actual value + begin_array, ///< the character for array begin `[` + begin_object, ///< the character for object begin `{` + end_array, ///< the character for array end `]` + end_object, ///< the character for object end `}` + name_separator, ///< the name separator `:` + value_separator, ///< the value separator `,` + parse_error, ///< indicating a parse error + end_of_input, ///< indicating the end of the input buffer + literal_or_value ///< a literal or the begin of a value (only for diagnostics) }; /// return name of values of type token_type (only used for errors) static const char* token_type_name(const token_type t) noexcept; - explicit lexer(wpi::raw_istream& s) - : is(s), decimal_point_char(get_decimal_point()) - {} + explicit lexer(raw_istream& s); + + // delete because of pointer members + lexer(const lexer&) = delete; + lexer& operator=(lexer&) = delete; private: ///////////////////// @@ -97,7 +93,7 @@ class lexer { const auto loc = localeconv(); assert(loc != nullptr); - return (loc->decimal_point == nullptr) ? '.' : loc->decimal_point[0]; + return (loc->decimal_point == nullptr) ? '.' : *(loc->decimal_point); } ///////////////////// @@ -107,26 +103,68 @@ class lexer /*! @brief get codepoint from 4 hex characters following `\u` - @return codepoint or -1 in case of an error (e.g. EOF or non-hex - character) + For input "\u c1 c2 c3 c4" the codepoint is: + (c1 * 0x1000) + (c2 * 0x0100) + (c3 * 0x0010) + c4 + = (c1 << 12) + (c2 << 8) + (c3 << 4) + (c4 << 0) + + Furthermore, the possible characters '0'..'9', 'A'..'F', and 'a'..'f' + must be converted to the integers 0x0..0x9, 0xA..0xF, 0xA..0xF, resp. The + conversion is done by subtracting the offset (0x30, 0x37, and 0x57) + between the ASCII value of the character and the desired integer value. + + @return codepoint (0x0000..0xFFFF) or -1 in case of an error (e.g. EOF or + non-hex character) */ int get_codepoint(); /*! - @brief create diagnostic representation of a codepoint - @return string "U+XXXX" for codepoint XXXX + @brief check if the next byte(s) are inside a given range + + Adds the current byte and, for each passed range, reads a new byte and + checks if it is inside the range. If a violation was detected, set up an + error message and return false. Otherwise, return true. + + @param[in] ranges list of integers; interpreted as list of pairs of + inclusive lower and upper bound, respectively + + @pre The passed list @a ranges must have 2, 4, or 6 elements; that is, + 1, 2, or 3 pairs. This precondition is enforced by an assertion. + + @return true if and only if no range violation was detected */ - static std::string codepoint_to_string(int codepoint); + bool next_byte_in_range(std::initializer_list ranges) + { + assert(ranges.size() == 2 or ranges.size() == 4 or ranges.size() == 6); + add(current); + + for (auto range = ranges.begin(); range != ranges.end(); ++range) + { + get(); + if (JSON_LIKELY(*range <= current and current <= *(++range))) + { + add(current); + } + else + { + error_message = "invalid string: ill-formed UTF-8 byte"; + return false; + } + } + + return true; + } /*! @brief scan a string literal This function scans a string according to Sect. 7 of RFC 7159. While - scanning, bytes are escaped and copied into buffer yytext. Then the - function returns successfully. + scanning, bytes are escaped and copied into buffer token_buffer. Then the + function returns successfully, token_buffer is *not* null-terminated (as it + may contain \0 bytes), and token_buffer.size() is the number of bytes in the + string. - @return token_type::value_string if string could be successfully - scanned, token_type::parse_error otherwise + @return token_type::value_string if string could be successfully scanned, + token_type::parse_error otherwise @note In case of errors, variable error_message contains a textual description. @@ -153,12 +191,11 @@ class lexer This function scans a string according to Sect. 6 of RFC 7159. - The function is realized with a deterministic finite state machine - derived from the grammar described in RFC 7159. Starting in state - "init", the input is read and used to determined the next state. Only - state "done" accepts the number. State "error" is a trap state to model - errors. In the table below, "anything" means any character but the ones - listed before. + The function is realized with a deterministic finite state machine derived + from the grammar described in RFC 7159. Starting in state "init", the + input is read and used to determined the next state. Only state "done" + accepts the number. State "error" is a trap state to model errors. In the + table below, "anything" means any character but the ones listed before. state | 0 | 1-9 | e E | + | - | . | anything ---------|----------|----------|----------|---------|---------|----------|----------- @@ -177,7 +214,7 @@ class lexer contains cycles, but any cycle can be left when EOF is read. Therefore, the function is guaranteed to terminate. - During scanning, the read bytes are stored in yytext. This string is + During scanning, the read bytes are stored in token_buffer. This string is then converted to a signed integer, an unsigned integer, or a floating-point number. @@ -191,48 +228,87 @@ class lexer */ token_type scan_number(); - token_type scan_true(); - token_type scan_false(); - token_type scan_null(); + /*! + @param[in] literal_text the literal text to expect + @param[in] length the length of the passed literal text + @param[in] return_type the token type to return on success + */ + token_type scan_literal(const char* literal_text, const std::size_t length, + token_type return_type); ///////////////////// // input management ///////////////////// - /// reset yytext + /// reset token_buffer; current character is beginning of token void reset() noexcept { - token_string.resize(0); - yytext.resize(0); + token_buffer.clear(); + token_string.clear(); + token_string.push_back(std::char_traits::to_char_type(current)); } - /// get a character from the input - int get() + /* + @brief get next character from the input + + This function provides the interface to the used input adapter. It does + not throw in case the input reached EOF, but returns a + `std::char_traits::eof()` in that case. Stores the scanned characters + for use in error messages. + + @return character read from the input + */ + std::char_traits::int_type get() { ++chars_read; - if (next_unget) + if (JSON_UNLIKELY(!unget_chars.empty())) { - next_unget = false; + current = unget_chars.back(); + unget_chars.pop_back(); + token_string.push_back(current); return current; } char c; is.read(c); - if (is.has_error()) + if (JSON_UNLIKELY(is.has_error())) { current = std::char_traits::eof(); } else { - current = static_cast(c); + current = std::char_traits::to_int_type(c); token_string.push_back(c); } return current; } - /// add a character to yytext + /// unget current character (return it again on next get) + void unget() + { + --chars_read; + if (JSON_LIKELY(current != std::char_traits::eof())) + { + unget_chars.emplace_back(current); + assert(token_string.size() != 0); + token_string.pop_back(); + if (!token_string.empty()) + { + current = token_string.back(); + } + } + } + + /// put back character (returned on next get) + void putback(std::char_traits::int_type c) + { + --chars_read; + unget_chars.emplace_back(c); + } + + /// add a character to token_buffer void add(int c) { - yytext.push_back(static_cast(c)); + token_buffer.push_back(std::char_traits::to_char_type(c)); } public: @@ -241,13 +317,13 @@ class lexer ///////////////////// /// return integer value - std::int64_t get_number_integer() const noexcept + int64_t get_number_integer() const noexcept { return value_integer; } /// return unsigned integer value - std::uint64_t get_number_unsigned() const noexcept + uint64_t get_number_unsigned() const noexcept { return value_unsigned; } @@ -258,10 +334,10 @@ class lexer return value_float; } - /// return string value + /// return current string value StringRef get_string() { - return yytext.str(); + return token_buffer; } ///////////////////// @@ -269,16 +345,18 @@ class lexer ///////////////////// /// return position of last read token - size_t get_position() const noexcept + std::size_t get_position() const noexcept { return chars_read; } - /// return the last read token (for errors only) + /// return the last read token (for errors only). Will never contain EOF + /// (an arbitrary value that is not a valid char value, often -1), because + /// 255 may legitimately occur. May contain NUL, which should be escaped. std::string get_token_string() const; /// return syntax error message - const std::string& get_error_message() const noexcept + const char* get_error_message() const noexcept { return error_message; } @@ -291,38 +369,158 @@ class lexer private: /// input adapter - wpi::raw_istream& is; + raw_istream& is; /// the current character - int current = std::char_traits::eof(); + std::char_traits::int_type current = std::char_traits::eof(); - /// whether get() should return the last character again - bool next_unget = false; + /// unget characters + SmallVector::int_type, 4> unget_chars; /// the number of characters read - size_t chars_read = 0; + std::size_t chars_read = 0; - /// buffer for raw byte sequence of the current token - SmallString<128> token_string; + /// raw input token string (for error messages) + SmallString<128> token_string {}; /// buffer for variable-length tokens (numbers, strings) - SmallString<128> yytext; + SmallString<128> token_buffer {}; /// a description of occurred lexer errors - std::string error_message = ""; + const char* error_message = ""; // number values - std::int64_t value_integer = 0; - std::uint64_t value_unsigned = 0; + int64_t value_integer = 0; + uint64_t value_unsigned = 0; double value_float = 0; /// the decimal point const char decimal_point_char = '.'; }; -} // anonymous namespace +//////////// +// parser // +//////////// -const char* lexer::token_type_name(const token_type t) noexcept +/*! +@brief syntax analysis + +This class implements a recursive decent parser. +*/ +class json::parser +{ + using lexer_t = json::lexer; + using token_type = typename lexer_t::token_type; + + public: + /// a parser reading from an input adapter + explicit parser(raw_istream& s, + const parser_callback_t cb = nullptr, + const bool allow_exceptions_ = true) + : callback(cb), m_lexer(s), allow_exceptions(allow_exceptions_) + {} + + /*! + @brief public parser interface + + @param[in] strict whether to expect the last token to be EOF + @param[in,out] result parsed JSON value + + @throw parse_error.101 in case of an unexpected token + @throw parse_error.102 if to_unicode fails or surrogate error + @throw parse_error.103 if to_unicode fails + */ + void parse(const bool strict, json& result); + + /*! + @brief public accept interface + + @param[in] strict whether to expect the last token to be EOF + @return whether the input is a proper JSON text + */ + bool accept(const bool strict = true) + { + // read first token + get_token(); + + if (not accept_internal()) + { + return false; + } + + // strict => last token must be EOF + return not strict or (get_token() == token_type::end_of_input); + } + + private: + /*! + @brief the actual parser + @throw parse_error.101 in case of an unexpected token + @throw parse_error.102 if to_unicode fails or surrogate error + @throw parse_error.103 if to_unicode fails + */ + void parse_internal(bool keep, json& result); + + /*! + @brief the actual acceptor + + @invariant 1. The last token is not yet processed. Therefore, the caller + of this function must make sure a token has been read. + 2. When this function returns, the last token is processed. + That is, the last read character was already considered. + + This invariant makes sure that no token needs to be "unput". + */ + bool accept_internal(); + + /// get next token from lexer + token_type get_token() + { + return (last_token = m_lexer.scan()); + } + + /*! + @throw parse_error.101 if expected token did not occur + */ + bool expect(token_type t) + { + if (JSON_UNLIKELY(t != last_token)) + { + errored = true; + expected = t; + if (allow_exceptions) + { + throw_exception(); + } + else + { + return false; + } + } + + return true; + } + + [[noreturn]] void throw_exception() const; + + private: + /// current level of recursion + int depth = 0; + /// callback function + const parser_callback_t callback = nullptr; + /// the type of the last read token + token_type last_token = token_type::uninitialized; + /// the lexer + lexer_t m_lexer; + /// whether a syntax error occurred + bool errored = false; + /// possible reason for the syntax error + token_type expected = token_type::uninitialized; + /// whether to throw exceptions in case of errors + const bool allow_exceptions = true; +}; + +const char* json::lexer::token_type_name(const token_type t) noexcept { switch (t) { @@ -356,274 +554,78 @@ const char* lexer::token_type_name(const token_type t) noexcept return ""; case token_type::end_of_input: return "end of input"; - default: - { - // catch non-enum values + case token_type::literal_or_value: + return "'[', '{', or a literal"; + default: // catch non-enum values return "unknown token"; // LCOV_EXCL_LINE - } } } -int lexer::get_codepoint() +json::lexer::lexer(raw_istream& s) + : is(s), decimal_point_char(get_decimal_point()) +{ + // skip byte order mark + std::char_traits::int_type c; + if ((c = get()) == 0xEF) + { + if ((c = get()) == 0xBB) + { + if ((c = get()) == 0xBF) + { + chars_read = 0; + return; // Ignore BOM + } + else if (c != std::char_traits::eof()) + { + unget(); + } + putback('\xBB'); + } + else if (c != std::char_traits::eof()) + { + unget(); + } + putback('\xEF'); + } + unget(); // no byte order mark; process as usual +} + +int json::lexer::get_codepoint() { // this function only makes sense after reading `\u` assert(current == 'u'); int codepoint = 0; - // byte 1: \uXxxx - switch (get()) + const auto factors = { 12, 8, 4, 0 }; + for (const auto factor : factors) { - case '0': - break; - case '1': - codepoint += 0x1000; - break; - case '2': - codepoint += 0x2000; - break; - case '3': - codepoint += 0x3000; - break; - case '4': - codepoint += 0x4000; - break; - case '5': - codepoint += 0x5000; - break; - case '6': - codepoint += 0x6000; - break; - case '7': - codepoint += 0x7000; - break; - case '8': - codepoint += 0x8000; - break; - case '9': - codepoint += 0x9000; - break; - case 'A': - case 'a': - codepoint += 0xa000; - break; - case 'B': - case 'b': - codepoint += 0xb000; - break; - case 'C': - case 'c': - codepoint += 0xc000; - break; - case 'D': - case 'd': - codepoint += 0xd000; - break; - case 'E': - case 'e': - codepoint += 0xe000; - break; - case 'F': - case 'f': - codepoint += 0xf000; - break; - default: - return -1; - } - - // byte 2: \uxXxx - switch (get()) - { - case '0': - break; - case '1': - codepoint += 0x0100; - break; - case '2': - codepoint += 0x0200; - break; - case '3': - codepoint += 0x0300; - break; - case '4': - codepoint += 0x0400; - break; - case '5': - codepoint += 0x0500; - break; - case '6': - codepoint += 0x0600; - break; - case '7': - codepoint += 0x0700; - break; - case '8': - codepoint += 0x0800; - break; - case '9': - codepoint += 0x0900; - break; - case 'A': - case 'a': - codepoint += 0x0a00; - break; - case 'B': - case 'b': - codepoint += 0x0b00; - break; - case 'C': - case 'c': - codepoint += 0x0c00; - break; - case 'D': - case 'd': - codepoint += 0x0d00; - break; - case 'E': - case 'e': - codepoint += 0x0e00; - break; - case 'F': - case 'f': - codepoint += 0x0f00; - break; - default: - return -1; - } - - // byte 3: \uxxXx - switch (get()) - { - case '0': - break; - case '1': - codepoint += 0x0010; - break; - case '2': - codepoint += 0x0020; - break; - case '3': - codepoint += 0x0030; - break; - case '4': - codepoint += 0x0040; - break; - case '5': - codepoint += 0x0050; - break; - case '6': - codepoint += 0x0060; - break; - case '7': - codepoint += 0x0070; - break; - case '8': - codepoint += 0x0080; - break; - case '9': - codepoint += 0x0090; - break; - case 'A': - case 'a': - codepoint += 0x00a0; - break; - case 'B': - case 'b': - codepoint += 0x00b0; - break; - case 'C': - case 'c': - codepoint += 0x00c0; - break; - case 'D': - case 'd': - codepoint += 0x00d0; - break; - case 'E': - case 'e': - codepoint += 0x00e0; - break; - case 'F': - case 'f': - codepoint += 0x00f0; - break; - default: - return -1; - } - - // byte 4: \uxxxX - switch (get()) - { - case '0': - break; - case '1': - codepoint += 0x0001; - break; - case '2': - codepoint += 0x0002; - break; - case '3': - codepoint += 0x0003; - break; - case '4': - codepoint += 0x0004; - break; - case '5': - codepoint += 0x0005; - break; - case '6': - codepoint += 0x0006; - break; - case '7': - codepoint += 0x0007; - break; - case '8': - codepoint += 0x0008; - break; - case '9': - codepoint += 0x0009; - break; - case 'A': - case 'a': - codepoint += 0x000a; - break; - case 'B': - case 'b': - codepoint += 0x000b; - break; - case 'C': - case 'c': - codepoint += 0x000c; - break; - case 'D': - case 'd': - codepoint += 0x000d; - break; - case 'E': - case 'e': - codepoint += 0x000e; - break; - case 'F': - case 'f': - codepoint += 0x000f; - break; - default: + get(); + + if (current >= '0' and current <= '9') + { + codepoint += ((current - 0x30) << factor); + } + else if (current >= 'A' and current <= 'F') + { + codepoint += ((current - 0x37) << factor); + } + else if (current >= 'a' and current <= 'f') + { + codepoint += ((current - 0x57) << factor); + } + else + { return -1; + } } + assert(0x0000 <= codepoint and codepoint <= 0xFFFF); return codepoint; } -std::string lexer::codepoint_to_string(int codepoint) +json::lexer::token_type json::lexer::scan_string() { - std::string s; - raw_string_ostream ss(s); - ss << "U+" << format_hex_no_prefix(codepoint, 4, true); - return ss.str(); -} - -lexer::token_type lexer::scan_string() -{ - // reset yytext (ignore opening quote) + // reset token_buffer (ignore opening quote) reset(); // we entered the function by reading an open quote @@ -632,9 +634,7 @@ lexer::token_type lexer::scan_string() while (true) { // get next character - get(); - - switch (current) + switch (get()) { // end of file while parsing string case std::char_traits::eof(): @@ -646,7 +646,6 @@ lexer::token_type lexer::scan_string() // closing quote case '\"': { - // terminate yytext return token_type::value_string; } @@ -691,8 +690,8 @@ lexer::token_type lexer::scan_string() // unicode escapes case 'u': { - int codepoint; - int codepoint1 = get_codepoint(); + const int codepoint1 = get_codepoint(); + int codepoint = codepoint1; // start with codepoint1 if (JSON_UNLIKELY(codepoint1 == -1)) { @@ -701,10 +700,10 @@ lexer::token_type lexer::scan_string() } // check if code point is a high surrogate - if (0xD800 <= codepoint1 && codepoint1 <= 0xDBFF) + if (0xD800 <= codepoint1 and codepoint1 <= 0xDBFF) { // expect next \uxxxx entry - if (JSON_LIKELY(get() == '\\' && get() == 'u')) + if (JSON_LIKELY(get() == '\\' and get() == 'u')) { const int codepoint2 = get_codepoint(); @@ -715,8 +714,9 @@ lexer::token_type lexer::scan_string() } // check if codepoint2 is a low surrogate - if (JSON_LIKELY(0xDC00 <= codepoint2 && codepoint2 <= 0xDFFF)) + if (JSON_LIKELY(0xDC00 <= codepoint2 and codepoint2 <= 0xDFFF)) { + // overwrite codepoint codepoint = // high surrogate occupies the most significant 22 bits (codepoint1 << 10) @@ -729,44 +729,41 @@ lexer::token_type lexer::scan_string() } else { - error_message = "invalid string: surrogate " + codepoint_to_string(codepoint1) + " must be followed by U+DC00..U+DFFF instead of " + codepoint_to_string(codepoint2); + error_message = "invalid string: surrogate U+DC00..U+DFFF must be followed by U+DC00..U+DFFF"; return token_type::parse_error; } } else { - error_message = "invalid string: surrogate " + codepoint_to_string(codepoint1) + " must be followed by U+DC00..U+DFFF"; + error_message = "invalid string: surrogate U+DC00..U+DFFF must be followed by U+DC00..U+DFFF"; return token_type::parse_error; } } else { - if (JSON_UNLIKELY(0xDC00 <= codepoint1 && codepoint1 <= 0xDFFF)) + if (JSON_UNLIKELY(0xDC00 <= codepoint1 and codepoint1 <= 0xDFFF)) { - error_message = "invalid string: surrogate " + codepoint_to_string(codepoint1) + " must follow U+D800..U+DBFF"; + error_message = "invalid string: surrogate U+DC00..U+DFFF must follow U+D800..U+DBFF"; return token_type::parse_error; } - - // only work with first code point - codepoint = codepoint1; } // result of the above calculation yields a proper codepoint - assert(0x00 <= codepoint && codepoint <= 0x10FFFF); + assert(0x00 <= codepoint and codepoint <= 0x10FFFF); - // translate code point to bytes + // translate codepoint into bytes if (codepoint < 0x80) { // 1-byte characters: 0xxxxxxx (ASCII) add(codepoint); } - else if (codepoint <= 0x7ff) + else if (codepoint <= 0x7FF) { // 2-byte characters: 110xxxxx 10xxxxxx add(0xC0 | (codepoint >> 6)); add(0x80 | (codepoint & 0x3F)); } - else if (codepoint <= 0xffff) + else if (codepoint <= 0xFFFF) { // 3-byte characters: 1110xxxx 10xxxxxx 10xxxxxx add(0xE0 | (codepoint >> 12)); @@ -805,12 +802,12 @@ lexer::token_type lexer::scan_string() case 0x07: case 0x08: case 0x09: - case 0x0a: - case 0x0b: - case 0x0c: - case 0x0d: - case 0x0e: - case 0x0f: + case 0x0A: + case 0x0B: + case 0x0C: + case 0x0D: + case 0x0E: + case 0x0F: case 0x10: case 0x11: case 0x12: @@ -821,14 +818,14 @@ lexer::token_type lexer::scan_string() case 0x17: case 0x18: case 0x19: - case 0x1a: - case 0x1b: - case 0x1c: - case 0x1d: - case 0x1e: - case 0x1f: + case 0x1A: + case 0x1B: + case 0x1C: + case 0x1D: + case 0x1E: + case 0x1F: { - error_message = "invalid string: control character " + codepoint_to_string(current) + " must be escaped"; + error_message = "invalid string: control character must be escaped"; return token_type::parse_error; } @@ -842,12 +839,12 @@ lexer::token_type lexer::scan_string() case 0x27: case 0x28: case 0x29: - case 0x2a: - case 0x2b: - case 0x2c: - case 0x2d: - case 0x2e: - case 0x2f: + case 0x2A: + case 0x2B: + case 0x2C: + case 0x2D: + case 0x2E: + case 0x2F: case 0x30: case 0x31: case 0x32: @@ -858,12 +855,12 @@ lexer::token_type lexer::scan_string() case 0x37: case 0x38: case 0x39: - case 0x3a: - case 0x3b: - case 0x3c: - case 0x3d: - case 0x3e: - case 0x3f: + case 0x3A: + case 0x3B: + case 0x3C: + case 0x3D: + case 0x3E: + case 0x3F: case 0x40: case 0x41: case 0x42: @@ -874,12 +871,12 @@ lexer::token_type lexer::scan_string() case 0x47: case 0x48: case 0x49: - case 0x4a: - case 0x4b: - case 0x4c: - case 0x4d: - case 0x4e: - case 0x4f: + case 0x4A: + case 0x4B: + case 0x4C: + case 0x4D: + case 0x4E: + case 0x4F: case 0x50: case 0x51: case 0x52: @@ -890,11 +887,11 @@ lexer::token_type lexer::scan_string() case 0x57: case 0x58: case 0x59: - case 0x5a: - case 0x5b: - case 0x5d: - case 0x5e: - case 0x5f: + case 0x5A: + case 0x5B: + case 0x5D: + case 0x5E: + case 0x5F: case 0x60: case 0x61: case 0x62: @@ -905,12 +902,12 @@ lexer::token_type lexer::scan_string() case 0x67: case 0x68: case 0x69: - case 0x6a: - case 0x6b: - case 0x6c: - case 0x6d: - case 0x6e: - case 0x6f: + case 0x6A: + case 0x6B: + case 0x6C: + case 0x6D: + case 0x6E: + case 0x6F: case 0x70: case 0x71: case 0x72: @@ -921,210 +918,130 @@ lexer::token_type lexer::scan_string() case 0x77: case 0x78: case 0x79: - case 0x7a: - case 0x7b: - case 0x7c: - case 0x7d: - case 0x7e: - case 0x7f: + case 0x7A: + case 0x7B: + case 0x7C: + case 0x7D: + case 0x7E: + case 0x7F: { add(current); break; } // U+0080..U+07FF: bytes C2..DF 80..BF - case 0xc2: - case 0xc3: - case 0xc4: - case 0xc5: - case 0xc6: - case 0xc7: - case 0xc8: - case 0xc9: - case 0xca: - case 0xcb: - case 0xcc: - case 0xcd: - case 0xce: - case 0xcf: - case 0xd0: - case 0xd1: - case 0xd2: - case 0xd3: - case 0xd4: - case 0xd5: - case 0xd6: - case 0xd7: - case 0xd8: - case 0xd9: - case 0xda: - case 0xdb: - case 0xdc: - case 0xdd: - case 0xde: - case 0xdf: + case 0xC2: + case 0xC3: + case 0xC4: + case 0xC5: + case 0xC6: + case 0xC7: + case 0xC8: + case 0xC9: + case 0xCA: + case 0xCB: + case 0xCC: + case 0xCD: + case 0xCE: + case 0xCF: + case 0xD0: + case 0xD1: + case 0xD2: + case 0xD3: + case 0xD4: + case 0xD5: + case 0xD6: + case 0xD7: + case 0xD8: + case 0xD9: + case 0xDA: + case 0xDB: + case 0xDC: + case 0xDD: + case 0xDE: + case 0xDF: { - add(current); - get(); - if (JSON_LIKELY(0x80 <= current && current <= 0xbf)) + if (JSON_UNLIKELY(not next_byte_in_range({0x80, 0xBF}))) { - add(current); - continue; + return token_type::parse_error; } - - error_message = "invalid string: ill-formed UTF-8 byte"; - return token_type::parse_error; + break; } // U+0800..U+0FFF: bytes E0 A0..BF 80..BF - case 0xe0: + case 0xE0: { - add(current); - get(); - if (JSON_LIKELY(0xa0 <= current && current <= 0xbf)) + if (JSON_UNLIKELY(not (next_byte_in_range({0xA0, 0xBF, 0x80, 0xBF})))) { - add(current); - get(); - if (JSON_LIKELY(0x80 <= current && current <= 0xbf)) - { - add(current); - continue; - } + return token_type::parse_error; } - - error_message = "invalid string: ill-formed UTF-8 byte"; - return token_type::parse_error; + break; } // U+1000..U+CFFF: bytes E1..EC 80..BF 80..BF // U+E000..U+FFFF: bytes EE..EF 80..BF 80..BF - case 0xe1: - case 0xe2: - case 0xe3: - case 0xe4: - case 0xe5: - case 0xe6: - case 0xe7: - case 0xe8: - case 0xe9: - case 0xea: - case 0xeb: - case 0xec: - case 0xee: - case 0xef: + case 0xE1: + case 0xE2: + case 0xE3: + case 0xE4: + case 0xE5: + case 0xE6: + case 0xE7: + case 0xE8: + case 0xE9: + case 0xEA: + case 0xEB: + case 0xEC: + case 0xEE: + case 0xEF: { - add(current); - get(); - if (JSON_LIKELY(0x80 <= current && current <= 0xbf)) + if (JSON_UNLIKELY(not (next_byte_in_range({0x80, 0xBF, 0x80, 0xBF})))) { - add(current); - get(); - if (JSON_LIKELY(0x80 <= current && current <= 0xbf)) - { - add(current); - continue; - } + return token_type::parse_error; } - - error_message = "invalid string: ill-formed UTF-8 byte"; - return token_type::parse_error; + break; } // U+D000..U+D7FF: bytes ED 80..9F 80..BF - case 0xed: + case 0xED: { - add(current); - get(); - if (JSON_LIKELY(0x80 <= current && current <= 0x9f)) + if (JSON_UNLIKELY(not (next_byte_in_range({0x80, 0x9F, 0x80, 0xBF})))) { - add(current); - get(); - if (JSON_LIKELY(0x80 <= current && current <= 0xbf)) - { - add(current); - continue; - } + return token_type::parse_error; } - - error_message = "invalid string: ill-formed UTF-8 byte"; - return token_type::parse_error; + break; } // U+10000..U+3FFFF F0 90..BF 80..BF 80..BF - case 0xf0: + case 0xF0: { - add(current); - get(); - if (JSON_LIKELY(0x90 <= current && current <= 0xbf)) + if (JSON_UNLIKELY(not (next_byte_in_range({0x90, 0xBF, 0x80, 0xBF, 0x80, 0xBF})))) { - add(current); - get(); - if (JSON_LIKELY(0x80 <= current && current <= 0xbf)) - { - add(current); - get(); - if (JSON_LIKELY(0x80 <= current && current <= 0xbf)) - { - add(current); - continue; - } - } + return token_type::parse_error; } - - error_message = "invalid string: ill-formed UTF-8 byte"; - return token_type::parse_error; + break; } // U+40000..U+FFFFF F1..F3 80..BF 80..BF 80..BF - case 0xf1: - case 0xf2: - case 0xf3: + case 0xF1: + case 0xF2: + case 0xF3: { - add(current); - get(); - if (JSON_LIKELY(0x80 <= current && current <= 0xbf)) + if (JSON_UNLIKELY(not (next_byte_in_range({0x80, 0xBF, 0x80, 0xBF, 0x80, 0xBF})))) { - add(current); - get(); - if (JSON_LIKELY(0x80 <= current && current <= 0xbf)) - { - add(current); - get(); - if (JSON_LIKELY(0x80 <= current && current <= 0xbf)) - { - add(current); - continue; - } - } + return token_type::parse_error; } - - error_message = "invalid string: ill-formed UTF-8 byte"; - return token_type::parse_error; + break; } // U+100000..U+10FFFF F4 80..8F 80..BF 80..BF - case 0xf4: + case 0xF4: { - add(current); - get(); - if (JSON_LIKELY(0x80 <= current && current <= 0x8f)) + if (JSON_UNLIKELY(not (next_byte_in_range({0x80, 0x8F, 0x80, 0xBF, 0x80, 0xBF})))) { - add(current); - get(); - if (JSON_LIKELY(0x80 <= current && current <= 0xbf)) - { - add(current); - get(); - if (JSON_LIKELY(0x80 <= current && current <= 0xbf)) - { - add(current); - continue; - } - } + return token_type::parse_error; } - - error_message = "invalid string: ill-formed UTF-8 byte"; - return token_type::parse_error; + break; } // remaining bytes (80..C1 and F5..FF) are ill-formed @@ -1137,9 +1054,9 @@ lexer::token_type lexer::scan_string() } } -lexer::token_type lexer::scan_number() +json::lexer::token_type json::lexer::scan_number() { - // reset yytext to store the number's bytes + // reset token_buffer to store the number's bytes reset(); // the type of the parsed number; initially set to unsigned; will be @@ -1178,7 +1095,7 @@ lexer::token_type lexer::scan_number() default: { // all other characters are rejected outside scan_number() - assert(false); // LCOV_EXCL_LINE + assert(false); // LCOV_EXCL_LINE } } @@ -1232,9 +1149,7 @@ scan_number_zero: } default: - { goto scan_number_done; - } } scan_number_any1: @@ -1270,9 +1185,7 @@ scan_number_any1: } default: - { goto scan_number_done; - } } scan_number_decimal1: @@ -1329,9 +1242,7 @@ scan_number_decimal2: } default: - { goto scan_number_done; - } } scan_number_exponent: @@ -1363,7 +1274,8 @@ scan_number_exponent: default: { - error_message = "invalid number; expected '+', '-', or digit after exponent"; + error_message = + "invalid number; expected '+', '-', or digit after exponent"; return token_type::parse_error; } } @@ -1414,30 +1326,28 @@ scan_number_any2: } default: - { goto scan_number_done; - } } scan_number_done: - // unget the character after the number (we only read it to know - // that we are done scanning a number) - --chars_read; - next_unget = true; + // unget the character after the number (we only read it to know that + // we are done scanning a number) + unget(); + + char* endptr = nullptr; + errno = 0; // try to parse integers first and fall back to floats if (number_type == token_type::value_unsigned) { - char* endptr = nullptr; - errno = 0; - const auto x = std::strtoull(yytext.c_str(), &endptr, 10); + const auto x = std::strtoull(token_buffer.c_str(), &endptr, 10); // we checked the number format before - assert(endptr == yytext.data() + yytext.size()); + assert(endptr == token_buffer.data() + token_buffer.size()); if (errno == 0) { - value_unsigned = static_cast(x); + value_unsigned = static_cast(x); if (value_unsigned == x) { return token_type::value_unsigned; @@ -1446,16 +1356,14 @@ scan_number_done: } else if (number_type == token_type::value_integer) { - char* endptr = nullptr; - errno = 0; - const auto x = std::strtoll(yytext.c_str(), &endptr, 10); + const auto x = std::strtoll(token_buffer.c_str(), &endptr, 10); // we checked the number format before - assert(endptr == yytext.data() + yytext.size()); + assert(endptr == token_buffer.data() + token_buffer.size()); if (errno == 0) { - value_integer = static_cast(x); + value_integer = static_cast(x); if (value_integer == x) { return token_type::value_integer; @@ -1463,82 +1371,62 @@ scan_number_done: } } - // this code is reached if we parse a floating-point number or if - // an integer conversion above failed - strtof(value_float, yytext.c_str(), nullptr); + // this code is reached if we parse a floating-point number or if an + // integer conversion above failed + strtof(value_float, token_buffer.c_str(), &endptr); + + // we checked the number format before + assert(endptr == token_buffer.data() + token_buffer.size()); + return token_type::value_float; } -lexer::token_type lexer::scan_true() +json::lexer::token_type json::lexer::scan_literal(const char* literal_text, const std::size_t length, + token_type return_type) { - assert(current == 't'); - if (JSON_LIKELY((get() == 'r' && get() == 'u' && get() == 'e'))) + assert(current == literal_text[0]); + for (std::size_t i = 1; i < length; ++i) { - return token_type::literal_true; + if (JSON_UNLIKELY(get() != literal_text[i])) + { + error_message = "invalid literal"; + return token_type::parse_error; + } } - - error_message = "invalid literal; expected 'true'"; - return token_type::parse_error; + return return_type; } -lexer::token_type lexer::scan_false() -{ - assert(current == 'f'); - if (JSON_LIKELY((get() == 'a' && get() == 'l' && get() == 's' && get() == 'e'))) - { - return token_type::literal_false; - } - - error_message = "invalid literal; expected 'false'"; - return token_type::parse_error; -} - -lexer::token_type lexer::scan_null() -{ - assert(current == 'n'); - if (JSON_LIKELY((get() == 'u' && get() == 'l' && get() == 'l'))) - { - return token_type::literal_null; - } - - error_message = "invalid literal; expected 'null'"; - return token_type::parse_error; -} - -std::string lexer::get_token_string() const +std::string json::lexer::get_token_string() const { // escape control characters std::string result; - for (auto c : token_string) + raw_string_ostream ss(result); + for (const unsigned char c : token_string) { - if (c == '\0' || c == std::char_traits::eof()) - { - // ignore EOF - continue; - } - else if ('\x00' <= c && c <= '\x1f') + if (c <= '\x1F') { // escape control characters - result += "<" + codepoint_to_string(c) + ">"; + ss << "'; } else { // add character as is - result.append(1, c); + ss << c; } } + ss.flush(); return result; } -lexer::token_type lexer::scan() +json::lexer::token_type json::lexer::scan() { // read next character and ignore whitespace do { get(); } - while (current == ' ' || current == '\t' || current == '\n' || current == '\r'); + while (current == ' ' or current == '\t' or current == '\n' or current == '\r'); switch (current) { @@ -1558,11 +1446,11 @@ lexer::token_type lexer::scan() // literals case 't': - return scan_true(); + return scan_literal("true", 4, token_type::literal_true); case 'f': - return scan_false(); + return scan_literal("false", 5, token_type::literal_false); case 'n': - return scan_null(); + return scan_literal("null", 4, token_type::literal_null); // string case '\"': @@ -1595,164 +1483,92 @@ lexer::token_type lexer::scan() } } -/*! -@brief syntax analysis - -This class implements a recursive decent parser. -*/ -class json::parser -{ - using value_t = json::value_t; - - public: - /// a parser reading from an input adapter - explicit parser(wpi::raw_istream& s, - const parser_callback_t cb = nullptr) - : callback(cb), m_lexer(s) - {} - - /*! - @brief public parser interface - - @param[in] strict whether to expect the last token to be EOF - @return parsed JSON value - - @throw parse_error.101 in case of an unexpected token - @throw parse_error.102 if to_unicode fails or surrogate error - @throw parse_error.103 if to_unicode fails - */ - json parse(bool strict = true); - - /*! - @brief public accept interface - - @param[in] strict whether to expect the last token to be EOF - @return whether the input is a proper JSON text - */ - bool accept(bool strict = true); - - private: - /*! - @brief the actual parser - @throw parse_error.101 in case of an unexpected token - @throw parse_error.102 if to_unicode fails or surrogate error - @throw parse_error.103 if to_unicode fails - */ - json parse_internal(bool keep); - - /*! - @brief the acutal acceptor - - @invariant 1. The last token is not yet processed. Therefore, the - caller of this function must make sure a token has - been read. - 2. When this function returns, the last token is processed. - That is, the last read character was already considered. - - This invariant makes sure that no token needs to be "unput". - */ - bool accept_internal(); - - /// get next token from lexer - lexer::token_type get_token() - { - last_token = m_lexer.scan(); - return last_token; - } - - /*! - @throw parse_error.101 if expected token did not occur - */ - void expect(lexer::token_type t) const; - - /*! - @throw parse_error.101 if unexpected token occurred - */ - void unexpect(lexer::token_type t) const; - - private: - /// current level of recursion - int depth = 0; - /// callback function - const parser_callback_t callback = nullptr; - /// the type of the last read token - lexer::token_type last_token = lexer::token_type::uninitialized; - /// the lexer - lexer m_lexer; -}; - -json json::parser::parse(bool strict) +void json::parser::parse(const bool strict, json& result) { // read first token get_token(); - json result = parse_internal(true); + parse_internal(true, result); result.assert_invariant(); + // in strict mode, input must be completely read if (strict) { get_token(); - expect(lexer::token_type::end_of_input); + expect(token_type::end_of_input); } - // return parser result and replace it with null in case the - // top-level value was discarded by the callback function - return result.is_discarded() ? json() : std::move(result); + // in case of an error, return discarded value + if (errored) + { + result = value_t::discarded; + return; + } + + // set top-level value to null if it was discarded by the callback + // function + if (result.is_discarded()) + { + result = nullptr; + } } -bool json::parser::accept(bool strict) +void json::parser::parse_internal(bool keep, json& result) { - // read first token - get_token(); + // never parse after a parse error was detected + assert(not errored); - if (!accept_internal()) + // start with a discarded value + if (not result.is_discarded()) { - return false; + result.m_value.destroy(result.m_type); + result.m_type = value_t::discarded; } - if (strict && get_token() != lexer::token_type::end_of_input) - { - return false; - } - - return true; -} - -json json::parser::parse_internal(bool keep) -{ - auto result = json(value_t::discarded); - switch (last_token) { - case lexer::token_type::begin_object: + case token_type::begin_object: { - if (keep && (!callback - || ((keep = callback(depth++, parse_event_t::object_start, result)) != 0))) + if (keep) { - // explicitly set result to object to cope with {} - result.m_type = value_t::object; - result.m_value = value_t::object; + if (callback) + { + keep = callback(depth++, parse_event_t::object_start, result); + } + + if (not callback or keep) + { + // explicitly set result to object to cope with {} + result.m_type = value_t::object; + result.m_value = value_t::object; + } } // read next token get_token(); // closing } -> we are done - if (last_token == lexer::token_type::end_object) + if (last_token == token_type::end_object) { - if (keep && callback && !callback(--depth, parse_event_t::object_end, result)) + if (keep and callback and not callback(--depth, parse_event_t::object_end, result)) { - result = json(value_t::discarded); + result.m_value.destroy(result.m_type); + result.m_type = value_t::discarded; } - return result; + break; } // parse values + SmallString<128> key; + json value; while (true) { // store key - expect(lexer::token_type::value_string); - std::string key = m_lexer.get_string(); + if (not expect(token_type::value_string)) + { + return; + } + key = m_lexer.get_string(); bool keep_tag = false; if (keep) @@ -1770,170 +1586,223 @@ json json::parser::parse_internal(bool keep) // parse separator (:) get_token(); - expect(lexer::token_type::name_separator); + if (not expect(token_type::name_separator)) + { + return; + } // parse and add value get_token(); - auto value = parse_internal(keep); - if (keep && keep_tag && !value.is_discarded()) + value.m_value.destroy(value.m_type); + value.m_type = value_t::discarded; + parse_internal(keep, value); + + if (JSON_UNLIKELY(errored)) { - result[key] = std::move(value); + return; + } + + if (keep and keep_tag and not value.is_discarded()) + { + result.m_value.object->emplace_second(StringRef(key.data(), key.size()), std::move(value)); } // comma -> next value get_token(); - if (last_token == lexer::token_type::value_separator) + if (last_token == token_type::value_separator) { get_token(); continue; } // closing } - expect(lexer::token_type::end_object); + if (not expect(token_type::end_object)) + { + return; + } break; } - if (keep && callback && !callback(--depth, parse_event_t::object_end, result)) + if (keep and callback and not callback(--depth, parse_event_t::object_end, result)) { - result = json(value_t::discarded); + result.m_value.destroy(result.m_type); + result.m_type = value_t::discarded; } - - return result; + break; } - case lexer::token_type::begin_array: + case token_type::begin_array: { - if (keep && (!callback - || ((keep = callback(depth++, parse_event_t::array_start, result)) != 0))) + if (keep) { - // explicitly set result to object to cope with [] - result.m_type = value_t::array; - result.m_value = value_t::array; + if (callback) + { + keep = callback(depth++, parse_event_t::array_start, result); + } + + if (not callback or keep) + { + // explicitly set result to array to cope with [] + result.m_type = value_t::array; + result.m_value = value_t::array; + } } // read next token get_token(); // closing ] -> we are done - if (last_token == lexer::token_type::end_array) + if (last_token == token_type::end_array) { - if (callback && !callback(--depth, parse_event_t::array_end, result)) + if (callback and not callback(--depth, parse_event_t::array_end, result)) { - result = json(value_t::discarded); + result.m_value.destroy(result.m_type); + result.m_type = value_t::discarded; } - return result; + break; } // parse values + json value; while (true) { // parse value - auto value = parse_internal(keep); - if (keep && !value.is_discarded()) + value.m_value.destroy(value.m_type); + value.m_type = value_t::discarded; + parse_internal(keep, value); + + if (JSON_UNLIKELY(errored)) { - result.push_back(std::move(value)); + return; + } + + if (keep and not value.is_discarded()) + { + result.m_value.array->push_back(std::move(value)); } // comma -> next value get_token(); - if (last_token == lexer::token_type::value_separator) + if (last_token == token_type::value_separator) { get_token(); continue; } // closing ] - expect(lexer::token_type::end_array); + if (not expect(token_type::end_array)) + { + return; + } break; } - if (keep && callback && !callback(--depth, parse_event_t::array_end, result)) + if (keep and callback and not callback(--depth, parse_event_t::array_end, result)) { - result = json(value_t::discarded); + result.m_value.destroy(result.m_type); + result.m_type = value_t::discarded; } - - return result; + break; } - case lexer::token_type::literal_null: + case token_type::literal_null: { result.m_type = value_t::null; break; } - case lexer::token_type::value_string: + case token_type::value_string: { - result = json(m_lexer.get_string()); + result.m_type = value_t::string; + result.m_value = m_lexer.get_string(); break; } - case lexer::token_type::literal_true: + case token_type::literal_true: { result.m_type = value_t::boolean; result.m_value = true; break; } - case lexer::token_type::literal_false: + case token_type::literal_false: { result.m_type = value_t::boolean; result.m_value = false; break; } - case lexer::token_type::value_unsigned: + case token_type::value_unsigned: { result.m_type = value_t::number_unsigned; result.m_value = m_lexer.get_number_unsigned(); break; } - case lexer::token_type::value_integer: + case token_type::value_integer: { result.m_type = value_t::number_integer; result.m_value = m_lexer.get_number_integer(); break; } - case lexer::token_type::value_float: + case token_type::value_float: { result.m_type = value_t::number_float; result.m_value = m_lexer.get_number_float(); // throw in case of infinity or NAN - if (JSON_UNLIKELY(!std::isfinite(result.m_value.number_float))) + if (JSON_UNLIKELY(not std::isfinite(result.m_value.number_float))) { - JSON_THROW(json::out_of_range::create(406, "number overflow parsing '" + m_lexer.get_token_string() + "'")); + if (allow_exceptions) + { + JSON_THROW(out_of_range::create(406, "number overflow parsing '" + + Twine(m_lexer.get_token_string()) + "'")); + } + expect(token_type::uninitialized); } - break; } + case token_type::parse_error: + { + // using "uninitialized" to avoid "expected" message + if (not expect(token_type::uninitialized)) + { + return; + } + break; // LCOV_EXCL_LINE + } + default: { - // the last token was unexpected - unexpect(last_token); + // the last token was unexpected; we expected a value + if (not expect(token_type::literal_or_value)) + { + return; + } + break; // LCOV_EXCL_LINE } } - if (keep && callback && !callback(depth, parse_event_t::value, result)) + if (keep and callback and not callback(depth, parse_event_t::value, result)) { - result = json(value_t::discarded); + result.m_value.destroy(result.m_type); + result.m_type = value_t::discarded; } - return result; } bool json::parser::accept_internal() { switch (last_token) { - case lexer::token_type::begin_object: + case token_type::begin_object: { // read next token get_token(); // closing } -> we are done - if (last_token == lexer::token_type::end_object) + if (last_token == token_type::end_object) { return true; } @@ -1942,50 +1811,45 @@ bool json::parser::accept_internal() while (true) { // parse key - if (last_token != lexer::token_type::value_string) + if (last_token != token_type::value_string) { return false; } // parse separator (:) get_token(); - if (last_token != lexer::token_type::name_separator) + if (last_token != token_type::name_separator) { return false; } // parse value get_token(); - if (!accept_internal()) + if (not accept_internal()) { return false; } // comma -> next value get_token(); - if (last_token == lexer::token_type::value_separator) + if (last_token == token_type::value_separator) { get_token(); continue; } // closing } - if (last_token != lexer::token_type::end_object) - { - return false; - } - - return true; + return (last_token == token_type::end_object); } } - case lexer::token_type::begin_array: + case token_type::begin_array: { // read next token get_token(); // closing ] -> we are done - if (last_token == lexer::token_type::end_array) + if (last_token == token_type::end_array) { return true; } @@ -1994,101 +1858,109 @@ bool json::parser::accept_internal() while (true) { // parse value - if (!accept_internal()) + if (not accept_internal()) { return false; } // comma -> next value get_token(); - if (last_token == lexer::token_type::value_separator) + if (last_token == token_type::value_separator) { get_token(); continue; } // closing ] - if (last_token != lexer::token_type::end_array) - { - return false; - } - - return true; + return (last_token == token_type::end_array); } } - case lexer::token_type::literal_false: - case lexer::token_type::literal_null: - case lexer::token_type::literal_true: - case lexer::token_type::value_float: - case lexer::token_type::value_integer: - case lexer::token_type::value_string: - case lexer::token_type::value_unsigned: + case token_type::value_float: { + // reject infinity or NAN + return std::isfinite(m_lexer.get_number_float()); + } + + case token_type::literal_false: + case token_type::literal_null: + case token_type::literal_true: + case token_type::value_integer: + case token_type::value_string: + case token_type::value_unsigned: return true; - } - default: - { - // the last token was unexpected + default: // the last token was unexpected return false; - } } } -void json::parser::expect(lexer::token_type t) const +void json::parser::throw_exception() const { - if (JSON_UNLIKELY(t != last_token)) + std::string error_msg = "syntax error - "; + if (last_token == token_type::parse_error) { - std::string error_msg = "syntax error - "; - if (last_token == lexer::token_type::parse_error) - { - error_msg += m_lexer.get_error_message() + "; last read: '" + m_lexer.get_token_string() + "'"; - } - else - { - error_msg += "unexpected " + std::string(lexer::token_type_name(last_token)); - } - - error_msg += "; expected " + std::string(lexer::token_type_name(t)); - JSON_THROW(json::parse_error::create(101, m_lexer.get_position(), error_msg)); + error_msg += std::string(m_lexer.get_error_message()) + "; last read: '" + + m_lexer.get_token_string() + "'"; } -} - -void json::parser::unexpect(lexer::token_type t) const -{ - if (JSON_UNLIKELY(t == last_token)) + else { - std::string error_msg = "syntax error - "; - if (last_token == lexer::token_type::parse_error) - { - error_msg += m_lexer.get_error_message() + "; last read '" + m_lexer.get_token_string() + "'"; - } - else - { - error_msg += "unexpected " + std::string(lexer::token_type_name(last_token)); - } - - JSON_THROW(json::parse_error::create(101, m_lexer.get_position(), error_msg)); + error_msg += "unexpected " + std::string(lexer_t::token_type_name(last_token)); } + + if (expected != token_type::uninitialized) + { + error_msg += "; expected " + std::string(lexer_t::token_type_name(expected)); + } + + JSON_THROW(parse_error::create(101, m_lexer.get_position(), error_msg)); } -json json::parse(StringRef s, const parser_callback_t cb) +json json::parse(StringRef s, + const parser_callback_t cb, + const bool allow_exceptions) { - wpi::raw_mem_istream is(s.data(), s.size()); - return parser(is, cb).parse(true); + raw_mem_istream is(makeArrayRef(s.data(), s.size())); + return parse(is, cb, allow_exceptions); } -json json::parse(wpi::raw_istream& i, const parser_callback_t cb) +json json::parse(ArrayRef arr, + const parser_callback_t cb, + const bool allow_exceptions) { - return parser(i, cb).parse(true); + raw_mem_istream is(arr); + return parse(is, cb, allow_exceptions); } -namespace wpi { - -wpi::raw_istream& operator>>(wpi::raw_istream& i, json& j) +json json::parse(raw_istream& i, + const parser_callback_t cb, + const bool allow_exceptions) { - j = json::parser(i).parse(false); + json result; + parser(i, cb, allow_exceptions).parse(true, result); + return result; +} + +bool json::accept(StringRef s) +{ + raw_mem_istream is(makeArrayRef(s.data(), s.size())); + return parser(is).accept(true); +} + +bool json::accept(ArrayRef arr) +{ + raw_mem_istream is(arr); + return parser(is).accept(true); +} + +bool json::accept(raw_istream& i) +{ + return parser(i).accept(true); +} + +raw_istream& operator>>(raw_istream& i, json& j) +{ + json::parser(i).parse(false, j); return i; } diff --git a/wpiutil/src/main/native/cpp/json_pointer.cpp b/wpiutil/src/main/native/cpp/json_pointer.cpp index d0ebb28059..9cb1ec4fc3 100644 --- a/wpiutil/src/main/native/cpp/json_pointer.cpp +++ b/wpiutil/src/main/native/cpp/json_pointer.cpp @@ -1,5 +1,5 @@ /*----------------------------------------------------------------------------*/ -/* Modifications Copyright (c) FIRST 2017. All Rights Reserved. */ +/* Modifications Copyright (c) 2017-2018 FIRST. All Rights Reserved. */ /* Open Source Software - may be modified and shared by FRC teams. The code */ /* must be accompanied by the FIRST BSD license file in the root directory of */ /* the project. */ @@ -7,11 +7,11 @@ /* __ _____ _____ _____ __| | __| | | | JSON for Modern C++ -| | |__ | | | | | | version 2.1.1 +| | |__ | | | | | | version 3.1.2 |_____|_____|_____|_|___| https://github.com/nlohmann/json Licensed under the MIT License . -Copyright (c) 2013-2017 Niels Lohmann . +Copyright (c) 2013-2018 Niels Lohmann . Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal @@ -34,32 +34,50 @@ SOFTWARE. #define WPI_JSON_IMPLEMENTATION #include "wpi/json.h" -#include #include // accumulate -using namespace wpi; +#include "wpi/SmallString.h" -std::string json::json_pointer::to_string() const noexcept +namespace wpi { + +std::string json_pointer::to_string() const noexcept { - return std::accumulate(reference_tokens.begin(), - reference_tokens.end(), std::string{}, + return std::accumulate(reference_tokens.begin(), reference_tokens.end(), + std::string{}, [](const std::string & a, const std::string & b) { return a + "/" + escape(b); }); } -json::reference json::json_pointer::get_and_create(reference j) const +int json_pointer::array_index(const Twine& s) { - pointer result = &j; + SmallString<128> buf; + StringRef str = s.toNullTerminatedStringRef(buf); + std::size_t processed_chars = 0; + const int res = std::stoi(str, &processed_chars); - // in case no reference tokens exist, return a reference to the - // JSON value j which will be overwritten by a primitive value + // check if the string was completely read + if (JSON_UNLIKELY(processed_chars != str.size())) + { + JSON_THROW(detail::out_of_range::create(404, "unresolved reference token '" + Twine(s) + "'")); + } + + return res; +} + +json& json_pointer::get_and_create(json& j) const +{ + using size_type = typename json::size_type; + auto result = &j; + + // in case no reference tokens exist, return a reference to the JSON value + // j which will be overwritten by a primitive value for (const auto& reference_token : reference_tokens) { switch (result->m_type) { - case value_t::null: + case detail::value_t::null: { if (reference_token == "0") { @@ -74,86 +92,80 @@ json::reference json::json_pointer::get_and_create(reference j) const break; } - case value_t::object: + case detail::value_t::object: { // create an entry in the object result = &result->operator[](reference_token); break; } - case value_t::array: + case detail::value_t::array: { // create an entry in the array JSON_TRY { - result = &result->operator[](static_cast(std::stoi(reference_token))); + result = &result->operator[](static_cast(array_index(reference_token))); } - JSON_CATCH (std::invalid_argument&) + JSON_CATCH(std::invalid_argument&) { - JSON_THROW(parse_error::create(109, 0, "array index '" + reference_token + "' is not a number")); + JSON_THROW(detail::parse_error::create(109, 0, "array index '" + Twine(reference_token) + "' is not a number")); } break; } /* - The following code is only reached if there exists a - reference token _and_ the current value is primitive. In - this case, we have an error situation, because primitive - values may only occur as single value; that is, with an - empty list of reference tokens. + The following code is only reached if there exists a reference + token _and_ the current value is primitive. In this case, we have + an error situation, because primitive values may only occur as + single value; that is, with an empty list of reference tokens. */ default: - { - JSON_THROW(type_error::create(313, "invalid value to unflatten")); - } + JSON_THROW(detail::type_error::create(313, "invalid value to unflatten")); } } return *result; } -json::reference json::json_pointer::get_unchecked(pointer ptr) const +json& json_pointer::get_unchecked(json* ptr) const { + using size_type = typename json::size_type; for (const auto& reference_token : reference_tokens) { // convert null values to arrays or objects before continuing - if (ptr->m_type == value_t::null) + if (ptr->m_type == detail::value_t::null) { // check if reference token is a number - const bool nums = std::all_of(reference_token.begin(), - reference_token.end(), - [](const char x) + const bool nums = + std::all_of(reference_token.begin(), reference_token.end(), + [](const char x) { - return (x >= '0' && x <= '9'); + return (x >= '0' and x <= '9'); }); - // change value to array for numbers or "-" or to object - // otherwise - if (nums || reference_token == "-") - { - *ptr = value_t::array; - } - else - { - *ptr = value_t::object; - } + // change value to array for numbers or "-" or to object otherwise + *ptr = (nums or reference_token == "-") + ? detail::value_t::array + : detail::value_t::object; } switch (ptr->m_type) { - case value_t::object: + case detail::value_t::object: { // use unchecked object access ptr = &ptr->operator[](reference_token); break; } - case value_t::array: + case detail::value_t::array: { // error condition (cf. RFC 6901, Sect. 4) - if (reference_token.size() > 1 && reference_token[0] == '0') + if (JSON_UNLIKELY(reference_token.size() > 1 and reference_token[0] == '0')) { - JSON_THROW(parse_error::create(106, 0, "array index '" + reference_token + "' must not begin with '0'")); + JSON_THROW(detail::parse_error::create(106, 0, + "array index '" + Twine(reference_token) + + "' must not begin with '0'")); } if (reference_token == "-") @@ -166,193 +178,200 @@ json::reference json::json_pointer::get_unchecked(pointer ptr) const // convert array index to number; unchecked access JSON_TRY { - ptr = &ptr->operator[](static_cast(std::stoi(reference_token))); + ptr = &ptr->operator[]( + static_cast(array_index(reference_token))); } - JSON_CATCH (std::invalid_argument&) + JSON_CATCH(std::invalid_argument&) { - JSON_THROW(parse_error::create(109, 0, "array index '" + reference_token + "' is not a number")); + JSON_THROW(detail::parse_error::create(109, 0, "array index '" + Twine(reference_token) + "' is not a number")); } } break; } default: - { - JSON_THROW(out_of_range::create(404, "unresolved reference token '" + reference_token + "'")); - } + JSON_THROW(detail::out_of_range::create(404, "unresolved reference token '" + Twine(reference_token) + "'")); } } return *ptr; } -json::reference json::json_pointer::get_checked(pointer ptr) const +json& json_pointer::get_checked(json* ptr) const { + using size_type = typename json::size_type; for (const auto& reference_token : reference_tokens) { switch (ptr->m_type) { - case value_t::object: + case detail::value_t::object: { // note: at performs range check ptr = &ptr->at(reference_token); break; } - case value_t::array: + case detail::value_t::array: { - if (reference_token == "-") + if (JSON_UNLIKELY(reference_token == "-")) { // "-" always fails the range check - JSON_THROW(out_of_range::create(402, "array index '-' (" + - std::to_string(ptr->m_value.array->size()) + - ") is out of range")); + JSON_THROW(detail::out_of_range::create(402, + "array index '-' (" + Twine(ptr->m_value.array->size()) + + ") is out of range")); } // error condition (cf. RFC 6901, Sect. 4) - if (reference_token.size() > 1 && reference_token[0] == '0') + if (JSON_UNLIKELY(reference_token.size() > 1 and reference_token[0] == '0')) { - JSON_THROW(parse_error::create(106, 0, "array index '" + reference_token + "' must not begin with '0'")); + JSON_THROW(detail::parse_error::create(106, 0, + "array index '" + Twine(reference_token) + + "' must not begin with '0'")); } // note: at performs range check JSON_TRY { - ptr = &ptr->at(static_cast(std::stoi(reference_token))); + ptr = &ptr->at(static_cast(array_index(reference_token))); } - JSON_CATCH (std::invalid_argument&) + JSON_CATCH(std::invalid_argument&) { - JSON_THROW(parse_error::create(109, 0, "array index '" + reference_token + "' is not a number")); + JSON_THROW(detail::parse_error::create(109, 0, "array index '" + Twine(reference_token) + "' is not a number")); } break; } default: - { - JSON_THROW(out_of_range::create(404, "unresolved reference token '" + reference_token + "'")); - } + JSON_THROW(detail::out_of_range::create(404, "unresolved reference token '" + Twine(reference_token) + "'")); } } return *ptr; } -json::const_reference json::json_pointer::get_unchecked(const_pointer ptr) const +const json& json_pointer::get_unchecked(const json* ptr) const { + using size_type = typename json::size_type; for (const auto& reference_token : reference_tokens) { switch (ptr->m_type) { - case value_t::object: + case detail::value_t::object: { // use unchecked object access ptr = &ptr->operator[](reference_token); break; } - case value_t::array: + case detail::value_t::array: { - if (reference_token == "-") + if (JSON_UNLIKELY(reference_token == "-")) { // "-" cannot be used for const access - JSON_THROW(out_of_range::create(402, "array index '-' (" + - std::to_string(ptr->m_value.array->size()) + - ") is out of range")); + JSON_THROW(detail::out_of_range::create(402, + "array index '-' (" + Twine(ptr->m_value.array->size()) + + ") is out of range")); } // error condition (cf. RFC 6901, Sect. 4) - if (reference_token.size() > 1 && reference_token[0] == '0') + if (JSON_UNLIKELY(reference_token.size() > 1 and reference_token[0] == '0')) { - JSON_THROW(parse_error::create(106, 0, "array index '" + reference_token + "' must not begin with '0'")); + JSON_THROW(detail::parse_error::create(106, 0, + "array index '" + Twine(reference_token) + + "' must not begin with '0'")); } // use unchecked array access JSON_TRY { - ptr = &ptr->operator[](static_cast(std::stoi(reference_token))); + ptr = &ptr->operator[]( + static_cast(array_index(reference_token))); } - JSON_CATCH (std::invalid_argument&) + JSON_CATCH(std::invalid_argument&) { - JSON_THROW(parse_error::create(109, 0, "array index '" + reference_token + "' is not a number")); + JSON_THROW(detail::parse_error::create(109, 0, "array index '" + Twine(reference_token) + "' is not a number")); } break; } default: - { - JSON_THROW(out_of_range::create(404, "unresolved reference token '" + reference_token + "'")); - } + JSON_THROW(detail::out_of_range::create(404, "unresolved reference token '" + Twine(reference_token) + "'")); } } return *ptr; } -json::const_reference json::json_pointer::get_checked(const_pointer ptr) const +const json& json_pointer::get_checked(const json* ptr) const { + using size_type = typename json::size_type; for (const auto& reference_token : reference_tokens) { switch (ptr->m_type) { - case value_t::object: + case detail::value_t::object: { // note: at performs range check ptr = &ptr->at(reference_token); break; } - case value_t::array: + case detail::value_t::array: { - if (reference_token == "-") + if (JSON_UNLIKELY(reference_token == "-")) { // "-" always fails the range check - JSON_THROW(out_of_range::create(402, "array index '-' (" + - std::to_string(ptr->m_value.array->size()) + - ") is out of range")); + JSON_THROW(detail::out_of_range::create(402, + "array index '-' (" + Twine(ptr->m_value.array->size()) + + ") is out of range")); } // error condition (cf. RFC 6901, Sect. 4) - if (reference_token.size() > 1 && reference_token[0] == '0') + if (JSON_UNLIKELY(reference_token.size() > 1 and reference_token[0] == '0')) { - JSON_THROW(parse_error::create(106, 0, "array index '" + reference_token + "' must not begin with '0'")); + JSON_THROW(detail::parse_error::create(106, 0, + "array index '" + Twine(reference_token) + + "' must not begin with '0'")); } // note: at performs range check JSON_TRY { - ptr = &ptr->at(static_cast(std::stoi(reference_token))); + ptr = &ptr->at(static_cast(array_index(reference_token))); } - JSON_CATCH (std::invalid_argument&) + JSON_CATCH(std::invalid_argument&) { - JSON_THROW(parse_error::create(109, 0, "array index '" + reference_token + "' is not a number")); + JSON_THROW(detail::parse_error::create(109, 0, "array index '" + Twine(reference_token) + "' is not a number")); } break; } default: - { - JSON_THROW(out_of_range::create(404, "unresolved reference token '" + reference_token + "'")); - } + JSON_THROW(detail::out_of_range::create(404, "unresolved reference token '" + Twine(reference_token) + "'")); } } return *ptr; } -std::vector json::json_pointer::split(const std::string& reference_string) +std::vector json_pointer::split(const Twine& reference_string) { + SmallString<128> ref_str_buf; + StringRef ref_str = reference_string.toStringRef(ref_str_buf); std::vector result; // special case: empty reference string -> no reference tokens - if (reference_string.empty()) + if (ref_str.empty()) { return result; } // check if nonempty reference string begins with slash - if (reference_string[0] != '/') + if (JSON_UNLIKELY(ref_str[0] != '/')) { - JSON_THROW(parse_error::create(107, 1, "JSON pointer must be empty or begin with '/' - was: '" + reference_string + "'")); + JSON_THROW(detail::parse_error::create(107, 1, + "JSON pointer must be empty or begin with '/' - was: '" + + Twine(ref_str) + "'")); } // extract the reference tokens: @@ -360,7 +379,7 @@ std::vector json::json_pointer::split(const std::string& reference_ // - start: position after the previous slash for ( // search for the first slash after the first character - size_t slash = reference_string.find_first_of('/', 1), + std::size_t slash = ref_str.find_first_of('/', 1), // set the beginning of the first reference token start = 1; // we can stop if start == string::npos+1 = 0 @@ -369,120 +388,102 @@ std::vector json::json_pointer::split(const std::string& reference_ // (will eventually be 0 if slash == std::string::npos) start = slash + 1, // find next slash - slash = reference_string.find_first_of('/', start)) + slash = ref_str.find_first_of('/', start)) { // use the text between the beginning of the reference token // (start) and the last slash (slash). - auto reference_token = reference_string.substr(start, slash - start); + auto reference_token = ref_str.slice(start, slash); // check reference tokens are properly escaped - for (size_t pos = reference_token.find_first_of('~'); - pos != std::string::npos; + for (std::size_t pos = reference_token.find_first_of('~'); + pos != StringRef::npos; pos = reference_token.find_first_of('~', pos + 1)) { assert(reference_token[pos] == '~'); // ~ must be followed by 0 or 1 - if (pos == reference_token.size() - 1 || - (reference_token[pos + 1] != '0' && - reference_token[pos + 1] != '1')) + if (JSON_UNLIKELY(pos == reference_token.size() - 1 or + (reference_token[pos + 1] != '0' and + reference_token[pos + 1] != '1'))) { - JSON_THROW(parse_error::create(108, 0, "escape character '~' must be followed with '0' or '1'")); + JSON_THROW(detail::parse_error::create(108, 0, "escape character '~' must be followed with '0' or '1'")); } } // finally, store the reference token - unescape(reference_token); - result.push_back(reference_token); + std::string ref_tok = reference_token; + unescape(ref_tok); + result.emplace_back(std::move(ref_tok)); } return result; } -/*! -@brief replace all occurrences of a substring by another string - -@param[in,out] s the string to manipulate; changed so that all - occurrences of @a f are replaced with @a t -@param[in] f the substring to replace with @a t -@param[in] t the string to replace @a f - -@pre The search string @a f must not be empty. **This precondition is - enforced with an assertion.** - -@since version 2.0.0 -*/ -void json::json_pointer::replace_substring(std::string& s, - const std::string& f, +void json_pointer::replace_substring(std::string& s, const std::string& f, const std::string& t) { - assert(!f.empty()); - - for ( - size_t pos = s.find(f); // find first occurrence of f - pos != std::string::npos; // make sure f was found - s.replace(pos, f.size(), t), // replace with t - pos = s.find(f, pos + t.size()) // find next occurrence of f - ); + assert(not f.empty()); + for (auto pos = s.find(f); // find first occurrence of f + pos != std::string::npos; // make sure f was found + s.replace(pos, f.size(), t), // replace with t, and + pos = s.find(f, pos + t.size())) // find next occurrence of f + {} } -/// escape tilde and slash -std::string json::json_pointer::escape(std::string s) +std::string json_pointer::escape(std::string s) { - // escape "~"" to "~0" and "/" to "~1" replace_substring(s, "~", "~0"); replace_substring(s, "/", "~1"); return s; } -/// unescape tilde and slash -void json::json_pointer::unescape(std::string& s) +/// unescape "~1" to tilde and "~0" to slash (order is important!) +void json_pointer::unescape(std::string& s) { - // first transform any occurrence of the sequence '~1' to '/' replace_substring(s, "~1", "/"); - // then transform any occurrence of the sequence '~0' to '~' replace_substring(s, "~0", "~"); } -void json::json_pointer::flatten(const std::string& reference_string, +void json_pointer::flatten(const Twine& reference_string, const json& value, json& result) { switch (value.m_type) { - case value_t::array: + case detail::value_t::array: { if (value.m_value.array->empty()) { // flatten empty array as null - result[reference_string] = nullptr; + SmallString<64> buf; + result[reference_string.toStringRef(buf)] = nullptr; } else { // iterate array and use index as reference string - for (size_t i = 0; i < value.m_value.array->size(); ++i) + for (std::size_t i = 0; i < value.m_value.array->size(); ++i) { - flatten(reference_string + "/" + std::to_string(i), + flatten(reference_string + "/" + Twine(i), value.m_value.array->operator[](i), result); } } break; } - case value_t::object: + case detail::value_t::object: { if (value.m_value.object->empty()) { // flatten empty object as null - result[reference_string] = nullptr; + SmallString<64> buf; + result[reference_string.toStringRef(buf)] = nullptr; } else { // iterate object and use keys as reference string for (const auto& element : *value.m_value.object) { - flatten(reference_string + "/" + escape(element.first()), - element.second, result); + flatten(reference_string + "/" + Twine(escape(element.first())), element.second, result); } } break; @@ -491,17 +492,19 @@ void json::json_pointer::flatten(const std::string& reference_string, default: { // add primitive value with its reference string - result[reference_string] = value; + SmallString<64> buf; + result[reference_string.toStringRef(buf)] = value; break; } } } -json json::json_pointer::unflatten(const json& value) +json +json_pointer::unflatten(const json& value) { - if (!value.is_object()) + if (JSON_UNLIKELY(not value.is_object())) { - JSON_THROW(type_error::create(314, "only objects can be unflattened")); + JSON_THROW(detail::type_error::create(314, "only objects can be unflattened")); } // we need to iterate over the object values in sorted key order @@ -511,7 +514,7 @@ json json::json_pointer::unflatten(const json& value) { if (!i->second.is_primitive()) { - JSON_THROW(type_error::create(315, "values in object must be primitive")); + JSON_THROW(detail::type_error::create(315, "values in object must be primitive")); } sorted.push_back(i); } @@ -538,3 +541,4 @@ json json::json_pointer::unflatten(const json& value) return result; } +} // namespace wpi diff --git a/wpiutil/src/main/native/cpp/json_serializer.cpp b/wpiutil/src/main/native/cpp/json_serializer.cpp index 51bc87a8e6..8ab92b12cc 100644 --- a/wpiutil/src/main/native/cpp/json_serializer.cpp +++ b/wpiutil/src/main/native/cpp/json_serializer.cpp @@ -1,5 +1,5 @@ /*----------------------------------------------------------------------------*/ -/* Modifications Copyright (c) FIRST 2017. All Rights Reserved. */ +/* Modifications Copyright (c) 2017-2018 FIRST. All Rights Reserved. */ /* Open Source Software - may be modified and shared by FRC teams. The code */ /* must be accompanied by the FIRST BSD license file in the root directory of */ /* the project. */ @@ -7,11 +7,11 @@ /* __ _____ _____ _____ __| | __| | | | JSON for Modern C++ -| | |__ | | | | | | version 2.1.1 +| | |__ | | | | | | version 3.1.2 |_____|_____|_____|_|___| https://github.com/nlohmann/json Licensed under the MIT License . -Copyright (c) 2013-2017 Niels Lohmann . +Copyright (c) 2013-2018 Niels Lohmann . Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal @@ -34,15 +34,1101 @@ SOFTWARE. #define WPI_JSON_IMPLEMENTATION #include "wpi/json.h" +#include "wpi/Format.h" #include "wpi/SmallString.h" #include "wpi/StringExtras.h" +#include "wpi/raw_os_ostream.h" #include "json_serializer.h" -using namespace wpi; +namespace wpi { -void json::serializer::dump(const json& val, - const bool pretty_print, +namespace { + +/*! +@brief implements the Grisu2 algorithm for binary to decimal floating-point +conversion. + +This implementation is a slightly modified version of the reference +implementation which may be obtained from +http://florian.loitsch.com/publications (bench.tar.gz). + +The code is distributed under the MIT license, Copyright (c) 2009 Florian Loitsch. + +For a detailed description of the algorithm see: + +[1] Loitsch, "Printing Floating-Point Numbers Quickly and Accurately with + Integers", Proceedings of the ACM SIGPLAN 2010 Conference on Programming + Language Design and Implementation, PLDI 2010 +[2] Burger, Dybvig, "Printing Floating-Point Numbers Quickly and Accurately", + Proceedings of the ACM SIGPLAN 1996 Conference on Programming Language + Design and Implementation, PLDI 1996 +*/ +namespace dtoa_impl +{ + +template +Target reinterpret_bits(const Source source) +{ + static_assert(sizeof(Target) == sizeof(Source), "size mismatch"); + + Target target; + std::memcpy(&target, &source, sizeof(Source)); + return target; +} + +struct diyfp // f * 2^e +{ + static constexpr int kPrecision = 64; // = q + + uint64_t f; + int e; + + constexpr diyfp() noexcept : f(0), e(0) {} + constexpr diyfp(uint64_t f_, int e_) noexcept : f(f_), e(e_) {} + + /*! + @brief returns x - y + @pre x.e == y.e and x.f >= y.f + */ + static diyfp sub(const diyfp& x, const diyfp& y) noexcept + { + assert(x.e == y.e); + assert(x.f >= y.f); + + return diyfp(x.f - y.f, x.e); + } + + /*! + @brief returns x * y + @note The result is rounded. (Only the upper q bits are returned.) + */ + static diyfp mul(const diyfp& x, const diyfp& y) noexcept + { + static_assert(kPrecision == 64, "internal error"); + + // Computes: + // f = round((x.f * y.f) / 2^q) + // e = x.e + y.e + q + + // Emulate the 64-bit * 64-bit multiplication: + // + // p = u * v + // = (u_lo + 2^32 u_hi) (v_lo + 2^32 v_hi) + // = (u_lo v_lo ) + 2^32 ((u_lo v_hi ) + (u_hi v_lo )) + 2^64 (u_hi v_hi ) + // = (p0 ) + 2^32 ((p1 ) + (p2 )) + 2^64 (p3 ) + // = (p0_lo + 2^32 p0_hi) + 2^32 ((p1_lo + 2^32 p1_hi) + (p2_lo + 2^32 p2_hi)) + 2^64 (p3 ) + // = (p0_lo ) + 2^32 (p0_hi + p1_lo + p2_lo ) + 2^64 (p1_hi + p2_hi + p3) + // = (p0_lo ) + 2^32 (Q ) + 2^64 (H ) + // = (p0_lo ) + 2^32 (Q_lo + 2^32 Q_hi ) + 2^64 (H ) + // + // (Since Q might be larger than 2^32 - 1) + // + // = (p0_lo + 2^32 Q_lo) + 2^64 (Q_hi + H) + // + // (Q_hi + H does not overflow a 64-bit int) + // + // = p_lo + 2^64 p_hi + + const uint64_t u_lo = x.f & 0xFFFFFFFF; + const uint64_t u_hi = x.f >> 32; + const uint64_t v_lo = y.f & 0xFFFFFFFF; + const uint64_t v_hi = y.f >> 32; + + const uint64_t p0 = u_lo * v_lo; + const uint64_t p1 = u_lo * v_hi; + const uint64_t p2 = u_hi * v_lo; + const uint64_t p3 = u_hi * v_hi; + + const uint64_t p0_hi = p0 >> 32; + const uint64_t p1_lo = p1 & 0xFFFFFFFF; + const uint64_t p1_hi = p1 >> 32; + const uint64_t p2_lo = p2 & 0xFFFFFFFF; + const uint64_t p2_hi = p2 >> 32; + + uint64_t Q = p0_hi + p1_lo + p2_lo; + + // The full product might now be computed as + // + // p_hi = p3 + p2_hi + p1_hi + (Q >> 32) + // p_lo = p0_lo + (Q << 32) + // + // But in this particular case here, the full p_lo is not required. + // Effectively we only need to add the highest bit in p_lo to p_hi (and + // Q_hi + 1 does not overflow). + + Q += uint64_t{1} << (64 - 32 - 1); // round, ties up + + const uint64_t h = p3 + p2_hi + p1_hi + (Q >> 32); + + return diyfp(h, x.e + y.e + 64); + } + + /*! + @brief normalize x such that the significand is >= 2^(q-1) + @pre x.f != 0 + */ + static diyfp normalize(diyfp x) noexcept + { + assert(x.f != 0); + + while ((x.f >> 63) == 0) + { + x.f <<= 1; + x.e--; + } + + return x; + } + + /*! + @brief normalize x such that the result has the exponent E + @pre e >= x.e and the upper e - x.e bits of x.f must be zero. + */ + static diyfp normalize_to(const diyfp& x, const int target_exponent) noexcept + { + const int delta = x.e - target_exponent; + + assert(delta >= 0); + assert(((x.f << delta) >> delta) == x.f); + + return diyfp(x.f << delta, target_exponent); + } +}; + +struct boundaries +{ + diyfp w; + diyfp minus; + diyfp plus; +}; + +/*! +Compute the (normalized) diyfp representing the input number 'value' and its +boundaries. + +@pre value must be finite and positive +*/ +template +boundaries compute_boundaries(FloatType value) +{ + assert(std::isfinite(value)); + assert(value > 0); + + // Convert the IEEE representation into a diyfp. + // + // If v is denormal: + // value = 0.F * 2^(1 - bias) = ( F) * 2^(1 - bias - (p-1)) + // If v is normalized: + // value = 1.F * 2^(E - bias) = (2^(p-1) + F) * 2^(E - bias - (p-1)) + + static_assert(std::numeric_limits::is_iec559, + "internal error: dtoa_short requires an IEEE-754 floating-point implementation"); + + constexpr int kPrecision = std::numeric_limits::digits; // = p (includes the hidden bit) + constexpr int kBias = std::numeric_limits::max_exponent - 1 + (kPrecision - 1); + constexpr int kMinExp = 1 - kBias; + constexpr uint64_t kHiddenBit = uint64_t{1} << (kPrecision - 1); // = 2^(p-1) + + using bits_type = typename std::conditional< kPrecision == 24, uint32_t, uint64_t >::type; + + const uint64_t bits = reinterpret_bits(value); + const uint64_t E = bits >> (kPrecision - 1); + const uint64_t F = bits & (kHiddenBit - 1); + + const bool is_denormal = (E == 0); + const diyfp v = is_denormal + ? diyfp(F, kMinExp) + : diyfp(F + kHiddenBit, static_cast(E) - kBias); + + // Compute the boundaries m- and m+ of the floating-point value + // v = f * 2^e. + // + // Determine v- and v+, the floating-point predecessor and successor if v, + // respectively. + // + // v- = v - 2^e if f != 2^(p-1) or e == e_min (A) + // = v - 2^(e-1) if f == 2^(p-1) and e > e_min (B) + // + // v+ = v + 2^e + // + // Let m- = (v- + v) / 2 and m+ = (v + v+) / 2. All real numbers _strictly_ + // between m- and m+ round to v, regardless of how the input rounding + // algorithm breaks ties. + // + // ---+-------------+-------------+-------------+-------------+--- (A) + // v- m- v m+ v+ + // + // -----------------+------+------+-------------+-------------+--- (B) + // v- m- v m+ v+ + + const bool lower_boundary_is_closer = (F == 0 and E > 1); + const diyfp m_plus = diyfp(2 * v.f + 1, v.e - 1); + const diyfp m_minus = lower_boundary_is_closer + ? diyfp(4 * v.f - 1, v.e - 2) // (B) + : diyfp(2 * v.f - 1, v.e - 1); // (A) + + // Determine the normalized w+ = m+. + const diyfp w_plus = diyfp::normalize(m_plus); + + // Determine w- = m- such that e_(w-) = e_(w+). + const diyfp w_minus = diyfp::normalize_to(m_minus, w_plus.e); + + return {diyfp::normalize(v), w_minus, w_plus}; +} + +// Given normalized diyfp w, Grisu needs to find a (normalized) cached +// power-of-ten c, such that the exponent of the product c * w = f * 2^e lies +// within a certain range [alpha, gamma] (Definition 3.2 from [1]) +// +// alpha <= e = e_c + e_w + q <= gamma +// +// or +// +// f_c * f_w * 2^alpha <= f_c 2^(e_c) * f_w 2^(e_w) * 2^q +// <= f_c * f_w * 2^gamma +// +// Since c and w are normalized, i.e. 2^(q-1) <= f < 2^q, this implies +// +// 2^(q-1) * 2^(q-1) * 2^alpha <= c * w * 2^q < 2^q * 2^q * 2^gamma +// +// or +// +// 2^(q - 2 + alpha) <= c * w < 2^(q + gamma) +// +// The choice of (alpha,gamma) determines the size of the table and the form of +// the digit generation procedure. Using (alpha,gamma)=(-60,-32) works out well +// in practice: +// +// The idea is to cut the number c * w = f * 2^e into two parts, which can be +// processed independently: An integral part p1, and a fractional part p2: +// +// f * 2^e = ( (f div 2^-e) * 2^-e + (f mod 2^-e) ) * 2^e +// = (f div 2^-e) + (f mod 2^-e) * 2^e +// = p1 + p2 * 2^e +// +// The conversion of p1 into decimal form requires a series of divisions and +// modulos by (a power of) 10. These operations are faster for 32-bit than for +// 64-bit integers, so p1 should ideally fit into a 32-bit integer. This can be +// achieved by choosing +// +// -e >= 32 or e <= -32 := gamma +// +// In order to convert the fractional part +// +// p2 * 2^e = p2 / 2^-e = d[-1] / 10^1 + d[-2] / 10^2 + ... +// +// into decimal form, the fraction is repeatedly multiplied by 10 and the digits +// d[-i] are extracted in order: +// +// (10 * p2) div 2^-e = d[-1] +// (10 * p2) mod 2^-e = d[-2] / 10^1 + ... +// +// The multiplication by 10 must not overflow. It is sufficient to choose +// +// 10 * p2 < 16 * p2 = 2^4 * p2 <= 2^64. +// +// Since p2 = f mod 2^-e < 2^-e, +// +// -e <= 60 or e >= -60 := alpha + +constexpr int kAlpha = -60; +constexpr int kGamma = -32; + +struct cached_power // c = f * 2^e ~= 10^k +{ + uint64_t f; + int e; + int k; +}; + +/*! +For a normalized diyfp w = f * 2^e, this function returns a (normalized) cached +power-of-ten c = f_c * 2^e_c, such that the exponent of the product w * c +satisfies (Definition 3.2 from [1]) + + alpha <= e_c + e + q <= gamma. +*/ +inline cached_power get_cached_power_for_binary_exponent(int e) +{ + // Now + // + // alpha <= e_c + e + q <= gamma (1) + // ==> f_c * 2^alpha <= c * 2^e * 2^q + // + // and since the c's are normalized, 2^(q-1) <= f_c, + // + // ==> 2^(q - 1 + alpha) <= c * 2^(e + q) + // ==> 2^(alpha - e - 1) <= c + // + // If c were an exakt power of ten, i.e. c = 10^k, one may determine k as + // + // k = ceil( log_10( 2^(alpha - e - 1) ) ) + // = ceil( (alpha - e - 1) * log_10(2) ) + // + // From the paper: + // "In theory the result of the procedure could be wrong since c is rounded, + // and the computation itself is approximated [...]. In practice, however, + // this simple function is sufficient." + // + // For IEEE double precision floating-point numbers converted into + // normalized diyfp's w = f * 2^e, with q = 64, + // + // e >= -1022 (min IEEE exponent) + // -52 (p - 1) + // -52 (p - 1, possibly normalize denormal IEEE numbers) + // -11 (normalize the diyfp) + // = -1137 + // + // and + // + // e <= +1023 (max IEEE exponent) + // -52 (p - 1) + // -11 (normalize the diyfp) + // = 960 + // + // This binary exponent range [-1137,960] results in a decimal exponent + // range [-307,324]. One does not need to store a cached power for each + // k in this range. For each such k it suffices to find a cached power + // such that the exponent of the product lies in [alpha,gamma]. + // This implies that the difference of the decimal exponents of adjacent + // table entries must be less than or equal to + // + // floor( (gamma - alpha) * log_10(2) ) = 8. + // + // (A smaller distance gamma-alpha would require a larger table.) + + // NB: + // Actually this function returns c, such that -60 <= e_c + e + 64 <= -34. + + constexpr int kCachedPowersSize = 79; + constexpr int kCachedPowersMinDecExp = -300; + constexpr int kCachedPowersDecStep = 8; + + static constexpr cached_power kCachedPowers[] = + { + { 0xAB70FE17C79AC6CA, -1060, -300 }, + { 0xFF77B1FCBEBCDC4F, -1034, -292 }, + { 0xBE5691EF416BD60C, -1007, -284 }, + { 0x8DD01FAD907FFC3C, -980, -276 }, + { 0xD3515C2831559A83, -954, -268 }, + { 0x9D71AC8FADA6C9B5, -927, -260 }, + { 0xEA9C227723EE8BCB, -901, -252 }, + { 0xAECC49914078536D, -874, -244 }, + { 0x823C12795DB6CE57, -847, -236 }, + { 0xC21094364DFB5637, -821, -228 }, + { 0x9096EA6F3848984F, -794, -220 }, + { 0xD77485CB25823AC7, -768, -212 }, + { 0xA086CFCD97BF97F4, -741, -204 }, + { 0xEF340A98172AACE5, -715, -196 }, + { 0xB23867FB2A35B28E, -688, -188 }, + { 0x84C8D4DFD2C63F3B, -661, -180 }, + { 0xC5DD44271AD3CDBA, -635, -172 }, + { 0x936B9FCEBB25C996, -608, -164 }, + { 0xDBAC6C247D62A584, -582, -156 }, + { 0xA3AB66580D5FDAF6, -555, -148 }, + { 0xF3E2F893DEC3F126, -529, -140 }, + { 0xB5B5ADA8AAFF80B8, -502, -132 }, + { 0x87625F056C7C4A8B, -475, -124 }, + { 0xC9BCFF6034C13053, -449, -116 }, + { 0x964E858C91BA2655, -422, -108 }, + { 0xDFF9772470297EBD, -396, -100 }, + { 0xA6DFBD9FB8E5B88F, -369, -92 }, + { 0xF8A95FCF88747D94, -343, -84 }, + { 0xB94470938FA89BCF, -316, -76 }, + { 0x8A08F0F8BF0F156B, -289, -68 }, + { 0xCDB02555653131B6, -263, -60 }, + { 0x993FE2C6D07B7FAC, -236, -52 }, + { 0xE45C10C42A2B3B06, -210, -44 }, + { 0xAA242499697392D3, -183, -36 }, + { 0xFD87B5F28300CA0E, -157, -28 }, + { 0xBCE5086492111AEB, -130, -20 }, + { 0x8CBCCC096F5088CC, -103, -12 }, + { 0xD1B71758E219652C, -77, -4 }, + { 0x9C40000000000000, -50, 4 }, + { 0xE8D4A51000000000, -24, 12 }, + { 0xAD78EBC5AC620000, 3, 20 }, + { 0x813F3978F8940984, 30, 28 }, + { 0xC097CE7BC90715B3, 56, 36 }, + { 0x8F7E32CE7BEA5C70, 83, 44 }, + { 0xD5D238A4ABE98068, 109, 52 }, + { 0x9F4F2726179A2245, 136, 60 }, + { 0xED63A231D4C4FB27, 162, 68 }, + { 0xB0DE65388CC8ADA8, 189, 76 }, + { 0x83C7088E1AAB65DB, 216, 84 }, + { 0xC45D1DF942711D9A, 242, 92 }, + { 0x924D692CA61BE758, 269, 100 }, + { 0xDA01EE641A708DEA, 295, 108 }, + { 0xA26DA3999AEF774A, 322, 116 }, + { 0xF209787BB47D6B85, 348, 124 }, + { 0xB454E4A179DD1877, 375, 132 }, + { 0x865B86925B9BC5C2, 402, 140 }, + { 0xC83553C5C8965D3D, 428, 148 }, + { 0x952AB45CFA97A0B3, 455, 156 }, + { 0xDE469FBD99A05FE3, 481, 164 }, + { 0xA59BC234DB398C25, 508, 172 }, + { 0xF6C69A72A3989F5C, 534, 180 }, + { 0xB7DCBF5354E9BECE, 561, 188 }, + { 0x88FCF317F22241E2, 588, 196 }, + { 0xCC20CE9BD35C78A5, 614, 204 }, + { 0x98165AF37B2153DF, 641, 212 }, + { 0xE2A0B5DC971F303A, 667, 220 }, + { 0xA8D9D1535CE3B396, 694, 228 }, + { 0xFB9B7CD9A4A7443C, 720, 236 }, + { 0xBB764C4CA7A44410, 747, 244 }, + { 0x8BAB8EEFB6409C1A, 774, 252 }, + { 0xD01FEF10A657842C, 800, 260 }, + { 0x9B10A4E5E9913129, 827, 268 }, + { 0xE7109BFBA19C0C9D, 853, 276 }, + { 0xAC2820D9623BF429, 880, 284 }, + { 0x80444B5E7AA7CF85, 907, 292 }, + { 0xBF21E44003ACDD2D, 933, 300 }, + { 0x8E679C2F5E44FF8F, 960, 308 }, + { 0xD433179D9C8CB841, 986, 316 }, + { 0x9E19DB92B4E31BA9, 1013, 324 }, + }; + + // This computation gives exactly the same results for k as + // k = ceil((kAlpha - e - 1) * 0.30102999566398114) + // for |e| <= 1500, but doesn't require floating-point operations. + // NB: log_10(2) ~= 78913 / 2^18 + assert(e >= -1500); + assert(e <= 1500); + const int f = kAlpha - e - 1; + const int k = (f * 78913) / (1 << 18) + (f > 0); + + const int index = (-kCachedPowersMinDecExp + k + (kCachedPowersDecStep - 1)) / kCachedPowersDecStep; + assert(index >= 0); + assert(index < kCachedPowersSize); + static_cast(kCachedPowersSize); // Fix warning. + + const cached_power cached = kCachedPowers[index]; + assert(kAlpha <= cached.e + e + 64); + assert(kGamma >= cached.e + e + 64); + + return cached; +} + +/*! +For n != 0, returns k, such that pow10 := 10^(k-1) <= n < 10^k. +For n == 0, returns 1 and sets pow10 := 1. +*/ +inline int find_largest_pow10(const uint32_t n, uint32_t& pow10) +{ + // LCOV_EXCL_START + if (n >= 1000000000) + { + pow10 = 1000000000; + return 10; + } + // LCOV_EXCL_STOP + else if (n >= 100000000) + { + pow10 = 100000000; + return 9; + } + else if (n >= 10000000) + { + pow10 = 10000000; + return 8; + } + else if (n >= 1000000) + { + pow10 = 1000000; + return 7; + } + else if (n >= 100000) + { + pow10 = 100000; + return 6; + } + else if (n >= 10000) + { + pow10 = 10000; + return 5; + } + else if (n >= 1000) + { + pow10 = 1000; + return 4; + } + else if (n >= 100) + { + pow10 = 100; + return 3; + } + else if (n >= 10) + { + pow10 = 10; + return 2; + } + else + { + pow10 = 1; + return 1; + } +} + +inline void grisu2_round(char* buf, int len, uint64_t dist, uint64_t delta, + uint64_t rest, uint64_t ten_k) +{ + assert(len >= 1); + assert(dist <= delta); + assert(rest <= delta); + assert(ten_k > 0); + + // <--------------------------- delta ----> + // <---- dist ---------> + // --------------[------------------+-------------------]-------------- + // M- w M+ + // + // ten_k + // <------> + // <---- rest ----> + // --------------[------------------+----+--------------]-------------- + // w V + // = buf * 10^k + // + // ten_k represents a unit-in-the-last-place in the decimal representation + // stored in buf. + // Decrement buf by ten_k while this takes buf closer to w. + + // The tests are written in this order to avoid overflow in unsigned + // integer arithmetic. + + while (rest < dist + and delta - rest >= ten_k + and (rest + ten_k < dist or dist - rest > rest + ten_k - dist)) + { + assert(buf[len - 1] != '0'); + buf[len - 1]--; + rest += ten_k; + } +} + +/*! +Generates V = buffer * 10^decimal_exponent, such that M- <= V <= M+. +M- and M+ must be normalized and share the same exponent -60 <= e <= -32. +*/ +inline void grisu2_digit_gen(char* buffer, int& length, int& decimal_exponent, + diyfp M_minus, diyfp w, diyfp M_plus) +{ + static_assert(kAlpha >= -60, "internal error"); + static_assert(kGamma <= -32, "internal error"); + + // Generates the digits (and the exponent) of a decimal floating-point + // number V = buffer * 10^decimal_exponent in the range [M-, M+]. The diyfp's + // w, M- and M+ share the same exponent e, which satisfies alpha <= e <= gamma. + // + // <--------------------------- delta ----> + // <---- dist ---------> + // --------------[------------------+-------------------]-------------- + // M- w M+ + // + // Grisu2 generates the digits of M+ from left to right and stops as soon as + // V is in [M-,M+]. + + assert(M_plus.e >= kAlpha); + assert(M_plus.e <= kGamma); + + uint64_t delta = diyfp::sub(M_plus, M_minus).f; // (significand of (M+ - M-), implicit exponent is e) + uint64_t dist = diyfp::sub(M_plus, w ).f; // (significand of (M+ - w ), implicit exponent is e) + + // Split M+ = f * 2^e into two parts p1 and p2 (note: e < 0): + // + // M+ = f * 2^e + // = ((f div 2^-e) * 2^-e + (f mod 2^-e)) * 2^e + // = ((p1 ) * 2^-e + (p2 )) * 2^e + // = p1 + p2 * 2^e + + const diyfp one(uint64_t{1} << -M_plus.e, M_plus.e); + + uint32_t p1 = static_cast(M_plus.f >> -one.e); // p1 = f div 2^-e (Since -e >= 32, p1 fits into a 32-bit int.) + uint64_t p2 = M_plus.f & (one.f - 1); // p2 = f mod 2^-e + + // 1) + // + // Generate the digits of the integral part p1 = d[n-1]...d[1]d[0] + + assert(p1 > 0); + + uint32_t pow10; + const int k = find_largest_pow10(p1, pow10); + + // 10^(k-1) <= p1 < 10^k, pow10 = 10^(k-1) + // + // p1 = (p1 div 10^(k-1)) * 10^(k-1) + (p1 mod 10^(k-1)) + // = (d[k-1] ) * 10^(k-1) + (p1 mod 10^(k-1)) + // + // M+ = p1 + p2 * 2^e + // = d[k-1] * 10^(k-1) + (p1 mod 10^(k-1)) + p2 * 2^e + // = d[k-1] * 10^(k-1) + ((p1 mod 10^(k-1)) * 2^-e + p2) * 2^e + // = d[k-1] * 10^(k-1) + ( rest) * 2^e + // + // Now generate the digits d[n] of p1 from left to right (n = k-1,...,0) + // + // p1 = d[k-1]...d[n] * 10^n + d[n-1]...d[0] + // + // but stop as soon as + // + // rest * 2^e = (d[n-1]...d[0] * 2^-e + p2) * 2^e <= delta * 2^e + + int n = k; + while (n > 0) + { + // Invariants: + // M+ = buffer * 10^n + (p1 + p2 * 2^e) (buffer = 0 for n = k) + // pow10 = 10^(n-1) <= p1 < 10^n + // + const uint32_t d = p1 / pow10; // d = p1 div 10^(n-1) + const uint32_t r = p1 % pow10; // r = p1 mod 10^(n-1) + // + // M+ = buffer * 10^n + (d * 10^(n-1) + r) + p2 * 2^e + // = (buffer * 10 + d) * 10^(n-1) + (r + p2 * 2^e) + // + assert(d <= 9); + buffer[length++] = static_cast('0' + d); // buffer := buffer * 10 + d + // + // M+ = buffer * 10^(n-1) + (r + p2 * 2^e) + // + p1 = r; + n--; + // + // M+ = buffer * 10^n + (p1 + p2 * 2^e) + // pow10 = 10^n + // + + // Now check if enough digits have been generated. + // Compute + // + // p1 + p2 * 2^e = (p1 * 2^-e + p2) * 2^e = rest * 2^e + // + // Note: + // Since rest and delta share the same exponent e, it suffices to + // compare the significands. + const uint64_t rest = (uint64_t{p1} << -one.e) + p2; + if (rest <= delta) + { + // V = buffer * 10^n, with M- <= V <= M+. + + decimal_exponent += n; + + // We may now just stop. But instead look if the buffer could be + // decremented to bring V closer to w. + // + // pow10 = 10^n is now 1 ulp in the decimal representation V. + // The rounding procedure works with diyfp's with an implicit + // exponent of e. + // + // 10^n = (10^n * 2^-e) * 2^e = ulp * 2^e + // + const uint64_t ten_n = uint64_t{pow10} << -one.e; + grisu2_round(buffer, length, dist, delta, rest, ten_n); + + return; + } + + pow10 /= 10; + // + // pow10 = 10^(n-1) <= p1 < 10^n + // Invariants restored. + } + + // 2) + // + // The digits of the integral part have been generated: + // + // M+ = d[k-1]...d[1]d[0] + p2 * 2^e + // = buffer + p2 * 2^e + // + // Now generate the digits of the fractional part p2 * 2^e. + // + // Note: + // No decimal point is generated: the exponent is adjusted instead. + // + // p2 actually represents the fraction + // + // p2 * 2^e + // = p2 / 2^-e + // = d[-1] / 10^1 + d[-2] / 10^2 + ... + // + // Now generate the digits d[-m] of p1 from left to right (m = 1,2,...) + // + // p2 * 2^e = d[-1]d[-2]...d[-m] * 10^-m + // + 10^-m * (d[-m-1] / 10^1 + d[-m-2] / 10^2 + ...) + // + // using + // + // 10^m * p2 = ((10^m * p2) div 2^-e) * 2^-e + ((10^m * p2) mod 2^-e) + // = ( d) * 2^-e + ( r) + // + // or + // 10^m * p2 * 2^e = d + r * 2^e + // + // i.e. + // + // M+ = buffer + p2 * 2^e + // = buffer + 10^-m * (d + r * 2^e) + // = (buffer * 10^m + d) * 10^-m + 10^-m * r * 2^e + // + // and stop as soon as 10^-m * r * 2^e <= delta * 2^e + + assert(p2 > delta); + + int m = 0; + for (;;) + { + // Invariant: + // M+ = buffer * 10^-m + 10^-m * (d[-m-1] / 10 + d[-m-2] / 10^2 + ...) * 2^e + // = buffer * 10^-m + 10^-m * (p2 ) * 2^e + // = buffer * 10^-m + 10^-m * (1/10 * (10 * p2) ) * 2^e + // = buffer * 10^-m + 10^-m * (1/10 * ((10*p2 div 2^-e) * 2^-e + (10*p2 mod 2^-e)) * 2^e + // + assert(p2 <= UINT64_MAX / 10); + p2 *= 10; + const uint64_t d = p2 >> -one.e; // d = (10 * p2) div 2^-e + const uint64_t r = p2 & (one.f - 1); // r = (10 * p2) mod 2^-e + // + // M+ = buffer * 10^-m + 10^-m * (1/10 * (d * 2^-e + r) * 2^e + // = buffer * 10^-m + 10^-m * (1/10 * (d + r * 2^e)) + // = (buffer * 10 + d) * 10^(-m-1) + 10^(-m-1) * r * 2^e + // + assert(d <= 9); + buffer[length++] = static_cast('0' + d); // buffer := buffer * 10 + d + // + // M+ = buffer * 10^(-m-1) + 10^(-m-1) * r * 2^e + // + p2 = r; + m++; + // + // M+ = buffer * 10^-m + 10^-m * p2 * 2^e + // Invariant restored. + + // Check if enough digits have been generated. + // + // 10^-m * p2 * 2^e <= delta * 2^e + // p2 * 2^e <= 10^m * delta * 2^e + // p2 <= 10^m * delta + delta *= 10; + dist *= 10; + if (p2 <= delta) + { + break; + } + } + + // V = buffer * 10^-m, with M- <= V <= M+. + + decimal_exponent -= m; + + // 1 ulp in the decimal representation is now 10^-m. + // Since delta and dist are now scaled by 10^m, we need to do the + // same with ulp in order to keep the units in sync. + // + // 10^m * 10^-m = 1 = 2^-e * 2^e = ten_m * 2^e + // + const uint64_t ten_m = one.f; + grisu2_round(buffer, length, dist, delta, p2, ten_m); + + // By construction this algorithm generates the shortest possible decimal + // number (Loitsch, Theorem 6.2) which rounds back to w. + // For an input number of precision p, at least + // + // N = 1 + ceil(p * log_10(2)) + // + // decimal digits are sufficient to identify all binary floating-point + // numbers (Matula, "In-and-Out conversions"). + // This implies that the algorithm does not produce more than N decimal + // digits. + // + // N = 17 for p = 53 (IEEE double precision) + // N = 9 for p = 24 (IEEE single precision) +} + +/*! +v = buf * 10^decimal_exponent +len is the length of the buffer (number of decimal digits) +The buffer must be large enough, i.e. >= max_digits10. +*/ +inline void grisu2(char* buf, int& len, int& decimal_exponent, + diyfp m_minus, diyfp v, diyfp m_plus) +{ + assert(m_plus.e == m_minus.e); + assert(m_plus.e == v.e); + + // --------(-----------------------+-----------------------)-------- (A) + // m- v m+ + // + // --------------------(-----------+-----------------------)-------- (B) + // m- v m+ + // + // First scale v (and m- and m+) such that the exponent is in the range + // [alpha, gamma]. + + const cached_power cached = get_cached_power_for_binary_exponent(m_plus.e); + + const diyfp c_minus_k(cached.f, cached.e); // = c ~= 10^-k + + // The exponent of the products is = v.e + c_minus_k.e + q and is in the range [alpha,gamma] + const diyfp w = diyfp::mul(v, c_minus_k); + const diyfp w_minus = diyfp::mul(m_minus, c_minus_k); + const diyfp w_plus = diyfp::mul(m_plus, c_minus_k); + + // ----(---+---)---------------(---+---)---------------(---+---)---- + // w- w w+ + // = c*m- = c*v = c*m+ + // + // diyfp::mul rounds its result and c_minus_k is approximated too. w, w- and + // w+ are now off by a small amount. + // In fact: + // + // w - v * 10^k < 1 ulp + // + // To account for this inaccuracy, add resp. subtract 1 ulp. + // + // --------+---[---------------(---+---)---------------]---+-------- + // w- M- w M+ w+ + // + // Now any number in [M-, M+] (bounds included) will round to w when input, + // regardless of how the input rounding algorithm breaks ties. + // + // And digit_gen generates the shortest possible such number in [M-, M+]. + // Note that this does not mean that Grisu2 always generates the shortest + // possible number in the interval (m-, m+). + const diyfp M_minus(w_minus.f + 1, w_minus.e); + const diyfp M_plus (w_plus.f - 1, w_plus.e ); + + decimal_exponent = -cached.k; // = -(-k) = k + + grisu2_digit_gen(buf, len, decimal_exponent, M_minus, w, M_plus); +} + +/*! +v = buf * 10^decimal_exponent +len is the length of the buffer (number of decimal digits) +The buffer must be large enough, i.e. >= max_digits10. +*/ +template +void grisu2(char* buf, int& len, int& decimal_exponent, FloatType value) +{ + static_assert(diyfp::kPrecision >= std::numeric_limits::digits + 3, + "internal error: not enough precision"); + + assert(std::isfinite(value)); + assert(value > 0); + + // If the neighbors (and boundaries) of 'value' are always computed for double-precision + // numbers, all float's can be recovered using strtod (and strtof). However, the resulting + // decimal representations are not exactly "short". + // + // The documentation for 'std::to_chars' (http://en.cppreference.com/w/cpp/utility/to_chars) + // says "value is converted to a string as if by std::sprintf in the default ("C") locale" + // and since sprintf promotes float's to double's, I think this is exactly what 'std::to_chars' + // does. + // On the other hand, the documentation for 'std::to_chars' requires that "parsing the + // representation using the corresponding std::from_chars function recovers value exactly". That + // indicates that single precision floating-point numbers should be recovered using + // 'std::strtof'. + // + // NB: If the neighbors are computed for single-precision numbers, there is a single float + // (7.0385307e-26f) which can't be recovered using strtod. The resulting double precision + // value is off by 1 ulp. +#if 0 + const boundaries w = compute_boundaries(static_cast(value)); +#else + const boundaries w = compute_boundaries(value); +#endif + + grisu2(buf, len, decimal_exponent, w.minus, w.w, w.plus); +} + +/*! +@brief appends a decimal representation of e to buf +@return a pointer to the element following the exponent. +@pre -1000 < e < 1000 +*/ +inline char* append_exponent(char* buf, int e) +{ + assert(e > -1000); + assert(e < 1000); + + if (e < 0) + { + e = -e; + *buf++ = '-'; + } + else + { + *buf++ = '+'; + } + + uint32_t k = static_cast(e); + if (k < 10) + { + // Always print at least two digits in the exponent. + // This is for compatibility with printf("%g"). + *buf++ = '0'; + *buf++ = static_cast('0' + k); + } + else if (k < 100) + { + *buf++ = static_cast('0' + k / 10); + k %= 10; + *buf++ = static_cast('0' + k); + } + else + { + *buf++ = static_cast('0' + k / 100); + k %= 100; + *buf++ = static_cast('0' + k / 10); + k %= 10; + *buf++ = static_cast('0' + k); + } + + return buf; +} + +/*! +@brief prettify v = buf * 10^decimal_exponent + +If v is in the range [10^min_exp, 10^max_exp) it will be printed in fixed-point +notation. Otherwise it will be printed in exponential notation. + +@pre min_exp < 0 +@pre max_exp > 0 +*/ +inline char* format_buffer(char* buf, int len, int decimal_exponent, + int min_exp, int max_exp) +{ + assert(min_exp < 0); + assert(max_exp > 0); + + const int k = len; + const int n = len + decimal_exponent; + + // v = buf * 10^(n-k) + // k is the length of the buffer (number of decimal digits) + // n is the position of the decimal point relative to the start of the buffer. + + if (k <= n and n <= max_exp) + { + // digits[000] + // len <= max_exp + 2 + + std::memset(buf + k, '0', static_cast(n - k)); + // Make it look like a floating-point number (#362, #378) + buf[n + 0] = '.'; + buf[n + 1] = '0'; + return buf + (n + 2); + } + + if (0 < n and n <= max_exp) + { + // dig.its + // len <= max_digits10 + 1 + + assert(k > n); + + std::memmove(buf + (n + 1), buf + n, static_cast(k - n)); + buf[n] = '.'; + return buf + (k + 1); + } + + if (min_exp < n and n <= 0) + { + // 0.[000]digits + // len <= 2 + (-min_exp - 1) + max_digits10 + + std::memmove(buf + (2 + -n), buf, static_cast(k)); + buf[0] = '0'; + buf[1] = '.'; + std::memset(buf + 2, '0', static_cast(-n)); + return buf + (2 + (-n) + k); + } + + if (k == 1) + { + // dE+123 + // len <= 1 + 5 + + buf += 1; + } + else + { + // d.igitsE+123 + // len <= max_digits10 + 1 + 5 + + std::memmove(buf + 2, buf + 1, static_cast(k - 1)); + buf[1] = '.'; + buf += 1 + k; + } + + *buf++ = 'e'; + return append_exponent(buf, n - 1); +} + +} // namespace dtoa_impl + +/*! +@brief generates a decimal representation of the floating-point number value in [first, last). + +The format of the resulting decimal representation is similar to printf's %g +format. Returns an iterator pointing past-the-end of the decimal representation. + +@note The input number must be finite, i.e. NaN's and Inf's are not supported. +@note The buffer must be large enough. +@note The result is NOT null-terminated. +*/ +template +char* to_chars(char* first, char* last, FloatType value) +{ + static_cast(last); // maybe unused - fix warning + assert(std::isfinite(value)); + + // Use signbit(value) instead of (value < 0) since signbit works for -0. + if (std::signbit(value)) + { + value = -value; + *first++ = '-'; + } + + if (value == 0) // +-0 + { + *first++ = '0'; + // Make it look like a floating-point number (#362, #378) + *first++ = '.'; + *first++ = '0'; + return first; + } + + assert(last - first >= std::numeric_limits::max_digits10); + + // Compute v = buffer * 10^decimal_exponent. + // The decimal digits are stored in the buffer, which needs to be interpreted + // as an unsigned decimal integer. + // len is the length of the buffer, i.e. the number of decimal digits. + int len = 0; + int decimal_exponent = 0; + dtoa_impl::grisu2(first, len, decimal_exponent, value); + + assert(len <= std::numeric_limits::max_digits10); + + // Format the buffer like printf("%.*g", prec, value) + constexpr int kMinExp = -4; + // Use digits10 here to increase compatibility with version 2. + constexpr int kMaxExp = std::numeric_limits::digits10; + + assert(last - first >= kMaxExp + 2); + assert(last - first >= 2 + (-kMinExp - 1) + std::numeric_limits::max_digits10); + assert(last - first >= std::numeric_limits::max_digits10 + 6); + + return dtoa_impl::format_buffer(first, len, decimal_exponent, kMinExp, kMaxExp); +} + +} // namespace + +void json::serializer::dump(const json& val, const bool pretty_print, + const bool ensure_ascii, const unsigned int indent_step, const unsigned int current_indent) { @@ -56,35 +1142,53 @@ void json::serializer::dump(const json& val, return; } + // we need to iterate over the object values in sorted key order + SmallVector, 64> sorted; + for (auto i = val.m_value.object->begin(), + end = val.m_value.object->end(); i != end; ++i) + { + sorted.push_back(i); + } + std::sort(sorted.begin(), sorted.end(), + [](const StringMapConstIterator& a, + const StringMapConstIterator& b) { + return a->getKey() < b->getKey(); + }); + if (pretty_print) { o << "{\n"; // variable to hold indentation for recursive calls const auto new_indent = current_indent + indent_step; + if (JSON_UNLIKELY(indent_string.size() < new_indent)) + { + indent_string.resize(indent_string.size() * 2, indent_char); + } // 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) + auto i = sorted.begin(); + for (std::size_t cnt = 0; cnt < sorted.size() - 1; ++cnt, ++i) { - o.indent(new_indent); + o.write(indent_string.c_str(), new_indent); o << '\"'; - dump_escaped(i->first()); + dump_escaped((*i)->first(), ensure_ascii); o << "\": "; - dump(i->second, true, indent_step, new_indent); + dump((*i)->second, true, ensure_ascii, indent_step, new_indent); o << ",\n"; } // last element - assert(i != val.m_value.object->end()); - o.indent(new_indent); + assert(i != sorted.end()); + //assert(std::next(i) == val.m_value.object->end()); + o.write(indent_string.c_str(), new_indent); o << '\"'; - dump_escaped(i->first()); + dump_escaped((*i)->first(), ensure_ascii); o << "\": "; - dump(i->second, true, indent_step, new_indent); + dump((*i)->second, true, ensure_ascii, indent_step, new_indent); o << '\n'; - o.indent(current_indent); + o.write(indent_string.c_str(), current_indent); o << '}'; } else @@ -92,22 +1196,23 @@ void json::serializer::dump(const json& val, 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) + auto i = sorted.begin(); + for (std::size_t cnt = 0; cnt < sorted.size() - 1; ++cnt, ++i) { o << '\"'; - dump_escaped(i->first()); + dump_escaped((*i)->first(), ensure_ascii); o << "\":"; - dump(i->second, false, indent_step, current_indent); + dump((*i)->second, false, ensure_ascii, indent_step, current_indent); o << ','; } // last element - assert(i != val.m_value.object->end()); + assert(i != sorted.end()); + //assert(std::next(i) == val.m_value.object->end()); o << '\"'; - dump_escaped(i->first()); + dump_escaped((*i)->first(), ensure_ascii); o << "\":"; - dump(i->second, false, indent_step, current_indent); + dump((*i)->second, false, ensure_ascii, indent_step, current_indent); o << '}'; } @@ -129,22 +1234,27 @@ void json::serializer::dump(const json& val, // variable to hold indentation for recursive calls const auto new_indent = current_indent + indent_step; + if (JSON_UNLIKELY(indent_string.size() < new_indent)) + { + indent_string.resize(indent_string.size() * 2, indent_char); + } // first n-1 elements - for (auto i = val.m_value.array->cbegin(); i != val.m_value.array->cend() - 1; ++i) + 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.write(indent_string.c_str(), new_indent); + dump(*i, true, ensure_ascii, 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); + assert(not val.m_value.array->empty()); + o.write(indent_string.c_str(), new_indent); + dump(val.m_value.array->back(), true, ensure_ascii, indent_step, new_indent); o << '\n'; - o.indent(current_indent); + o.write(indent_string.c_str(), current_indent); o << ']'; } else @@ -152,15 +1262,16 @@ void json::serializer::dump(const json& val, o << '['; // first n-1 elements - for (auto i = val.m_value.array->cbegin(); i != val.m_value.array->cend() - 1; ++i) + for (auto i = val.m_value.array->cbegin(); + i != val.m_value.array->cend() - 1; ++i) { - dump(*i, false, indent_step, current_indent); + dump(*i, false, ensure_ascii, indent_step, current_indent); o << ','; } // last element - assert(!val.m_value.array->empty()); - dump(val.m_value.array->back(), false, indent_step, current_indent); + assert(not val.m_value.array->empty()); + dump(val.m_value.array->back(), false, ensure_ascii, indent_step, current_indent); o << ']'; } @@ -171,7 +1282,7 @@ void json::serializer::dump(const json& val, case value_t::string: { o << '\"'; - dump_escaped(*val.m_value.string); + dump_escaped(*val.m_value.string, ensure_ascii); o << '\"'; return; } @@ -191,13 +1302,13 @@ void json::serializer::dump(const json& val, case value_t::number_integer: { - o << static_cast(val.m_value.number_integer); + dump_integer(val.m_value.number_integer); return; } case value_t::number_unsigned: { - o << static_cast(val.m_value.number_unsigned); + dump_integer(val.m_value.number_unsigned); return; } @@ -221,185 +1332,189 @@ void json::serializer::dump(const json& val, } } -void json::serializer::dump_escaped(StringRef s) const +void json::serializer::dump_escaped(StringRef s, const bool ensure_ascii) { - for (const auto& c : s) + uint32_t codepoint; + uint8_t state = UTF8_ACCEPT; + + for (std::size_t i = 0; i < s.size(); ++i) { - switch (c) + const auto byte = static_cast(s[i]); + + switch (decode(state, codepoint, byte)) { - // quotation mark (0x22) - case '"': + case UTF8_ACCEPT: // decode found a new code point { - o << '\\' << '"'; + switch (codepoint) + { + case 0x08: // backspace + { + o << '\\' << 'b'; + break; + } + + case 0x09: // horizontal tab + { + o << '\\' << 't'; + break; + } + + case 0x0A: // newline + { + o << '\\' << 'n'; + break; + } + + case 0x0C: // formfeed + { + o << '\\' << 'f'; + break; + } + + case 0x0D: // carriage return + { + o << '\\' << 'r'; + break; + } + + case 0x22: // quotation mark + { + o << '\\' << '\"'; + break; + } + + case 0x5C: // reverse solidus + { + o << '\\' << '\\'; + break; + } + + default: + { + // escape control characters (0x00..0x1F) or, if + // ensure_ascii parameter is used, non-ASCII characters + if ((codepoint <= 0x1F) or (ensure_ascii and (codepoint >= 0x7F))) + { + if (codepoint <= 0xFFFF) + { + o << '\\' << 'u' << format_hex_no_prefix(codepoint, 4); + } + else + { + o << '\\' << 'u' << format_hex_no_prefix(0xD7C0 + (codepoint >> 10), 4); + o << '\\' << 'u' << format_hex_no_prefix(0xDC00 + (codepoint & 0x3FF), 4); + } + } + else + { + // copy byte to buffer (all previous bytes + // been copied have in default case above) + o << s[i]; + } + break; + } + } break; } - // reverse solidus (0x5c) - case '\\': + case UTF8_REJECT: // decode found invalid UTF-8 byte { - // nothing to change - o << '\\' << '\\'; - break; + JSON_THROW(type_error::create(316, "invalid UTF-8 byte at index " + Twine(i) + ": 0x" + Twine::utohexstr(byte))); } - // backspace (0x08) - case '\b': + default: // decode found yet incomplete multi-byte code point { - 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 << hexdigit((c >> 4) & 0xf, true); - o << hexdigit((c >> 0) & 0xf, true); - break; - } - - default: - { - // all other characters are added as-is - o << c; + if (not ensure_ascii) + { + // code point will not be escaped - copy byte to buffer + o << s[i]; + } break; } } } + + if (JSON_UNLIKELY(state != UTF8_ACCEPT)) + { + // we finish reading, but do not accept: string was incomplete + JSON_THROW(type_error::create(316, "incomplete UTF-8 string; last byte: 0x" + Twine::utohexstr(static_cast(s.back())))); + } } void json::serializer::dump_float(double x) { // NaN / inf - if (!std::isfinite(x) || std::isnan(x)) + if (not std::isfinite(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; - } + // use the Grisu2 algorithm to produce short numbers which are + // guaranteed to round-trip, using strtof and strtod, resp. + char* begin = number_buffer.data(); + char* end = to_chars(begin, begin + number_buffer.size(), x); - // get number of digits for a text -> float -> text round-trip - static constexpr auto d = std::numeric_limits::digits10; - - // the actual conversion - 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(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(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"; - } + o.write(begin, static_cast(end - begin)); } -namespace wpi { +uint8_t json::serializer::decode(uint8_t& state, uint32_t& codep, const uint8_t byte) noexcept +{ + static const std::array utf8d = + { + { + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 00..1F + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 20..3F + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 40..5F + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 60..7F + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, // 80..9F + 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, // A0..BF + 8, 8, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, // C0..DF + 0xA, 0x3, 0x3, 0x3, 0x3, 0x3, 0x3, 0x3, 0x3, 0x3, 0x3, 0x3, 0x3, 0x4, 0x3, 0x3, // E0..EF + 0xB, 0x6, 0x6, 0x6, 0x5, 0x8, 0x8, 0x8, 0x8, 0x8, 0x8, 0x8, 0x8, 0x8, 0x8, 0x8, // F0..FF + 0x0, 0x1, 0x2, 0x3, 0x5, 0x8, 0x7, 0x1, 0x1, 0x1, 0x4, 0x6, 0x1, 0x1, 0x1, 0x1, // s0..s0 + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 1, 1, 1, 1, 0, 1, 0, 1, 1, 1, 1, 1, 1, // s1..s2 + 1, 2, 1, 1, 1, 1, 1, 2, 1, 2, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 1, 1, 1, 1, 1, 1, 1, 1, // s3..s4 + 1, 2, 1, 1, 1, 1, 1, 1, 1, 2, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 3, 1, 3, 1, 1, 1, 1, 1, 1, // s5..s6 + 1, 3, 1, 1, 1, 1, 1, 3, 1, 3, 1, 1, 1, 1, 1, 1, 1, 3, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 // s7..s8 + } + }; + + const uint8_t type = utf8d[byte]; + + codep = (state != UTF8_ACCEPT) + ? (byte & 0x3fu) | (codep << 6) + : static_cast(0xff >> type) & (byte); + + state = utf8d[256u + state * 16u + type]; + return state; +} + +std::string json::dump(const int indent, const char indent_char, + const bool ensure_ascii) const +{ + std::string result; + raw_string_ostream os(result); + dump(os, indent, indent_char, ensure_ascii); + return result; +} + +void json::dump(raw_ostream& os, int indent, const char indent_char, + const bool ensure_ascii) const +{ + serializer s(os, indent_char); + + if (indent >= 0) + { + s.dump(*this, true, ensure_ascii, static_cast(indent)); + } + else + { + s.dump(*this, false, ensure_ascii, 0); + } + + os.flush(); +} raw_ostream& operator<<(raw_ostream& o, const json& j) { @@ -407,27 +1522,11 @@ raw_ostream& operator<<(raw_ostream& o, const json& j) return o; } +std::ostream& operator<<(std::ostream& o, const json& j) +{ + raw_os_ostream os(o); + j.dump(os, 0); + return o; +} + } // namespace wpi - -std::string json::dump(int indent) const -{ - std::string s; - raw_string_ostream os(s); - dump(os, indent); - os.flush(); - return s; -} - -void json::dump(raw_ostream& os, int indent) const -{ - serializer s(os); - - if (indent >= 0) - { - s.dump(*this, true, static_cast(indent)); - } - else - { - s.dump(*this, false, 0); - } -} diff --git a/wpiutil/src/main/native/cpp/json_serializer.h b/wpiutil/src/main/native/cpp/json_serializer.h index b596f0145f..a9b36d2b49 100644 --- a/wpiutil/src/main/native/cpp/json_serializer.h +++ b/wpiutil/src/main/native/cpp/json_serializer.h @@ -1,5 +1,5 @@ /*----------------------------------------------------------------------------*/ -/* Modifications Copyright (c) FIRST 2017. All Rights Reserved. */ +/* Modifications Copyright (c) 2017-2018 FIRST. All Rights Reserved. */ /* Open Source Software - may be modified and shared by FRC teams. The code */ /* must be accompanied by the FIRST BSD license file in the root directory of */ /* the project. */ @@ -7,11 +7,11 @@ /* __ _____ _____ _____ __| | __| | | | JSON for Modern C++ -| | |__ | | | | | | version 2.1.1 +| | |__ | | | | | | version 3.1.2 |_____|_____|_____|_|___| https://github.com/nlohmann/json Licensed under the MIT License . -Copyright (c) 2013-2017 Niels Lohmann . +Copyright (c) 2013-2018 Niels Lohmann . Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal @@ -34,38 +34,41 @@ SOFTWARE. #include "wpi/json.h" #include // lconv, localeconv +#include // labs, isfinite, isnan, signbit, ldexp #include // locale #include "wpi/raw_ostream.h" namespace wpi { -/*! -@brief wrapper around the serialization functions -*/ class json::serializer { - public: - serializer(const serializer&) = delete; - serializer& operator=(const serializer&) = delete; + static constexpr uint8_t UTF8_ACCEPT = 0; + static constexpr uint8_t UTF8_REJECT = 1; + public: /*! @param[in] s output stream to serialize to @param[in] ichar indentation character to use */ - explicit serializer(raw_ostream& s) + serializer(raw_ostream& s, const char ichar) : 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]) + thousands_sep(loc->thousands_sep == nullptr ? '\0' : * (loc->thousands_sep)), + decimal_point(loc->decimal_point == nullptr ? '\0' : * (loc->decimal_point)), + indent_char(ichar), indent_string(512, indent_char) {} + // delete because of pointer members + serializer(const serializer&) = delete; + serializer& operator=(const serializer&) = delete; + /*! @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. + 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<<` @@ -76,45 +79,128 @@ class json::serializer @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, + void dump(const json& val, const bool pretty_print, + const bool ensure_ascii, const unsigned int indent_step, const unsigned int current_indent = 0); + private: /*! @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 + 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 + @param[in] ensure_ascii whether to escape non-ASCII characters with + \uXXXX sequences @complexity Linear in the length of string @a s. */ - void dump_escaped(StringRef s) const; + void dump_escaped(StringRef s, const bool ensure_ascii); + + /*! + @brief dump an integer + + Dump a given integer to output stream @a o. Works internally with + @a number_buffer. + + @param[in] x integer number (signed or unsigned) to dump + @tparam NumberType either @a int64_t or @a uint64_t + */ + template::value or + std::is_same::value, + int> = 0> + void dump_integer(NumberType x) + { + // special case for "0" + if (x == 0) + { + o << '0'; + return; + } + + const bool is_negative = (x <= 0) and (x != 0); // see issue #755 + std::size_t i = 0; + + while (x != 0) + { + // spare 1 byte for '\0' + assert(i < number_buffer.size() - 1); + + const auto digit = std::labs(static_cast(x % 10)); + number_buffer[i++] = static_cast('0' + digit); + x /= 10; + } + + if (is_negative) + { + // make sure there is capacity for the '-' + assert(i < number_buffer.size() - 2); + number_buffer[i++] = '-'; + } + + std::reverse(number_buffer.begin(), number_buffer.begin() + i); + o.write(number_buffer.data(), i); + } /*! @brief dump a floating-point number - Dump a given floating-point number to output stream @a o. Works - internally with @a number_buffer. + 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); + /*! + @brief check whether a string is UTF-8 encoded + + The function checks each byte of a string whether it is UTF-8 encoded. The + result of the check is stored in the @a state parameter. The function must + be called initially with state 0 (accept). State 1 means the string must + be rejected, because the current byte is not allowed. If the string is + completely processed, but the state is non-zero, the string ended + prematurely; that is, the last byte indicated more bytes should have + followed. + + @param[in,out] state the state of the decoding + @param[in,out] codep codepoint (valid only if resulting state is UTF8_ACCEPT) + @param[in] byte next byte to decode + @return new state + + @note The function has been edited: a std::array is used. + + @copyright Copyright (c) 2008-2009 Bjoern Hoehrmann + @sa http://bjoern.hoehrmann.de/utf-8/decoder/dfa/ + */ + static uint8_t decode(uint8_t& state, uint32_t& codep, const uint8_t byte) noexcept; + private: /// the output of the serializer raw_ostream& o; + /// a (hopefully) large enough character buffer + std::array number_buffer{{}}; + /// 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'; + + /// string buffer + std::array string_buffer{{}}; + + /// the indentation character + const char indent_char; + /// the indentation string + std::string indent_string; }; } // namespace wpi diff --git a/wpiutil/src/main/native/include/wpi/json.h b/wpiutil/src/main/native/include/wpi/json.h index 5ed6cf24f6..8e2362971c 100644 --- a/wpiutil/src/main/native/include/wpi/json.h +++ b/wpiutil/src/main/native/include/wpi/json.h @@ -1,5 +1,5 @@ /*----------------------------------------------------------------------------*/ -/* Modifications Copyright (c) FIRST 2017. All Rights Reserved. */ +/* Modifications Copyright (c) 2017-2018 FIRST. All Rights Reserved. */ /* Open Source Software - may be modified and shared by FRC teams. The code */ /* must be accompanied by the FIRST BSD license file in the root directory of */ /* the project. */ @@ -7,11 +7,11 @@ /* __ _____ _____ _____ __| | __| | | | JSON for Modern C++ -| | |__ | | | | | | version 2.1.1 +| | |__ | | | | | | version 3.1.2 |_____|_____|_____|_|___| https://github.com/nlohmann/json Licensed under the MIT License . -Copyright (c) 2013-2017 Niels Lohmann . +Copyright (c) 2013-2018 Niels Lohmann . Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal @@ -32,37 +32,87 @@ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ -#ifndef WPI_SUPPORT_JSON_H_ -#define WPI_SUPPORT_JSON_H_ +#ifndef WPIUTIL_JSON_H +#define WPIUTIL_JSON_H +#define NLOHMANN_JSON_VERSION_MAJOR 3 +#define NLOHMANN_JSON_VERSION_MINOR 1 +#define NLOHMANN_JSON_VERSION_PATCH 2 + + +#include // all_of, copy, find, for_each, generate_n, min, reverse, remove, fill, none_of, transform +#include // array #include // assert -#include // isfinite, labs, ldexp, signbit +#include // and, not, or #include // nullptr_t, ptrdiff_t, size_t -#include // int64_t, uint64_t -#include // abort, strtod, strtof, strtold, strtoul, strtoll, strtoull -#include // memcpy, strlen +#include // uint8_t, uint16_t, uint32_t, uint64_t +#include // exception #include // function, hash, less #include // initializer_list -#include // advance, begin, back_inserter, bidirectional_iterator_tag, distance, end, inserter, iterator, iterator_traits, next, random_access_iterator_tag +#include #include // numeric_limits -#include // addressof, allocator, allocator_traits, unique_ptr -#include // getline, stoi, string, to_string -#include // add_pointer, conditional, decay, enable_if, false_type, integral_constant, is_arithmetic, is_base_of, is_const, is_constructible, is_convertible, is_default_constructible, is_enum, is_floating_point, is_integral, is_nothrow_move_assignable, is_nothrow_move_constructible, is_pointer, is_reference, is_same, is_scalar, is_signed, remove_const, remove_cv, remove_pointer, remove_reference, true_type, underlying_type -#include // declval, forward, make_pair, move, pair, swap +#include // allocator, shared_ptr, make_shared, addressof +#include // runtime_error +#include // string, char_traits, stoi, to_string +#include // tuple, get, make_tuple +#include +#include #include // vector #include "wpi/ArrayRef.h" -#include "wpi/raw_ostream.h" #include "wpi/StringMap.h" #include "wpi/StringRef.h" +#include "wpi/Twine.h" + +namespace wpi +{ + +class raw_istream; +class raw_ostream; + +class JsonTest; + +/*! +@brief default JSONSerializer template argument + +This serializer ignores the template arguments and uses ADL +([argument-dependent lookup](http://en.cppreference.com/w/cpp/language/adl)) +for serialization. +*/ +template +struct adl_serializer; + +/*! +@brief JSON Pointer + +A JSON pointer defines a string syntax for identifying a specific value +within a JSON document. It can be used with functions `at` and +`operator[]`. Furthermore, JSON pointers are the base for JSON patches. + +@sa [RFC 6901](https://tools.ietf.org/html/rfc6901) + +@since version 2.0.0 +*/ +class json_pointer; + +/*! +@brief default JSON class + +This type is the default specialization of the @ref json class which +uses the standard template types. + +@since version 1.0.0 +*/ +class json; +} // exclude unsupported compilers #if defined(__clang__) #if (__clang_major__ * 10000 + __clang_minor__ * 100 + __clang_patchlevel__) < 30400 #error "unsupported Clang version - see https://github.com/nlohmann/json#supported-compilers" #endif -#elif defined(__GNUC__) - #if (__GNUC__ * 10000 + __GNUC_MINOR__ * 100 + __GNUC_PATCHLEVEL__) < 40800 +#elif defined(__GNUC__) && !(defined(__ICC) || defined(__INTEL_COMPILER)) + #if (__GNUC__ * 10000 + __GNUC_MINOR__ * 100 + __GNUC_PATCHLEVEL__) < 40900 #error "unsupported GCC version - see https://github.com/nlohmann/json#supported-compilers" #endif #endif @@ -71,14 +121,12 @@ SOFTWARE. #if defined(__clang__) || defined(__GNUC__) || defined(__GNUG__) #pragma GCC diagnostic push #pragma GCC diagnostic ignored "-Wfloat-equal" - #pragma GCC diagnostic ignored "-Wtype-limits" #endif // disable documentation warnings on clang #if defined(__clang__) #pragma GCC diagnostic push #pragma GCC diagnostic ignored "-Wdocumentation" - #pragma GCC diagnostic ignored "-Wtautological-constant-out-of-range-compare" #endif // allow to disable exceptions @@ -92,6 +140,20 @@ SOFTWARE. #define JSON_CATCH(exception) if(false) #endif +// override exception macros +#if defined(JSON_THROW_USER) + #undef JSON_THROW + #define JSON_THROW JSON_THROW_USER +#endif +#if defined(JSON_TRY_USER) + #undef JSON_TRY + #define JSON_TRY JSON_TRY_USER +#endif +#if defined(JSON_CATCH_USER) + #undef JSON_CATCH + #define JSON_CATCH JSON_CATCH_USER +#endif + // manual branch prediction #if defined(__clang__) || defined(__GNUC__) || defined(__GNUG__) #define JSON_LIKELY(x) __builtin_expect(!!(x), 1) @@ -101,21 +163,280 @@ SOFTWARE. #define JSON_UNLIKELY(x) x #endif -namespace wpi -{ - -class raw_istream; +// C++ language standard detection +#if (defined(__cplusplus) && __cplusplus >= 201703L) || (defined(_HAS_CXX17) && _HAS_CXX17 == 1) // fix for issue #464 + #define JSON_HAS_CPP_17 + #define JSON_HAS_CPP_14 +#elif (defined(__cplusplus) && __cplusplus >= 201402L) || (defined(_HAS_CXX14) && _HAS_CXX14 == 1) + #define JSON_HAS_CPP_14 +#endif /*! -@brief unnamed namespace with internal helper functions +@brief Helper to determine whether there's a key_type for T. -This namespace collects some functions that could not be defined inside the -@ref json class. +This helper is used to tell associative containers apart from other containers +such as sequence containers. For instance, `std::map` passes the test as it +contains a `mapped_type`, whereas `std::vector` fails the test. + +@sa http://stackoverflow.com/a/7728728/266378 +@since version 1.0.0, overworked in version 2.0.6 +*/ +#define NLOHMANN_JSON_HAS_HELPER(type) \ + template struct has_##type { \ + private: \ + template \ + static int detect(U &&); \ + static void detect(...); \ + public: \ + static constexpr bool value = \ + std::is_integral()))>::value; \ + } + +namespace wpi +{ +/*! +@brief detail namespace with internal helper functions + +This namespace collects functions that should not be exposed, +implementations of some @ref json methods, and meta-programming helpers. @since version 2.1.0 */ namespace detail { +///////////// +// helpers // +///////////// + +template struct is_json : std::false_type {}; + +template<> struct is_json : std::true_type {}; + +// alias templates to reduce boilerplate +template +using enable_if_t = typename std::enable_if::type; + +template +using uncvref_t = typename std::remove_cv::type>::type; + +// implementation of C++14 index_sequence and affiliates +// source: https://stackoverflow.com/a/32223343 +template +struct index_sequence +{ + using type = index_sequence; + using value_type = std::size_t; + static constexpr std::size_t size() noexcept + { + return sizeof...(Ints); + } +}; + +template +struct merge_and_renumber; + +template +struct merge_and_renumber, index_sequence> + : index_sequence < I1..., (sizeof...(I1) + I2)... > {}; + +template +struct make_index_sequence + : merge_and_renumber < typename make_index_sequence < N / 2 >::type, + typename make_index_sequence < N - N / 2 >::type > {}; + +template<> struct make_index_sequence<0> : index_sequence<> {}; +template<> struct make_index_sequence<1> : index_sequence<0> {}; + +template +using index_sequence_for = make_index_sequence; + +/* +Implementation of two C++17 constructs: conjunction, negation. This is needed +to avoid evaluating all the traits in a condition + +For example: not std::is_same::value and has_value_type::value +will not compile when T = void (on MSVC at least). Whereas +conjunction>, has_value_type>::value will +stop evaluating if negation<...>::value == false + +Please note that those constructs must be used with caution, since symbols can +become very long quickly (which can slow down compilation and cause MSVC +internal compiler errors). Only use it when you have to (see example ahead). +*/ +template struct conjunction : std::true_type {}; +template struct conjunction : B1 {}; +template +struct conjunction : std::conditional, B1>::type {}; + +template struct negation : std::integral_constant {}; + +// dispatch utility (taken from ranges-v3) +template struct priority_tag : priority_tag < N - 1 > {}; +template<> struct priority_tag<0> {}; + +//////////////////////// +// has_/is_ functions // +//////////////////////// + +// source: https://stackoverflow.com/a/37193089/4116453 + +template +struct is_complete_type : std::false_type {}; + +template +struct is_complete_type : std::true_type {}; + +NLOHMANN_JSON_HAS_HELPER(mapped_type); +NLOHMANN_JSON_HAS_HELPER(key_type); +NLOHMANN_JSON_HAS_HELPER(value_type); +NLOHMANN_JSON_HAS_HELPER(iterator); + +template +struct is_compatible_object_type_impl : std::false_type {}; + +template +struct is_compatible_object_type_impl +{ + static constexpr auto value = + std::is_constructible::value and + std::is_constructible::value; +}; + +template +struct is_compatible_object_type +{ + static auto constexpr value = is_compatible_object_type_impl < + conjunction>, + has_mapped_type, + has_key_type>::value, + typename BasicJsonType::object_t, CompatibleObjectType >::value; +}; + +template +struct is_json_nested_type +{ + static auto constexpr value = std::is_same::value or + std::is_same::value or + std::is_same::value or + std::is_same::value; +}; + +template +struct is_compatible_array_type +{ + static auto constexpr value = + conjunction>, + negation>, + negation>, + negation>, + has_value_type, + has_iterator>::value; +}; + +template +struct is_compatible_integer_type_impl : std::false_type {}; + +template +struct is_compatible_integer_type_impl +{ + // is there an assert somewhere on overflows? + using RealLimits = std::numeric_limits; + using CompatibleLimits = std::numeric_limits; + + static constexpr auto value = + std::is_constructible::value and + CompatibleLimits::is_integer and + RealLimits::is_signed == CompatibleLimits::is_signed; +}; + +template +struct is_compatible_integer_type +{ + static constexpr auto value = + is_compatible_integer_type_impl < + std::is_integral::value and + not std::is_same::value, + RealIntegerType, CompatibleNumberIntegerType > ::value; +}; + +// trait checking if JSONSerializer::from_json(json const&, udt&) exists +template +struct has_from_json +{ + private: + // also check the return type of from_json + template::from_json( + std::declval(), std::declval()))>::value>> + static int detect(U&&); + static void detect(...); + + public: + static constexpr bool value = std::is_integral>()))>::value; +}; + +// This trait checks if JSONSerializer::from_json(json const&) exists +// this overload is used for non-default-constructible user-defined-types +template +struct has_non_default_from_json +{ + private: + template < + typename U, + typename = enable_if_t::from_json(std::declval()))>::value >> + static int detect(U&&); + static void detect(...); + + public: + static constexpr bool value = std::is_integral>()))>::value; +}; + +// This trait checks if BasicJsonType::json_serializer::to_json exists +template +struct has_to_json +{ + private: + template::to_json( + std::declval(), std::declval()))> + static int detect(U&&); + static void detect(...); + + public: + static constexpr bool value = std::is_integral>()))>::value; +}; + +template +struct is_compatible_complete_type +{ + static constexpr bool value = + not std::is_base_of::value and + not is_json::value and + not is_json_nested_type::value and + has_to_json::value; +}; + +template +struct is_compatible_type + : conjunction, + is_compatible_complete_type> +{ +}; + +// taken from ranges-v3 +template +struct static_const +{ + static constexpr T value{}; +}; + +template +constexpr T static_const::value; + //////////////// // exceptions // //////////////// @@ -123,12 +444,28 @@ namespace detail /*! @brief general exception of the @ref json class -Extension of std::exception objects with a member @a id for exception ids. +This class is an extension of `std::exception` objects with a member @a id for +exception ids. It is used as the base class for all exceptions thrown by the +@ref json class. This class can hence be used as "wildcard" to catch +exceptions. +Subclasses: +- @ref parse_error for exceptions indicating a parse error +- @ref invalid_iterator for exceptions indicating errors with iterators +- @ref type_error for exceptions indicating executing a member function with + a wrong type +- @ref out_of_range for exceptions indicating access out of the defined range +- @ref other_error for exceptions indicating other library errors + +@internal @note To have nothrow-copy-constructible exceptions, we internally use - std::runtime_error which can cope with arbitrary-length error messages. + `std::runtime_error` which can cope with arbitrary-length error messages. Intermediate strings are built with static functions and then passed to the actual constructor. +@endinternal + +@liveexample{The following code shows how arbitrary library exceptions can be +caught.,exception} @since version 3.0.0 */ @@ -136,7 +473,7 @@ class exception : public std::exception { public: /// returns the explanatory string - virtual const char* what() const noexcept override + const char* what() const noexcept override { return m.what(); } @@ -145,11 +482,7 @@ class exception : public std::exception const int id; protected: - exception(int id_, const char* what_arg) - : id(id_), m(what_arg) - {} - - static std::string name(const std::string& ename, int id); + exception(int id_, const Twine& what_arg); private: /// an exception object as storage for error messages @@ -159,36 +492,44 @@ class exception : public std::exception /*! @brief exception indicating a parse error -This excpetion is thrown by the library when a parse error occurs. Parse -errors can occur during the deserialization of JSON text as well as when -using JSON Patch. +This exception is thrown by the library when a parse error occurs. Parse errors +can occur during the deserialization of JSON text, CBOR, MessagePack, as well +as when using JSON Patch. Member @a byte holds the byte index of the last read character in the input file. -@note For an input with n bytes, 1 is the index of the first character - and n+1 is the index of the terminating null byte or the end of - file. This also holds true when reading a byte vector (CBOR or - MessagePack). - Exceptions have ids 1xx. -name / id | example massage | description +name / id | example message | description ------------------------------ | --------------- | ------------------------- json.exception.parse_error.101 | parse error at 2: unexpected end of input; expected string literal | This error indicates a syntax error while deserializing a JSON text. The error message describes that an unexpected token (character) was encountered, and the member @a byte indicates the error position. json.exception.parse_error.102 | parse error at 14: missing or wrong low surrogate | JSON uses the `\uxxxx` format to describe Unicode characters. Code points above above 0xFFFF are split into two `\uxxxx` entries ("surrogate pairs"). This error indicates that the surrogate pair is incomplete or contains an invalid code point. json.exception.parse_error.103 | parse error: code points above 0x10FFFF are invalid | Unicode supports code points up to 0x10FFFF. Code points above 0x10FFFF are invalid. json.exception.parse_error.104 | parse error: JSON patch must be an array of objects | [RFC 6902](https://tools.ietf.org/html/rfc6902) requires a JSON Patch document to be a JSON document that represents an array of objects. json.exception.parse_error.105 | parse error: operation must have string member 'op' | An operation of a JSON Patch document must contain exactly one "op" member, whose value indicates the operation to perform. Its value must be one of "add", "remove", "replace", "move", "copy", or "test"; other values are errors. -json.exception.parse_error.106 | parse error: array index '01' must not begin with '0' | An array index in a JSON Pointer ([RFC 6901](https://tools.ietf.org/html/rfc6901)) may be `0` or any number wihtout a leading `0`. +json.exception.parse_error.106 | parse error: array index '01' must not begin with '0' | An array index in a JSON Pointer ([RFC 6901](https://tools.ietf.org/html/rfc6901)) may be `0` or any number without a leading `0`. json.exception.parse_error.107 | parse error: JSON pointer must be empty or begin with '/' - was: 'foo' | A JSON Pointer must be a Unicode string containing a sequence of zero or more reference tokens, each prefixed by a `/` character. json.exception.parse_error.108 | parse error: escape character '~' must be followed with '0' or '1' | In a JSON Pointer, only `~0` and `~1` are valid escape sequences. json.exception.parse_error.109 | parse error: array index 'one' is not a number | A JSON Pointer array index must be a number. json.exception.parse_error.110 | parse error at 1: cannot read 2 bytes from vector | When parsing CBOR or MessagePack, the byte vector ends before the complete value has been read. -json.exception.parse_error.111 | parse error: bad input stream | Parsing CBOR or MessagePack from an input stream where the [`badbit` or `failbit`](http://en.cppreference.com/w/cpp/io/ios_base/iostate) is set. -json.exception.parse_error.112 | parse error at 1: error reading CBOR; last byte: 0xf8 | Not all types of CBOR or MessagePack are supported. This exception occurs if an unsupported byte was read. +json.exception.parse_error.112 | parse error at 1: error reading CBOR; last byte: 0xF8 | Not all types of CBOR or MessagePack are supported. This exception occurs if an unsupported byte was read. json.exception.parse_error.113 | parse error at 2: expected a CBOR string; last byte: 0x98 | While parsing a map key, a value that is not a string has been read. +@note For an input with n bytes, 1 is the index of the first character and n+1 + is the index of the terminating null byte or the end of file. This also + holds true when reading a byte vector (CBOR or MessagePack). + +@liveexample{The following code shows how a `parse_error` exception can be +caught.,parse_error} + +@sa @ref exception for the base class of the library exceptions +@sa @ref invalid_iterator for exceptions indicating errors with iterators +@sa @ref type_error for exceptions indicating executing a member function with + a wrong type +@sa @ref out_of_range for exceptions indicating access out of the defined range +@sa @ref other_error for exceptions indicating other library errors + @since version 3.0.0 */ class parse_error : public exception @@ -196,38 +537,39 @@ class parse_error : public exception public: /*! @brief create a parse error exception - @param[in] id the id of the exception - @param[in] byte_ the byte index where the error occured (or 0 if - the position cannot be determined) - @param[in] what_arg the explanatory string + @param[in] id_ the id of the exception + @param[in] byte_ the byte index where the error occurred (or 0 if the + position cannot be determined) + @param[in] what_arg the explanatory string @return parse_error object */ - static parse_error create(int id, size_t byte_, const std::string& what_arg); + static parse_error create(int id_, std::size_t byte_, const Twine& what_arg); /*! @brief byte index of the parse error The byte index of the last read character in the input file. - @note For an input with n bytes, 1 is the index of the first character - and n+1 is the index of the terminating null byte or the end of - file. This also holds true when reading a byte vector (CBOR or - MessagePack). + @note For an input with n bytes, 1 is the index of the first character and + n+1 is the index of the terminating null byte or the end of file. + This also holds true when reading a byte vector (CBOR or MessagePack). */ - const size_t byte; + const std::size_t byte; private: - parse_error(int id_, size_t byte_, const char* what_arg) - : exception(id_, what_arg), byte(byte_) - {} + parse_error(int id_, std::size_t byte_, const Twine& what_arg) + : exception(id_, what_arg), byte(byte_) {} }; /*! @brief exception indicating errors with iterators +This exception is thrown if iterators passed to a library function do not match +the expected semantics. + Exceptions have ids 2xx. -name / id | example massage | description +name / id | example message | description ----------------------------------- | --------------- | ------------------------- json.exception.invalid_iterator.201 | iterators are not compatible | The iterators passed to constructor @ref json(InputIT first, InputIT last) are not compatible, meaning they do not belong to the same container. Therefore, the range (@a first, @a last) is invalid. json.exception.invalid_iterator.202 | iterator does not fit current value | In an erase or insert function, the passed iterator @a pos does not belong to the JSON value for which the function was called. It hence does not define a valid position for the deletion/insertion. @@ -241,25 +583,37 @@ json.exception.invalid_iterator.209 | cannot use offsets with object iterators | json.exception.invalid_iterator.210 | iterators do not fit | The iterator range passed to the insert function are not compatible, meaning they do not belong to the same container. Therefore, the range (@a first, @a last) is invalid. json.exception.invalid_iterator.211 | passed iterators may not belong to container | The iterator range passed to the insert function must not be a subrange of the container to insert to. json.exception.invalid_iterator.212 | cannot compare iterators of different containers | When two iterators are compared, they must belong to the same container. -json.exception.invalid_iterator.213 | cannot compare order of object iterators | The order of object iterators cannot be compated, because JSON objects are unordered. +json.exception.invalid_iterator.213 | cannot compare order of object iterators | The order of object iterators cannot be compared, because JSON objects are unordered. json.exception.invalid_iterator.214 | cannot get value | Cannot get value for iterator: Either the iterator belongs to a null value or it is an iterator to a primitive type (number, boolean, or string), but the iterator is different to @ref begin(). +@liveexample{The following code shows how an `invalid_iterator` exception can be +caught.,invalid_iterator} + +@sa @ref exception for the base class of the library exceptions +@sa @ref parse_error for exceptions indicating a parse error +@sa @ref type_error for exceptions indicating executing a member function with + a wrong type +@sa @ref out_of_range for exceptions indicating access out of the defined range +@sa @ref other_error for exceptions indicating other library errors + @since version 3.0.0 */ class invalid_iterator : public exception { public: - static invalid_iterator create(int id, const std::string& what_arg); + static invalid_iterator create(int id_, const Twine& what_arg); private: - invalid_iterator(int id_, const char* what_arg) - : exception(id_, what_arg) - {} + invalid_iterator(int id_, const Twine& what_arg) + : exception(id_, what_arg) {} }; /*! @brief exception indicating executing a member function with a wrong type +This exception is thrown in case of a type error; that is, a library function is +executed on a JSON value whose type does not match the expected semantics. + Exceptions have ids 3xx. name / id | example message | description @@ -275,26 +629,39 @@ json.exception.type_error.308 | cannot use push_back() with string | The @ref pu json.exception.type_error.309 | cannot use insert() with | The @ref insert() member functions can only be executed for certain JSON types. json.exception.type_error.310 | cannot use swap() with number | The @ref swap() member functions can only be executed for certain JSON types. json.exception.type_error.311 | cannot use emplace_back() with string | The @ref emplace_back() member function can only be executed for certain JSON types. +json.exception.type_error.312 | cannot use update() with string | The @ref update() member functions can only be executed for certain JSON types. json.exception.type_error.313 | invalid value to unflatten | The @ref unflatten function converts an object whose keys are JSON Pointers back into an arbitrary nested JSON value. The JSON Pointers must not overlap, because then the resulting value would not be well defined. json.exception.type_error.314 | only objects can be unflattened | The @ref unflatten function only works for an object whose keys are JSON Pointers. json.exception.type_error.315 | values in object must be primitive | The @ref unflatten function only works for an object whose keys are JSON Pointers and whose values are primitive. +json.exception.type_error.316 | invalid UTF-8 byte at index 10: 0x7E | The @ref dump function only works with UTF-8 encoded strings; that is, if you assign a `std::string` to a JSON value, make sure it is UTF-8 encoded. | + +@liveexample{The following code shows how a `type_error` exception can be +caught.,type_error} + +@sa @ref exception for the base class of the library exceptions +@sa @ref parse_error for exceptions indicating a parse error +@sa @ref invalid_iterator for exceptions indicating errors with iterators +@sa @ref out_of_range for exceptions indicating access out of the defined range +@sa @ref other_error for exceptions indicating other library errors @since version 3.0.0 */ class type_error : public exception { public: - static type_error create(int id, const std::string& what_arg); + static type_error create(int id_, const Twine& what_arg); private: - type_error(int id_, const char* what_arg) - : exception(id_, what_arg) - {} + type_error(int id_, const Twine& what_arg) : exception(id_, what_arg) {} }; /*! @brief exception indicating access out of the defined range +This exception is thrown in case a library function is called on an input +parameter that exceeds the expected range, for instance in case of array +indices or nonexisting object keys. + Exceptions have ids 4xx. name / id | example message | description @@ -305,45 +672,63 @@ json.exception.out_of_range.403 | key 'foo' not found | The provided key was not json.exception.out_of_range.404 | unresolved reference token 'foo' | A reference token in a JSON Pointer could not be resolved. json.exception.out_of_range.405 | JSON pointer has no parent | The JSON Patch operations 'remove' and 'add' can not be applied to the root element of the JSON value. json.exception.out_of_range.406 | number overflow parsing '10E1000' | A parsed number could not be stored as without changing it to NaN or INF. +json.exception.out_of_range.407 | number overflow serializing '9223372036854775808' | UBJSON only supports integers numbers up to 9223372036854775807. | +json.exception.out_of_range.408 | excessive array size: 8658170730974374167 | The size (following `#`) of an UBJSON array or object exceeds the maximal capacity. | + +@liveexample{The following code shows how an `out_of_range` exception can be +caught.,out_of_range} + +@sa @ref exception for the base class of the library exceptions +@sa @ref parse_error for exceptions indicating a parse error +@sa @ref invalid_iterator for exceptions indicating errors with iterators +@sa @ref type_error for exceptions indicating executing a member function with + a wrong type +@sa @ref other_error for exceptions indicating other library errors @since version 3.0.0 */ class out_of_range : public exception { public: - static out_of_range create(int id, const std::string& what_arg); + static out_of_range create(int id_, const Twine& what_arg); private: - out_of_range(int id_, const char* what_arg) - : exception(id_, what_arg) - {} + out_of_range(int id_, const Twine& what_arg) : exception(id_, what_arg) {} }; /*! -@brief exception indicating other errors +@brief exception indicating other library errors + +This exception is thrown in case of errors that cannot be classified with the +other exception types. Exceptions have ids 5xx. name / id | example message | description ------------------------------ | --------------- | ------------------------- json.exception.other_error.501 | unsuccessful: {"op":"test","path":"/baz", "value":"bar"} | A JSON Patch operation 'test' failed. The unsuccessful operation is also printed. -json.exception.other_error.502 | invalid object size for conversion | Some conversions to user-defined types impose constraints on the object size (e.g. std::pair) + +@sa @ref exception for the base class of the library exceptions +@sa @ref parse_error for exceptions indicating a parse error +@sa @ref invalid_iterator for exceptions indicating errors with iterators +@sa @ref type_error for exceptions indicating executing a member function with + a wrong type +@sa @ref out_of_range for exceptions indicating access out of the defined range + +@liveexample{The following code shows how an `other_error` exception can be +caught.,other_error} @since version 3.0.0 */ class other_error : public exception { public: - static other_error create(int id, const std::string& what_arg); + static other_error create(int id_, const Twine& what_arg); private: - other_error(int id_, const char* what_arg) - : exception(id_, what_arg) - {} + other_error(int id_, const Twine& what_arg) : exception(id_, what_arg) {} }; - - /////////////////////////// // JSON type enumeration // /////////////////////////// @@ -362,8 +747,8 @@ distinguish the stored values, and the functions @ref json::is_null(), @note There are three enumeration entries (number_integer, number_unsigned, and number_float), because the library distinguishes these three types for numbers: -std::uint64_t is used for unsigned integers, -std::int64_t is used for signed integers, and +uint64_t is used for unsigned integers, +int64_t is used for signed integers, and double is used for floating-point numbers or to approximate integers which do not fit in the limits of their respective type. @@ -372,17 +757,17 @@ value with the default value for a given type @since version 1.0.0 */ -enum class value_t : uint8_t +enum class value_t : std::uint8_t { - null, ///< null value - object, ///< object (unordered set of name/value pairs) - array, ///< array (ordered collection of values) - string, ///< string value - boolean, ///< boolean value - number_integer, ///< number value (signed integer) - number_unsigned, ///< number value (unsigned integer) - number_float, ///< number value (floating-point) - discarded ///< discarded by the the parser callback function + null, ///< null value + object, ///< object (unordered set of name/value pairs) + array, ///< array (ordered collection of values) + string, ///< string value + boolean, ///< boolean value + number_integer, ///< number value (signed integer) + number_unsigned, ///< number value (unsigned integer) + number_float, ///< number value (floating-point) + discarded ///< discarded by the the parser callback function }; /*! @@ -391,69 +776,320 @@ enum class value_t : uint8_t Returns an ordering that is similar to Python: - order: null < boolean < number < object < array < string - furthermore, each type is not smaller than itself +- discarded values are not comparable @since version 1.0.0 */ inline bool operator<(const value_t lhs, const value_t rhs) noexcept { - static constexpr uint8_t order[] = { - 0, // null - 3, // object - 4, // array - 5, // string - 1, // boolean - 2, // integer - 2, // unsigned - 2, // float + static constexpr std::array order = {{ + 0 /* null */, 3 /* object */, 4 /* array */, 5 /* string */, + 1 /* boolean */, 2 /* integer */, 2 /* unsigned */, 2 /* float */ + } }; - // discarded values are not comparable - if (lhs == value_t::discarded || rhs == value_t::discarded) - { - return false; - } - - return order[static_cast(lhs)] < - order[static_cast(rhs)]; + const auto l_index = static_cast(lhs); + const auto r_index = static_cast(rhs); + return l_index < order.size() and r_index < order.size() and order[l_index] < order[r_index]; } +// overloads for json template parameters +template::value and + not std::is_same::value, + int> = 0> +void get_arithmetic_value(const BasicJsonType& j, ArithmeticType& val) +{ + switch (static_cast(j)) + { + case value_t::number_unsigned: + { + val = static_cast(*j.template get_ptr()); + break; + } + case value_t::number_integer: + { + val = static_cast(*j.template get_ptr()); + break; + } + case value_t::number_float: + { + val = static_cast(*j.template get_ptr()); + break; + } -///////////// -// helpers // -///////////// + default: + JSON_THROW(type_error::create(302, "type must be number, but is " + Twine(j.type_name()))); + } +} -// alias templates to reduce boilerplate -template -using enable_if_t = typename std::enable_if::type; +template +void from_json(const BasicJsonType& j, bool& b) +{ + if (JSON_UNLIKELY(not j.is_boolean())) + { + JSON_THROW(type_error::create(302, "type must be boolean, but is " + Twine(j.type_name()))); + } + b = *j.template get_ptr(); +} -template -using uncvref_t = typename std::remove_cv::type>::type; +template +void from_json(const BasicJsonType& j, std::string& s) +{ + if (JSON_UNLIKELY(not j.is_string())) + { + JSON_THROW(type_error::create(302, "type must be string, but is " + Twine(j.type_name()))); + } + s = *j.template get_ptr(); +} -/* -Implementation of two C++17 constructs: conjunction, negation. This is needed -to avoid evaluating all the traits in a condition +template +void from_json(const BasicJsonType& j, double& val) +{ + get_arithmetic_value(j, val); +} -For example: !std::is_same::value && has_value_type::value -will not compile when T = void (on MSVC at least). Whereas -conjunction>, has_value_type>::value will -stop evaluating if negation<...>::value == false +template +void from_json(const BasicJsonType& j, uint64_t& val) +{ + get_arithmetic_value(j, val); +} -Please note that those constructs must be used with caution, since symbols can -become very long quickly (which can slow down compilation and cause MSVC -internal compiler errors). Only use it when you have to (see example ahead). -*/ -template struct conjunction : std::true_type {}; -template struct conjunction : B1 {}; -template -struct conjunction : std::conditional, B1>::type {}; +template +void from_json(const BasicJsonType& j, int64_t& val) +{ + get_arithmetic_value(j, val); +} -template struct negation : std::integral_constant < bool, !B::value > {}; +template::value, int> = 0> +void from_json(const BasicJsonType& j, EnumType& e) +{ + typename std::underlying_type::type val; + get_arithmetic_value(j, val); + e = static_cast(val); +} -// dispatch utility (taken from ranges-v3) -template struct priority_tag : priority_tag < N - 1 > {}; -template<> struct priority_tag<0> {}; +template +void from_json(const BasicJsonType& j, typename BasicJsonType::array_t& arr) +{ + if (JSON_UNLIKELY(not j.is_array())) + { + JSON_THROW(type_error::create(302, "type must be array, but is " + Twine(j.type_name()))); + } + arr = *j.template get_ptr(); +} +template +void from_json_array_impl(const BasicJsonType& j, CompatibleArrayType& arr, priority_tag<0> /*unused*/) +{ + using std::end; + std::transform(j.begin(), j.end(), + std::inserter(arr, end(arr)), [](const BasicJsonType & i) + { + // get() returns *this, this won't call a from_json + // method when value_type is BasicJsonType + return i.template get(); + }); +} + +template +auto from_json_array_impl(const BasicJsonType& j, CompatibleArrayType& arr, priority_tag<1> /*unused*/) +-> decltype( + arr.reserve(std::declval()), + void()) +{ + using std::end; + + arr.reserve(j.size()); + std::transform(j.begin(), j.end(), + std::inserter(arr, end(arr)), [](const BasicJsonType & i) + { + // get() returns *this, this won't call a from_json + // method when value_type is BasicJsonType + return i.template get(); + }); +} + +template +void from_json_array_impl(const BasicJsonType& j, std::array& arr, priority_tag<2> /*unused*/) +{ + for (std::size_t i = 0; i < N; ++i) + { + arr[i] = j.at(i).template get(); + } +} + +template < + typename BasicJsonType, typename CompatibleArrayType, + enable_if_t < + is_compatible_array_type::value and + not std::is_same::value and + std::is_constructible < + BasicJsonType, typename CompatibleArrayType::value_type >::value, + int > = 0 > +void from_json(const BasicJsonType& j, CompatibleArrayType& arr) +{ + if (JSON_UNLIKELY(not j.is_array())) + { + JSON_THROW(type_error::create(302, "type must be array, but is " + + Twine(j.type_name()))); + } + + from_json_array_impl(j, arr, priority_tag<2> {}); +} + +template +inline +void from_json(const BasicJsonType& j, typename BasicJsonType::object_t& obj) +{ + if (!j.is_object()) + { + JSON_THROW(type_error::create(302, "type must be object, but is " + Twine(j.type_name()))); + } + + auto inner_object = j.template get_ptr(); + for (const auto& i : *inner_object) { + obj.emplace_second(i.first(), i.second); + } +} + +template::value and + not std::is_same::value, int> = 0> +void from_json(const BasicJsonType& j, CompatibleObjectType& obj) +{ + if (JSON_UNLIKELY(not j.is_object())) + { + JSON_THROW(type_error::create(302, "type must be object, but is " + Twine(j.type_name()))); + } + + auto inner_object = j.template get_ptr(); + using std::begin; + using std::end; + using value_type = typename CompatibleObjectType::value_type; + std::vector v; + v.reserve(j.size()); + for (const auto& p : *inner_object) + { + v.emplace_back( + p.first(), + p.second + .template get()); + } + // we could avoid the assignment, but this might require a for loop, which + // might be less efficient than the container constructor for some + // containers (would it?) + obj = CompatibleObjectType(std::make_move_iterator(begin(v)), + std::make_move_iterator(end(v))); +} + +// overload for arithmetic types, not chosen for json template arguments +// (BooleanType, etc..); note: Is it really necessary to provide explicit +// overloads for bool etc. in case of a custom BooleanType which is not +// an arithmetic type? +template::value and + not std::is_same::value and + not std::is_same::value and + not std::is_same::value and + not std::is_same::value, + int> = 0> +void from_json(const BasicJsonType& j, ArithmeticType& val) +{ + switch (static_cast(j)) + { + case value_t::number_unsigned: + { + val = static_cast(*j.template get_ptr()); + break; + } + case value_t::number_integer: + { + val = static_cast(*j.template get_ptr()); + break; + } + case value_t::number_float: + { + val = static_cast(*j.template get_ptr()); + break; + } + case value_t::boolean: + { + val = static_cast(*j.template get_ptr()); + break; + } + + default: + JSON_THROW(type_error::create(302, "type must be number, but is " + Twine(j.type_name()))); + } +} + +template +void from_json(const BasicJsonType& j, std::pair& p) +{ + p = {j.at(0).template get(), j.at(1).template get()}; +} + +template +void from_json_tuple_impl(const BasicJsonType& j, Tuple& t, index_sequence) +{ + t = std::make_tuple(j.at(Idx).template get::type>()...); +} + +template +void from_json(const BasicJsonType& j, std::tuple& t) +{ + from_json_tuple_impl(j, t, index_sequence_for {}); +} + +struct from_json_fn +{ + private: + template + auto call(const BasicJsonType& j, T& val, priority_tag<1> /*unused*/) const + noexcept(noexcept(from_json(j, val))) + -> decltype(from_json(j, val), void()) + { + return from_json(j, val); + } + + template + void call(const BasicJsonType& /*unused*/, T& /*unused*/, priority_tag<0> /*unused*/) const noexcept + { + static_assert(sizeof(BasicJsonType) == 0, + "could not find from_json() method in T's namespace"); +#ifdef _MSC_VER + // MSVC does not show a stacktrace for the above assert + using decayed = uncvref_t; + static_assert(sizeof(typename decayed::force_msvc_stacktrace) == 0, + "forcing MSVC stacktrace to show which T we're talking about."); +#endif + } + + public: + template + void operator()(const BasicJsonType& j, T& val) const + noexcept(noexcept(std::declval().call(j, val, priority_tag<1> {}))) + { + return call(j, val, priority_tag<1> {}); + } +}; +} + +/// namespace to hold default `from_json` function +/// to see why this is required: +/// http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2015/n4381.html +namespace +{ +constexpr const auto& from_json = detail::static_const::value; +} + +namespace detail +{ ////////////////// // constructors // ////////////////// @@ -482,6 +1118,15 @@ struct external_constructor j.m_value = s; j.assert_invariant(); } + + template::value, int> = 0> + static void construct(BasicJsonType& j, T&& s) + { + j.m_type = value_t::string; + j.m_value = std::move(s); + j.assert_invariant(); + } }; template<> @@ -500,7 +1145,7 @@ template<> struct external_constructor { template - static void construct(BasicJsonType& j, std::uint64_t val) noexcept + static void construct(BasicJsonType& j, uint64_t val) noexcept { j.m_type = value_t::number_unsigned; j.m_value = val; @@ -512,7 +1157,7 @@ template<> struct external_constructor { template - static void construct(BasicJsonType& j, std::int64_t val) noexcept + static void construct(BasicJsonType& j, int64_t val) noexcept { j.m_type = value_t::number_integer; j.m_value = val; @@ -531,6 +1176,14 @@ struct external_constructor j.assert_invariant(); } + template + static void construct(BasicJsonType& j, typename BasicJsonType::array_t&& arr) + { + j.m_type = value_t::array; + j.m_value = std::move(arr); + j.assert_invariant(); + } + template static void construct(BasicJsonType& j, ArrayRef arr) { @@ -542,8 +1195,7 @@ struct external_constructor } template::value, + enable_if_t::value, int> = 0> static void construct(BasicJsonType& j, const CompatibleArrayType& arr) { @@ -560,7 +1212,7 @@ struct external_constructor j.m_type = value_t::array; j.m_value = value_t::array; j.m_value.array->reserve(arr.size()); - for (bool x : arr) + for (const bool x : arr) { j.m_value.array->push_back(x); } @@ -579,10 +1231,16 @@ struct external_constructor j.assert_invariant(); } + template + static void construct(BasicJsonType& j, typename BasicJsonType::object_t&& obj) + { + j.m_type = value_t::object; + j.m_value = std::move(obj); + j.assert_invariant(); + } + template::value, - int> = 0> + enable_if_t::value, int> = 0> static void construct(BasicJsonType& j, const CompatibleObjectType& obj) { j.m_type = value_t::object; @@ -595,162 +1253,54 @@ struct external_constructor } }; - -//////////////////////// -// has_/is_ functions // -//////////////////////// - -/*! -@brief Helper to determine whether there's a key_type for T. - -This helper is used to tell associative containers apart from other containers -such as sequence containers. For instance, `std::map` passes the test as it -contains a `mapped_type`, whereas `std::vector` fails the test. - -@sa http://stackoverflow.com/a/7728728/266378 -@since version 1.0.0, overworked in version 2.0.6 -*/ -#define WPI_JSON_HAS_HELPER(type) \ - template struct has_##type { \ - private: \ - template \ - static int detect(U &&); \ - static void detect(...); \ - public: \ - static constexpr bool value = \ - std::is_integral()))>::value; \ - } - -WPI_JSON_HAS_HELPER(mapped_type); -WPI_JSON_HAS_HELPER(key_type); -WPI_JSON_HAS_HELPER(value_type); -WPI_JSON_HAS_HELPER(iterator); - -#undef WPI_JSON_HAS_HELPER - - -template -struct is_compatible_object_type_impl : std::false_type {}; - -template -struct is_compatible_object_type_impl -{ - static constexpr auto value = - std::is_constructible::value && - std::is_constructible::value; -}; - -template -struct is_compatible_object_type -{ - static auto constexpr value = is_compatible_object_type_impl < - conjunction>, - has_mapped_type, - has_key_type>::value, - typename BasicJsonType::object_t, CompatibleObjectType >::value; -}; - -template -struct is_json_nested_type -{ - static auto constexpr value = std::is_same::value || - std::is_same::value || - std::is_same::value; -}; - -template -struct is_compatible_array_type -{ - static auto constexpr value = - conjunction>, - negation>, - negation>, - negation>, - has_value_type, - has_iterator>::value; -}; - -template -struct is_compatible_integer_type_impl : std::false_type {}; - -template -struct is_compatible_integer_type_impl -{ - // is there an assert somewhere on overflows? - using RealLimits = std::numeric_limits; - using CompatibleLimits = std::numeric_limits; - - static constexpr auto value = - std::is_constructible::value && - CompatibleLimits::is_integer && - RealLimits::is_signed == CompatibleLimits::is_signed; -}; - -template -struct is_compatible_integer_type -{ - static constexpr auto value = - is_compatible_integer_type_impl < - std::is_integral::value && - !std::is_same::value, - RealIntegerType, CompatibleNumberIntegerType > ::value; -}; - ///////////// // to_json // ///////////// -template::value, int> = 0> -inline +template::value, int> = 0> void to_json(BasicJsonType& j, T b) noexcept { - detail::external_constructor::construct(j, b); + external_constructor::construct(j, b); } template::value, int> = 0> -inline + enable_if_t::value, int> = 0> void to_json(BasicJsonType& j, const CompatibleString& s) { external_constructor::construct(j, s); } +template::value, int> = 0> +void to_json(BasicJsonType& j, T&& s) +{ + external_constructor::construct(j, std::move(s)); +} + template::value, int> = 0> -inline void to_json(BasicJsonType& j, FloatType val) noexcept { external_constructor::construct(j, static_cast(val)); } -template < - typename BasicJsonType, typename CompatibleNumberUnsignedType, - enable_if_t::value, int> = 0 > -inline +template::value, int> = 0> void to_json(BasicJsonType& j, CompatibleNumberUnsignedType val) noexcept { - external_constructor::construct(j, static_cast(val)); + external_constructor::construct(j, static_cast(val)); } -template < - typename BasicJsonType, typename CompatibleNumberIntegerType, - enable_if_t::value, int> = 0 > -inline +template::value, int> = 0> void to_json(BasicJsonType& j, CompatibleNumberIntegerType val) noexcept { - external_constructor::construct(j, static_cast(val)); + external_constructor::construct(j, static_cast(val)); } template::value, int> = 0> -inline void to_json(BasicJsonType& j, EnumType e) noexcept { using underlying_type = typename std::underlying_type::type; @@ -758,331 +1308,86 @@ void to_json(BasicJsonType& j, EnumType e) noexcept } template -inline void to_json(BasicJsonType& j, const std::vector& e) { external_constructor::construct(j, e); } -template -inline -void to_json(BasicJsonType& j, ArrayRef arr) +template::value or + std::is_same::value, + int> = 0> +void to_json(BasicJsonType& j, const CompatibleArrayType& arr) { external_constructor::construct(j, arr); } -template < - typename BasicJsonType, typename CompatibleArrayType, - enable_if_t < - is_compatible_array_type::value || - std::is_same::value, - int > = 0 > -inline -void to_json(BasicJsonType& j, const CompatibleArrayType& arr) +template +void to_json(BasicJsonType& j, typename BasicJsonType::array_t&& arr) { - external_constructor::construct(j, arr); + external_constructor::construct(j, std::move(arr)); } -template < - typename BasicJsonType, typename CompatibleObjectType, - enable_if_t::value, - int> = 0 > -inline -void to_json(BasicJsonType& j, const CompatibleObjectType& arr) +template::value, int> = 0> +void to_json(BasicJsonType& j, const CompatibleObjectType& obj) { - external_constructor::construct(j, arr); + external_constructor::construct(j, obj); } -template ::value, - int> = 0> -inline +template +void to_json(BasicJsonType& j, typename BasicJsonType::object_t&& obj) +{ + external_constructor::construct(j, std::move(obj)); +} + +template::value, int> = 0> void to_json(BasicJsonType& j, T (&arr)[N]) { external_constructor::construct(j, arr); } -template ::value, int> = 0> -inline -void to_json(BasicJsonType& j, std::pair const& p) +template +void to_json(BasicJsonType& j, const std::pair& p) { - j[p.first] = p.second; + j = {p.first, p.second}; } -/////////////// -// from_json // -/////////////// - -// overloads for json template parameters -template::value && - !std::is_same::value, - int> = 0> -void get_arithmetic_value(const BasicJsonType& j, ArithmeticType& val) +template +void to_json_tuple_impl(BasicJsonType& j, const Tuple& t, index_sequence) { - switch (static_cast(j)) - { - case value_t::number_unsigned: - { - val = static_cast( - *j.template get_ptr()); - break; - } - case value_t::number_integer: - { - val = static_cast( - *j.template get_ptr()); - break; - } - case value_t::number_float: - { - val = static_cast( - *j.template get_ptr()); - break; - } - default: - { - JSON_THROW(type_error::create(302, "type must be number, but is " + j.type_name())); - } - } + j = {std::get(t)...}; } -template -inline -void from_json(const BasicJsonType& j, bool& b) +template +void to_json(BasicJsonType& j, const std::tuple& t) { - if (!j.is_boolean()) - { - JSON_THROW(type_error::create(302, "type must be boolean, but is " + j.type_name())); - } - b = *j.template get_ptr(); -} - -template -inline -void from_json(const BasicJsonType& j, std::string& s) -{ - if (!j.is_string()) - { - JSON_THROW(detail::type_error::create(302, "type must be string, but is " + j.type_name())); - } - s = *j.template get_ptr(); -} - -template -inline -void from_json(const BasicJsonType& j, double& val) -{ - get_arithmetic_value(j, val); -} - -template -inline -void from_json(const BasicJsonType& j, std::uint64_t& val) -{ - get_arithmetic_value(j, val); -} - -template -inline -void from_json(const BasicJsonType& j, std::int64_t& val) -{ - get_arithmetic_value(j, val); -} - -template::value, int> = 0> -inline -void from_json(const BasicJsonType& j, EnumType& e) -{ - typename std::underlying_type::type val; - get_arithmetic_value(j, val); - e = static_cast(val); -} - -template -inline -void from_json(const BasicJsonType& j, typename BasicJsonType::array_t& arr) -{ - if (!j.is_array()) - { - JSON_THROW(type_error::create(302, "type must be array, but is " + j.type_name())); - } - arr = *j.template get_ptr(); -} - -template -void from_json_array_impl(const BasicJsonType& j, CompatibleArrayType& arr, detail::priority_tag<0>) -{ - using std::begin; - using std::end; - - std::transform(j.begin(), j.end(), - std::inserter(arr, end(arr)), [](const BasicJsonType & i) - { - // get() returns *this, this won't call a from_json - // method when value_type is BasicJsonType - return i.template get(); - }); -} - -template -auto from_json_array_impl(const BasicJsonType& j, CompatibleArrayType& arr, detail::priority_tag<1>) --> decltype( - arr.reserve(std::declval()), - void()) -{ - using std::begin; - using std::end; - - arr.reserve(j.size()); - std::transform(j.begin(), j.end(), - std::inserter(arr, end(arr)), [](const BasicJsonType & i) - { - // get() returns *this, this won't call a from_json - // method when value_type is BasicJsonType - return i.template get(); - }); -} - -template::value && - std::is_convertible::value && - !std::is_same::value, int> = 0> -void from_json(const BasicJsonType& j, CompatibleArrayType& arr) -{ - if (!j.is_array()) - { - JSON_THROW(type_error::create(302, "type must be array, but is " + j.type_name())); - } - - from_json_array_impl(j, arr, priority_tag<1> {}); -} - -template -inline -void from_json(const BasicJsonType& j, typename BasicJsonType::object_t& obj) -{ - if (!j.is_object()) - { - JSON_THROW(type_error::create(302, "type must be object, but is " + j.type_name())); - } - - auto inner_object = j.template get_ptr(); - for (const auto& i : *inner_object) { - obj.emplace_second(i.first(), i.second); - } -} - -template::value && - !std::is_same::value, int> = 0> -void from_json(const BasicJsonType& j, CompatibleObjectType& obj) -{ - if (!j.is_object()) - { - JSON_THROW(type_error::create(302, "type must be object, but is " + j.type_name())); - } - - auto inner_object = j.template get_ptr(); - using std::begin; - using std::end; - using value_type = typename CompatibleObjectType::value_type; - std::vector v; - v.reserve(j.size()); - for (const auto& p : *inner_object) - { - v.emplace_back( - p.first(), - p.second - .template get()); - } - // we could avoid the assignment, but this might require a for loop, which - // might be less efficient than the container constructor for some - // containers (would it?) - obj = CompatibleObjectType(std::make_move_iterator(begin(v)), - std::make_move_iterator(end(v))); -} - -// overload for arithmetic types, not chosen for json template arguments; -template::value && - !std::is_same::value && - !std::is_same::value && - !std::is_same::value && - !std::is_same::value, - int> = 0> -void from_json(const BasicJsonType& j, ArithmeticType& val) -{ - switch (static_cast(j)) - { - case value_t::number_unsigned: - { - val = static_cast(*j.template get_ptr()); - break; - } - case value_t::number_integer: - { - val = static_cast(*j.template get_ptr()); - break; - } - case value_t::number_float: - { - val = static_cast(*j.template get_ptr()); - break; - } - case value_t::boolean: - { - val = static_cast(*j.template get_ptr()); - break; - } - default: - { - JSON_THROW(type_error::create(302, "type must be number, but is " + j.type_name())); - } - } -} - -template ::value, int> = 0> -void from_json(const BasicJsonType& j, std::pair& p) -{ - if (!j.is_object()) - { - JSON_THROW(type_error::create(302, "type must be object, but is " + j.type_name())); - } - - auto const inner_object = j.template get_ptr(); - auto const size = inner_object->size(); - if (size != 1) - { - JSON_THROW(other_error::create(502, "conversion to std::pair requires the object to have exactly one field, but it has " + std::to_string(size))); - } - auto const& obj = *inner_object->begin(); - // cannot use *inner_object, need to convert both members - p = std::make_pair(CompatibleString(obj.first()), obj.second.template get()); + to_json_tuple_impl(j, t, index_sequence_for {}); } struct to_json_fn { private: template - auto call(BasicJsonType& j, T&& val, priority_tag<1>) const noexcept(noexcept(to_json(j, std::forward(val)))) + auto call(BasicJsonType& j, T&& val, priority_tag<1> /*unused*/) const noexcept(noexcept(to_json(j, std::forward(val)))) -> decltype(to_json(j, std::forward(val)), void()) { return to_json(j, std::forward(val)); } template - void call(BasicJsonType&, T&&, priority_tag<0>) const noexcept + void call(BasicJsonType& /*unused*/, T&& /*unused*/, priority_tag<0> /*unused*/) const noexcept { static_assert(sizeof(BasicJsonType) == 0, "could not find to_json() method in T's namespace"); + +#ifdef _MSC_VER + // MSVC does not show a stacktrace for the above assert + using decayed = uncvref_t; + static_assert(sizeof(typename decayed::force_msvc_stacktrace) == 0, + "forcing MSVC stacktrace to show which T we're talking about."); +#endif } public: @@ -1093,53 +1398,1257 @@ struct to_json_fn return call(j, std::forward(val), priority_tag<1> {}); } }; +} -struct from_json_fn -{ - private: - template - auto call(const BasicJsonType& j, T& val, priority_tag<1>) const - noexcept(noexcept(from_json(j, val))) - -> decltype(from_json(j, val), void()) - { - return from_json(j, val); - } - - template - void call(const BasicJsonType&, T&, priority_tag<0>) const noexcept - { - static_assert(sizeof(BasicJsonType) == 0, - "could not find from_json() method in T's namespace"); - } - - public: - template - void operator()(const BasicJsonType& j, T& val) const - noexcept(noexcept(std::declval().call(j, val, priority_tag<1> {}))) - { - return call(j, val, priority_tag<1> {}); - } -}; - -// taken from ranges-v3 -template -struct static_const -{ - static constexpr T value{}; -}; - -template -constexpr T static_const::value; -} // namespace detail - - -/// namespace to hold default `to_json` / `from_json` functions +/// namespace to hold default `to_json` function namespace { constexpr const auto& to_json = detail::static_const::value; -constexpr const auto& from_json = detail::static_const::value; } +namespace detail +{ +/* +@brief an iterator for primitive JSON types + +This class models an iterator for primitive JSON types (boolean, number, +string). It's only purpose is to allow the iterator/const_iterator classes +to "iterate" over primitive values. Internally, the iterator is modeled by +a `difference_type` variable. Value begin_value (`0`) models the begin, +end_value (`1`) models past the end. +*/ +class primitive_iterator_t +{ + private: + using difference_type = std::ptrdiff_t; + static constexpr difference_type begin_value = 0; + static constexpr difference_type end_value = begin_value + 1; + + /// iterator as signed integer type + difference_type m_it = (std::numeric_limits::min)(); + + public: + constexpr difference_type get_value() const noexcept + { + return m_it; + } + + /// set iterator to a defined beginning + void set_begin() noexcept + { + m_it = begin_value; + } + + /// set iterator to a defined past the end + void set_end() noexcept + { + m_it = end_value; + } + + /// return whether the iterator can be dereferenced + constexpr bool is_begin() const noexcept + { + return m_it == begin_value; + } + + /// return whether the iterator is at end + constexpr bool is_end() const noexcept + { + return m_it == end_value; + } + + friend constexpr bool operator==(primitive_iterator_t lhs, primitive_iterator_t rhs) noexcept + { + return lhs.m_it == rhs.m_it; + } + + friend constexpr bool operator<(primitive_iterator_t lhs, primitive_iterator_t rhs) noexcept + { + return lhs.m_it < rhs.m_it; + } + + primitive_iterator_t operator+(difference_type n) noexcept + { + auto result = *this; + result += n; + return result; + } + + friend constexpr difference_type operator-(primitive_iterator_t lhs, primitive_iterator_t rhs) noexcept + { + return lhs.m_it - rhs.m_it; + } + + primitive_iterator_t& operator++() noexcept + { + ++m_it; + return *this; + } + + primitive_iterator_t const operator++(int) noexcept + { + auto result = *this; + m_it++; + return result; + } + + primitive_iterator_t& operator--() noexcept + { + --m_it; + return *this; + } + + primitive_iterator_t const operator--(int) noexcept + { + auto result = *this; + m_it--; + return result; + } + + primitive_iterator_t& operator+=(difference_type n) noexcept + { + m_it += n; + return *this; + } + + primitive_iterator_t& operator-=(difference_type n) noexcept + { + m_it -= n; + return *this; + } +}; + +/*! +@brief an iterator value + +@note This structure could easily be a union, but MSVC currently does not allow +unions members with complex constructors, see https://github.com/nlohmann/json/pull/105. +*/ +template struct internal_iterator +{ + /// iterator for JSON objects + typename BasicJsonType::object_t::iterator object_iterator {}; + /// iterator for JSON arrays + typename BasicJsonType::array_t::iterator array_iterator {}; + /// generic iterator for all other types + primitive_iterator_t primitive_iterator {}; +}; + +// forward declare, to be able to friend it later on +template class iteration_proxy; + +/*! +@brief a template for a bidirectional iterator for the @ref json class + +This class implements a both iterators (iterator and const_iterator) for the +@ref json class. + +@note An iterator is called *initialized* when a pointer to a JSON value has + been set (e.g., by a constructor or a copy assignment). If the iterator is + default-constructed, it is *uninitialized* and most methods are undefined. + **The library uses assertions to detect calls on uninitialized iterators.** + +@requirement The class satisfies the following concept requirements: +- +[BidirectionalIterator](http://en.cppreference.com/w/cpp/concept/BidirectionalIterator): + The iterator that can be moved can be moved in both directions (i.e. + incremented and decremented). + +@since version 1.0.0, simplified in version 2.0.9, change to bidirectional + iterators in version 3.0.0 (see https://github.com/nlohmann/json/issues/593) +*/ +template +class iter_impl +{ + /// allow json to access private members + friend iter_impl::value, typename std::remove_const::type, const BasicJsonType>::type>; + friend BasicJsonType; + friend iteration_proxy; + friend class ::wpi::JsonTest; + + using object_t = typename BasicJsonType::object_t; + using array_t = typename BasicJsonType::array_t; + // make sure BasicJsonType is json or const json + static_assert(is_json::type>::value, + "iter_impl only accepts (const) json"); + + public: + + /// The std::iterator class template (used as a base class to provide typedefs) is deprecated in C++17. + /// The C++ Standard has never required user-defined iterators to derive from std::iterator. + /// A user-defined iterator should provide publicly accessible typedefs named + /// iterator_category, value_type, difference_type, pointer, and reference. + /// Note that value_type is required to be non-const, even for constant iterators. + using iterator_category = std::bidirectional_iterator_tag; + + /// the type of the values when the iterator is dereferenced + using value_type = typename BasicJsonType::value_type; + /// a type to represent differences between iterators + using difference_type = typename BasicJsonType::difference_type; + /// defines a pointer to the type iterated over (value_type) + using pointer = typename std::conditional::value, + typename BasicJsonType::const_pointer, + typename BasicJsonType::pointer>::type; + /// defines a reference to the type iterated over (value_type) + using reference = + typename std::conditional::value, + typename BasicJsonType::const_reference, + typename BasicJsonType::reference>::type; + + /// default constructor + iter_impl() = default; + + /*! + @brief constructor for a given JSON instance + @param[in] object pointer to a JSON object for this iterator + @pre object != nullptr + @post The iterator is initialized; i.e. `m_object != nullptr`. + */ + explicit iter_impl(pointer object) noexcept : m_object(object) + { + assert(m_object != nullptr); + + switch (m_object->m_type) + { + case value_t::object: + { + m_it.object_iterator = typename object_t::iterator(); + break; + } + + case value_t::array: + { + m_it.array_iterator = typename array_t::iterator(); + break; + } + + default: + { + m_it.primitive_iterator = primitive_iterator_t(); + break; + } + } + } + + /*! + @note The conventional copy constructor and copy assignment are implicitly + defined. Combined with the following converting constructor and + assignment, they support: (1) copy from iterator to iterator, (2) + copy from const iterator to const iterator, and (3) conversion from + iterator to const iterator. However conversion from const iterator + to iterator is not defined. + */ + + /*! + @brief converting constructor + @param[in] other non-const iterator to copy from + @note It is not checked whether @a other is initialized. + */ + iter_impl(const iter_impl::type>& other) noexcept + : m_object(other.m_object), m_it(other.m_it) {} + + /*! + @brief converting assignment + @param[in,out] other non-const iterator to copy from + @return const/non-const iterator + @note It is not checked whether @a other is initialized. + */ + iter_impl& operator=(const iter_impl::type>& other) noexcept + { + m_object = other.m_object; + m_it = other.m_it; + return *this; + } + + private: + /*! + @brief set the iterator to the first value + @pre The iterator is initialized; i.e. `m_object != nullptr`. + */ + void set_begin() noexcept + { + assert(m_object != nullptr); + + switch (m_object->m_type) + { + case value_t::object: + { + m_it.object_iterator = m_object->m_value.object->begin(); + break; + } + + case value_t::array: + { + m_it.array_iterator = m_object->m_value.array->begin(); + break; + } + + case value_t::null: + { + // set to end so begin()==end() is true: null is empty + m_it.primitive_iterator.set_end(); + break; + } + + default: + { + m_it.primitive_iterator.set_begin(); + break; + } + } + } + + /*! + @brief set the iterator past the last value + @pre The iterator is initialized; i.e. `m_object != nullptr`. + */ + void set_end() noexcept + { + assert(m_object != nullptr); + + switch (m_object->m_type) + { + case value_t::object: + { + m_it.object_iterator = m_object->m_value.object->end(); + break; + } + + case value_t::array: + { + m_it.array_iterator = m_object->m_value.array->end(); + break; + } + + default: + { + m_it.primitive_iterator.set_end(); + break; + } + } + } + + public: + /*! + @brief return a reference to the value pointed to by the iterator + @pre The iterator is initialized; i.e. `m_object != nullptr`. + */ + reference operator*() const + { + assert(m_object != nullptr); + + switch (m_object->m_type) + { + case value_t::object: + { + assert(m_it.object_iterator != m_object->m_value.object->end()); + return m_it.object_iterator->second; + } + + case value_t::array: + { + assert(m_it.array_iterator != m_object->m_value.array->end()); + return *m_it.array_iterator; + } + + case value_t::null: + JSON_THROW(invalid_iterator::create(214, "cannot get value")); + + default: + { + if (JSON_LIKELY(m_it.primitive_iterator.is_begin())) + { + return *m_object; + } + + JSON_THROW(invalid_iterator::create(214, "cannot get value")); + } + } + } + + /*! + @brief dereference the iterator + @pre The iterator is initialized; i.e. `m_object != nullptr`. + */ + pointer operator->() const + { + assert(m_object != nullptr); + + switch (m_object->m_type) + { + case value_t::object: + { + assert(m_it.object_iterator != m_object->m_value.object->end()); + return &(m_it.object_iterator->second); + } + + case value_t::array: + { + assert(m_it.array_iterator != m_object->m_value.array->end()); + return &*m_it.array_iterator; + } + + default: + { + if (JSON_LIKELY(m_it.primitive_iterator.is_begin())) + { + return m_object; + } + + JSON_THROW(invalid_iterator::create(214, "cannot get value")); + } + } + } + + /*! + @brief post-increment (it++) + @pre The iterator is initialized; i.e. `m_object != nullptr`. + */ + iter_impl const operator++(int) + { + auto result = *this; + ++(*this); + return result; + } + + /*! + @brief pre-increment (++it) + @pre The iterator is initialized; i.e. `m_object != nullptr`. + */ + iter_impl& operator++() + { + assert(m_object != nullptr); + + switch (m_object->m_type) + { + case value_t::object: + { + ++m_it.object_iterator; + break; + } + + case value_t::array: + { + std::advance(m_it.array_iterator, 1); + break; + } + + default: + { + ++m_it.primitive_iterator; + break; + } + } + + return *this; + } + + /*! + @brief post-decrement (it--) + @pre The iterator is initialized; i.e. `m_object != nullptr`. + */ + iter_impl const operator--(int) + { + auto result = *this; + --(*this); + return result; + } + + /*! + @brief pre-decrement (--it) + @pre The iterator is initialized; i.e. `m_object != nullptr`. + */ + iter_impl& operator--() + { + assert(m_object != nullptr); + + switch (m_object->m_type) + { + case value_t::object: + { + --m_it.object_iterator; + break; + } + + case value_t::array: + { + std::advance(m_it.array_iterator, -1); + break; + } + + default: + { + --m_it.primitive_iterator; + break; + } + } + + return *this; + } + + /*! + @brief comparison: equal + @pre The iterator is initialized; i.e. `m_object != nullptr`. + */ + bool operator==(const iter_impl& other) const + { + // if objects are not the same, the comparison is undefined + if (JSON_UNLIKELY(m_object != other.m_object)) + { + JSON_THROW(invalid_iterator::create(212, "cannot compare iterators of different containers")); + } + + assert(m_object != nullptr); + + switch (m_object->m_type) + { + case value_t::object: + return (m_it.object_iterator == other.m_it.object_iterator); + + case value_t::array: + return (m_it.array_iterator == other.m_it.array_iterator); + + default: + return (m_it.primitive_iterator == other.m_it.primitive_iterator); + } + } + + /*! + @brief comparison: not equal + @pre The iterator is initialized; i.e. `m_object != nullptr`. + */ + bool operator!=(const iter_impl& other) const + { + return not operator==(other); + } + + /*! + @brief comparison: smaller + @pre The iterator is initialized; i.e. `m_object != nullptr`. + */ + bool operator<(const iter_impl& other) const + { + // if objects are not the same, the comparison is undefined + if (JSON_UNLIKELY(m_object != other.m_object)) + { + JSON_THROW(invalid_iterator::create(212, "cannot compare iterators of different containers")); + } + + assert(m_object != nullptr); + + switch (m_object->m_type) + { + case value_t::object: + JSON_THROW(invalid_iterator::create(213, "cannot compare order of object iterators")); + + case value_t::array: + return (m_it.array_iterator < other.m_it.array_iterator); + + default: + return (m_it.primitive_iterator < other.m_it.primitive_iterator); + } + } + + /*! + @brief comparison: less than or equal + @pre The iterator is initialized; i.e. `m_object != nullptr`. + */ + bool operator<=(const iter_impl& other) const + { + return not other.operator < (*this); + } + + /*! + @brief comparison: greater than + @pre The iterator is initialized; i.e. `m_object != nullptr`. + */ + bool operator>(const iter_impl& other) const + { + return not operator<=(other); + } + + /*! + @brief comparison: greater than or equal + @pre The iterator is initialized; i.e. `m_object != nullptr`. + */ + bool operator>=(const iter_impl& other) const + { + return not operator<(other); + } + + /*! + @brief add to iterator + @pre The iterator is initialized; i.e. `m_object != nullptr`. + */ + iter_impl& operator+=(difference_type i) + { + assert(m_object != nullptr); + + switch (m_object->m_type) + { + case value_t::object: + JSON_THROW(invalid_iterator::create(209, "cannot use offsets with object iterators")); + + case value_t::array: + { + std::advance(m_it.array_iterator, i); + break; + } + + default: + { + m_it.primitive_iterator += i; + break; + } + } + + return *this; + } + + /*! + @brief subtract from iterator + @pre The iterator is initialized; i.e. `m_object != nullptr`. + */ + iter_impl& operator-=(difference_type i) + { + return operator+=(-i); + } + + /*! + @brief add to iterator + @pre The iterator is initialized; i.e. `m_object != nullptr`. + */ + iter_impl operator+(difference_type i) const + { + auto result = *this; + result += i; + return result; + } + + /*! + @brief addition of distance and iterator + @pre The iterator is initialized; i.e. `m_object != nullptr`. + */ + friend iter_impl operator+(difference_type i, const iter_impl& it) + { + auto result = it; + result += i; + return result; + } + + /*! + @brief subtract from iterator + @pre The iterator is initialized; i.e. `m_object != nullptr`. + */ + iter_impl operator-(difference_type i) const + { + auto result = *this; + result -= i; + return result; + } + + /*! + @brief return difference + @pre The iterator is initialized; i.e. `m_object != nullptr`. + */ + difference_type operator-(const iter_impl& other) const + { + assert(m_object != nullptr); + + switch (m_object->m_type) + { + case value_t::object: + JSON_THROW(invalid_iterator::create(209, "cannot use offsets with object iterators")); + + case value_t::array: + return m_it.array_iterator - other.m_it.array_iterator; + + default: + return m_it.primitive_iterator - other.m_it.primitive_iterator; + } + } + + /*! + @brief access to successor + @pre The iterator is initialized; i.e. `m_object != nullptr`. + */ + reference operator[](difference_type n) const + { + assert(m_object != nullptr); + + switch (m_object->m_type) + { + case value_t::object: + JSON_THROW(invalid_iterator::create(208, "cannot use operator[] for object iterators")); + + case value_t::array: + return *std::next(m_it.array_iterator, n); + + case value_t::null: + JSON_THROW(invalid_iterator::create(214, "cannot get value")); + + default: + { + if (JSON_LIKELY(m_it.primitive_iterator.get_value() == -n)) + { + return *m_object; + } + + JSON_THROW(invalid_iterator::create(214, "cannot get value")); + } + } + } + + /*! + @brief return the key of an object iterator + @pre The iterator is initialized; i.e. `m_object != nullptr`. + */ + StringRef key() const + { + assert(m_object != nullptr); + + if (JSON_LIKELY(m_object->is_object())) + { + return m_it.object_iterator->first(); + } + + JSON_THROW(invalid_iterator::create(207, "cannot use key() for non-object iterators")); + } + + /*! + @brief return the value of an iterator + @pre The iterator is initialized; i.e. `m_object != nullptr`. + */ + reference value() const + { + return operator*(); + } + + private: + /// associated JSON instance + pointer m_object = nullptr; + /// the actual iterator of the associated instance + internal_iterator::type> m_it; +}; + +/// proxy class for the items() function +template class iteration_proxy +{ + private: + /// helper class for iteration + class iteration_proxy_internal + { + private: + /// the iterator + IteratorType anchor; + /// an index for arrays (used to create key names) + std::size_t array_index = 0; + + public: + explicit iteration_proxy_internal(IteratorType it) noexcept : anchor(it) {} + + /// dereference operator (needed for range-based for) + iteration_proxy_internal& operator*() + { + return *this; + } + + /// increment operator (needed for range-based for) + iteration_proxy_internal& operator++() + { + ++anchor; + ++array_index; + + return *this; + } + + /// inequality operator (needed for range-based for) + bool operator!=(const iteration_proxy_internal& o) const noexcept + { + return anchor != o.anchor; + } + + /// return key of the iterator + std::string key() const + { + assert(anchor.m_object != nullptr); + + switch (anchor.m_object->type()) + { + // use integer array index as key + case value_t::array: + return std::to_string(array_index); + + // use key from the object + case value_t::object: + return anchor.key(); + + // use an empty key for all primitive types + default: + return ""; + } + } + + /// return value of the iterator + typename IteratorType::reference value() const + { + return anchor.value(); + } + }; + + /// the container to iterate + typename IteratorType::reference container; + + public: + /// construct iteration proxy from a container + explicit iteration_proxy(typename IteratorType::reference cont) noexcept + : container(cont) {} + + /// return iterator begin (needed for range-based for) + iteration_proxy_internal begin() noexcept + { + return iteration_proxy_internal(container.begin()); + } + + /// return iterator end (needed for range-based for) + iteration_proxy_internal end() noexcept + { + return iteration_proxy_internal(container.end()); + } +}; + +////////////////////// +// reverse_iterator // +////////////////////// + +/*! +@brief a template for a reverse iterator class + +@tparam Base the base iterator type to reverse. Valid types are @ref +iterator (to create @ref reverse_iterator) and @ref const_iterator (to +create @ref const_reverse_iterator). + +@requirement The class satisfies the following concept requirements: +- +[BidirectionalIterator](http://en.cppreference.com/w/cpp/concept/BidirectionalIterator): + The iterator that can be moved can be moved in both directions (i.e. + incremented and decremented). +- [OutputIterator](http://en.cppreference.com/w/cpp/concept/OutputIterator): + It is possible to write to the pointed-to element (only if @a Base is + @ref iterator). + +@since version 1.0.0 +*/ +template +class json_reverse_iterator : public std::reverse_iterator +{ + public: + using difference_type = std::ptrdiff_t; + /// shortcut to the reverse iterator adapter + using base_iterator = std::reverse_iterator; + /// the reference type for the pointed-to element + using reference = typename Base::reference; + + /// create reverse iterator from iterator + json_reverse_iterator(const typename base_iterator::iterator_type& it) noexcept + : base_iterator(it) {} + + /// create reverse iterator from base class + json_reverse_iterator(const base_iterator& it) noexcept : base_iterator(it) {} + + /// post-increment (it++) + json_reverse_iterator const operator++(int) + { + return static_cast(base_iterator::operator++(1)); + } + + /// pre-increment (++it) + json_reverse_iterator& operator++() + { + return static_cast(base_iterator::operator++()); + } + + /// post-decrement (it--) + json_reverse_iterator const operator--(int) + { + return static_cast(base_iterator::operator--(1)); + } + + /// pre-decrement (--it) + json_reverse_iterator& operator--() + { + return static_cast(base_iterator::operator--()); + } + + /// add to iterator + json_reverse_iterator& operator+=(difference_type i) + { + return static_cast(base_iterator::operator+=(i)); + } + + /// add to iterator + json_reverse_iterator operator+(difference_type i) const + { + return static_cast(base_iterator::operator+(i)); + } + + /// subtract from iterator + json_reverse_iterator operator-(difference_type i) const + { + return static_cast(base_iterator::operator-(i)); + } + + /// return difference + difference_type operator-(const json_reverse_iterator& other) const + { + return base_iterator(*this) - base_iterator(other); + } + + /// access to successor + reference operator[](difference_type n) const + { + return *(this->operator+(n)); + } + + /// return the key of an object iterator + auto key() const -> decltype(std::declval().key()) + { + auto it = --this->base(); + return it.key(); + } + + /// return the value of an iterator + reference value() const + { + auto it = --this->base(); + return it.operator * (); + } +}; + +template +class json_ref +{ + public: + using value_type = BasicJsonType; + + json_ref(value_type&& value) + : owned_value(std::move(value)), value_ref(&owned_value), is_rvalue(true) + {} + + json_ref(const value_type& value) + : value_ref(const_cast(&value)), is_rvalue(false) + {} + + json_ref(std::initializer_list init) + : owned_value(init), value_ref(&owned_value), is_rvalue(true) + {} + + template + json_ref(Args&& ... args) + : owned_value(std::forward(args)...), value_ref(&owned_value), is_rvalue(true) + {} + + // class should be movable only + json_ref(json_ref&&) = default; + json_ref(const json_ref&) = delete; + json_ref& operator=(const json_ref&) = delete; + + value_type moved_or_copied() const + { + if (is_rvalue) + { + return std::move(*value_ref); + } + return *value_ref; + } + + value_type const& operator*() const + { + return *static_cast(value_ref); + } + + value_type const* operator->() const + { + return static_cast(value_ref); + } + + private: + mutable value_type owned_value = nullptr; + value_type* value_ref = nullptr; + const bool is_rvalue; +}; +} // namespace detail + +class json_pointer +{ + // allow json to access private members + friend class json; + friend class JsonTest; + + public: + /*! + @brief create JSON pointer + + Create a JSON pointer according to the syntax described in + [Section 3 of RFC6901](https://tools.ietf.org/html/rfc6901#section-3). + + @param[in] s string representing the JSON pointer; if omitted, the empty + string is assumed which references the whole JSON value + + @throw parse_error.107 if the given JSON pointer @a s is nonempty and does + not begin with a slash (`/`); see example below + + @throw parse_error.108 if a tilde (`~`) in the given JSON pointer @a s is + not followed by `0` (representing `~`) or `1` (representing `/`); see + example below + + @liveexample{The example shows the construction several valid JSON pointers + as well as the exceptional behavior.,json_pointer} + + @since version 2.0.0 + */ + explicit json_pointer(const Twine& s = {}) + : reference_tokens(split(s)) + {} + + /*! + @brief return a string representation of the JSON pointer + + @invariant For each JSON pointer `ptr`, it holds: + @code {.cpp} + ptr == json_pointer(ptr.to_string()); + @endcode + + @return a string representation of the JSON pointer + + @liveexample{The example shows the result of `to_string`., + json_pointer__to_string} + + @since version 2.0.0 + */ + std::string to_string() const noexcept; + + /// @copydoc to_string() + operator std::string() const + { + return to_string(); + } + + /*! + @param[in] s reference token to be converted into an array index + + @return integer representation of @a s + + @throw out_of_range.404 if string @a s could not be converted to an integer + */ + static int array_index(const Twine& s); + + private: + /*! + @brief remove and return last reference pointer + @throw out_of_range.405 if JSON pointer has no parent + */ + std::string pop_back() + { + if (JSON_UNLIKELY(is_root())) + { + JSON_THROW(detail::out_of_range::create(405, "JSON pointer has no parent")); + } + + auto last = reference_tokens.back(); + reference_tokens.pop_back(); + return last; + } + + /// return whether pointer points to the root document + bool is_root() const + { + return reference_tokens.empty(); + } + + json_pointer top() const + { + if (JSON_UNLIKELY(is_root())) + { + JSON_THROW(detail::out_of_range::create(405, "JSON pointer has no parent")); + } + + json_pointer result = *this; + result.reference_tokens = {reference_tokens[0]}; + return result; + } + + /*! + @brief create and return a reference to the pointed to value + + @complexity Linear in the number of reference tokens. + + @throw parse_error.109 if array index is not a number + @throw type_error.313 if value cannot be unflattened + */ + json& get_and_create(json& j) const; + + /*! + @brief return a reference to the pointed to value + + @note This version does not throw if a value is not present, but tries to + create nested values instead. For instance, calling this function + with pointer `"/this/that"` on a null value is equivalent to calling + `operator[]("this").operator[]("that")` on that value, effectively + changing the null value to an object. + + @param[in] ptr a JSON value + + @return reference to the JSON value pointed to by the JSON pointer + + @complexity Linear in the length of the JSON pointer. + + @throw parse_error.106 if an array index begins with '0' + @throw parse_error.109 if an array index was not a number + @throw out_of_range.404 if the JSON pointer can not be resolved + */ + json& get_unchecked(json* ptr) const; + + /*! + @throw parse_error.106 if an array index begins with '0' + @throw parse_error.109 if an array index was not a number + @throw out_of_range.402 if the array index '-' is used + @throw out_of_range.404 if the JSON pointer can not be resolved + */ + json& get_checked(json* ptr) const; + + /*! + @brief return a const reference to the pointed to value + + @param[in] ptr a JSON value + + @return const reference to the JSON value pointed to by the JSON + pointer + + @throw parse_error.106 if an array index begins with '0' + @throw parse_error.109 if an array index was not a number + @throw out_of_range.402 if the array index '-' is used + @throw out_of_range.404 if the JSON pointer can not be resolved + */ + const json& get_unchecked(const json* ptr) const; + + /*! + @throw parse_error.106 if an array index begins with '0' + @throw parse_error.109 if an array index was not a number + @throw out_of_range.402 if the array index '-' is used + @throw out_of_range.404 if the JSON pointer can not be resolved + */ + const json& get_checked(const json* ptr) const; + + /*! + @brief split the string input to reference tokens + + @note This function is only called by the json_pointer constructor. + All exceptions below are documented there. + + @throw parse_error.107 if the pointer is not empty or begins with '/' + @throw parse_error.108 if character '~' is not followed by '0' or '1' + */ + static std::vector split(const Twine& reference_string); + + /*! + @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 + */ + static void replace_substring(std::string& s, const std::string& f, + const std::string& t); + + /// escape "~"" to "~0" and "/" to "~1" + static std::string escape(std::string s); + + /// unescape "~1" to tilde and "~0" to slash (order is important!) + static void unescape(std::string& s); + + /*! + @param[in] reference_string the reference string to the current value + @param[in] value the value to consider + @param[in,out] result the result object to insert values to + + @note Empty objects or arrays are flattened to `null`. + */ + static void flatten(const Twine& reference_string, + const json& value, + json& result); + + /*! + @param[in] value flattened JSON + + @return unflattened JSON + + @throw parse_error.109 if array index is not a number + @throw type_error.314 if value is not an object + @throw type_error.315 if object values are not primitive + @throw type_error.313 if value cannot be unflattened + */ + static json + unflatten(const json& value); + + friend bool operator==(json_pointer const& lhs, + json_pointer const& rhs) noexcept + { + return (lhs.reference_tokens == rhs.reference_tokens); + } + + friend bool operator!=(json_pointer const& lhs, + json_pointer const& rhs) noexcept + { + return not (lhs == rhs); + } + + /// the reference tokens + std::vector reference_tokens; +}; + +template +struct adl_serializer +{ + /*! + @brief convert a JSON value to any value type + + This function is usually called by the `get()` function of the + @ref json class (either explicit or via conversion operators). + + @param[in] j JSON value to read from + @param[in,out] val value to write to + */ + template + static void from_json(BasicJsonType&& j, ValueType& val) noexcept( + noexcept(::wpi::from_json(std::forward(j), val))) + { + ::wpi::from_json(std::forward(j), val); + } + + /*! + @brief convert any value type to a JSON value + + This function is usually called by the constructors of the @ref json + class. + + @param[in,out] j JSON value to write to + @param[in] val value to read from + */ + template + static void to_json(BasicJsonType& j, ValueType&& val) noexcept( + noexcept(::wpi::to_json(j, std::forward(val)))) + { + ::wpi::to_json(j, std::forward(val)); + } +}; + /*! @brief a class to store JSON values @@ -1191,6 +2700,10 @@ relationship: - If `m_type == value_t::string`, then `m_value.string != nullptr`. The invariants are checked by member function assert_invariant(). +@internal +@note ObjectType trick from http://stackoverflow.com/a/9860911 +@endinternal + @see [RFC 7159: The JavaScript Object Notation (JSON) Data Interchange Format](http://rfc7159.net/rfc7159) @@ -1202,17 +2715,38 @@ class json { private: template friend struct detail::external_constructor; + friend ::wpi::json_pointer; + template + friend class ::wpi::detail::iter_impl; friend class JsonTest; + /// workaround type for MSVC + using json_t = json; + + // convenience aliases for types residing in namespace detail; + using primitive_iterator_t = ::wpi::detail::primitive_iterator_t; + template + using internal_iterator = ::wpi::detail::internal_iterator; + template + using iter_impl = ::wpi::detail::iter_impl; + template + using iteration_proxy = ::wpi::detail::iteration_proxy; + template using json_reverse_iterator = ::wpi::detail::json_reverse_iterator; + + class binary_reader; + class binary_writer; + class lexer; + class parser; + class serializer; + public: using value_t = detail::value_t; - // forward declarations - template class iter_impl; - class json_pointer; - - class parser; - class serializer; - class binary_writer; + /// @copydoc wpi::json_pointer + using json_pointer = ::wpi::json_pointer; + template + using json_serializer = adl_serializer; + /// helper type for initializer lists of json values + using initializer_list_t = std::initializer_list>; //////////////// // exceptions // @@ -1260,6 +2794,9 @@ class json /// a type to represent container sizes using size_type = std::size_t; + /// the allocator type + using allocator_type = std::allocator; + /// the type of an element pointer using pointer = json*; /// the type of an element const pointer @@ -1269,9 +2806,22 @@ class json using iterator = iter_impl; /// a const iterator for a json container using const_iterator = iter_impl; + /// a reverse iterator for a json container + using reverse_iterator = json_reverse_iterator; + /// a const reverse iterator for a json container + using const_reverse_iterator = json_reverse_iterator; /// @} + + /*! + @brief returns the allocator associated with the container + */ + static allocator_type get_allocator() + { + return allocator_type(); + } + /*! @brief returns version information on the library @@ -1291,12 +2841,16 @@ class json @liveexample{The following code shows an example output of the `meta()` function.,meta} + @exceptionsafety Strong guarantee: if an exception is thrown, there are no + changes to any JSON value. + @complexity Constant. @since 2.1.0 */ static json meta(); + /////////////////////////// // JSON value data types // /////////////////////////// @@ -1306,6 +2860,14 @@ class json /// the template arguments passed to class @ref json. /// @{ +#if defined(JSON_HAS_CPP_14) + // Use transparent comparator if possible, combined with perfect forwarding + // on find() and count() calls prevents unnecessary string construction. + using object_comparator_t = std::less<>; +#else + using object_comparator_t = std::less; +#endif + /*! @brief a type for an object @@ -1322,10 +2884,10 @@ class json - When all names are unique, objects will be interoperable in the sense that all software implementations receiving that object will agree on the name-value mappings. - - When the names within an object are not unique, later stored name/value - pairs overwrite previously stored name/value pairs, leaving the used - names unique. For instance, `{"key": 1}` and `{"key": 2, "key": 1}` will - be treated as equal and both stored as `{"key": 1}`. + - When the names within an object are not unique, it is unspecified which + one of the values for a given key will be chosen. For instance, + `{"key": 2, "key": 1}` could be equal to either `{"key": 1}` or + `{"key": 2}`. - Internally, name/value pairs are stored in lexicographical order of the names. Objects will also be serialized (see @ref dump) in this order. For instance, `{"b": 1, "a": 2}` and `{"a": 2, "b": 1}` will be stored @@ -1340,7 +2902,7 @@ class json [RFC 7159](http://rfc7159.net/rfc7159) specifies: > An implementation may set limits on the maximum depth of nesting. - In this class, the object's limit of nesting is not constraint explicitly. + In this class, the object's limit of nesting is not explicitly constrained. However, a maximum depth of nesting may be introduced by the compiler or runtime environment. A theoretical limit can be queried by calling the @ref max_size function of a JSON object. @@ -1351,8 +2913,6 @@ class json access to object values, a pointer of type `object_t*` must be dereferenced. - @sa @ref array_t -- type for an array value - @since version 1.0.0 @note The order name/value pairs are added to the object is *not* @@ -1371,25 +2931,12 @@ class json [RFC 7159](http://rfc7159.net/rfc7159) describes JSON arrays as follows: > An array is an ordered sequence of zero or more values. - To store objects in C++, a type is defined by the template parameters - explained below. - - #### Default type - - The default value for @a array_t is: - - @code {.cpp} - std::vector< - json // value_type - > - @endcode - #### Limits [RFC 7159](http://rfc7159.net/rfc7159) specifies: > An implementation may set limits on the maximum depth of nesting. - In this class, the array's limit of nesting is not constraint explicitly. + In this class, the array's limit of nesting is not explicitly constrained. However, a maximum depth of nesting may be introduced by the compiler or runtime environment. A theoretical limit can be queried by calling the @ref max_size function of a JSON array. @@ -1405,276 +2952,6 @@ class json */ using array_t = std::vector; - /*! - @brief a type for a string - - [RFC 7159](http://rfc7159.net/rfc7159) describes JSON strings as follows: - > A string is a sequence of zero or more Unicode characters. - - To store objects in C++, a type is defined by the template parameter - described below. Unicode values are split by the JSON class into - byte-sized characters during deserialization. - - #### Default type - - The default value for @a string_t is: - - @code {.cpp} - std::string - @endcode - - #### Encoding - - Strings are stored in UTF-8 encoding. Therefore, functions like - `std::string::size()` or `std::string::length()` return the number of - bytes in the string rather than the number of characters or glyphs. - - #### String comparison - - [RFC 7159](http://rfc7159.net/rfc7159) states: - > Software implementations are typically required to test names of object - > members for equality. Implementations that transform the textual - > representation into sequences of Unicode code units and then perform the - > comparison numerically, code unit by code unit, are interoperable in the - > sense that implementations will agree in all cases on equality or - > inequality of two strings. For example, implementations that compare - > strings with escaped characters unconverted may incorrectly find that - > `"a\\b"` and `"a\u005Cb"` are not equal. - - This implementation is interoperable as it does compare strings code unit - by code unit. - - #### Storage - - String values are stored as pointers in a @ref basic_json type. That is, - for any access to string values, a pointer of type `string_t*` must be - dereferenced. - - @since version 1.0.0 - */ - using string_t = std::string; - - /*! - @brief a type for a boolean - - [RFC 7159](http://rfc7159.net/rfc7159) implicitly describes a boolean as a - type which differentiates the two literals `true` and `false`. - - #### Default type - - The default value for @a boolean_t is: - - @code {.cpp} - bool - @endcode - - #### Storage - - Boolean values are stored directly inside a @ref basic_json type. - - @since version 1.0.0 - */ - using boolean_t = bool; - - /*! - @brief a type for a number (integer) - - [RFC 7159](http://rfc7159.net/rfc7159) describes numbers as follows: - > The representation of numbers is similar to that used in most - > programming languages. A number is represented in base 10 using decimal - > digits. It contains an integer component that may be prefixed with an - > optional minus sign, which may be followed by a fraction part and/or an - > exponent part. Leading zeros are not allowed. (...) Numeric values that - > cannot be represented in the grammar below (such as Infinity and NaN) - > are not permitted. - - This description includes both integer and floating-point numbers. - However, C++ allows more precise storage if it is known whether the number - is a signed integer, an unsigned integer or a floating-point number. - Therefore, three different types, @ref number_integer_t, @ref - number_unsigned_t and @ref number_float_t are used. - - #### Default type - - The default value for @a number_integer_t is: - - @code {.cpp} - int64_t - @endcode - - #### Default behavior - - - The restrictions about leading zeros is not enforced in C++. Instead, - leading zeros in integer literals lead to an interpretation as octal - number. Internally, the value will be stored as decimal number. For - instance, the C++ integer literal `010` will be serialized to `8`. - During deserialization, leading zeros yield an error. - - Not-a-number (NaN) values will be serialized to `null`. - - #### Limits - - [RFC 7159](http://rfc7159.net/rfc7159) specifies: - > An implementation may set limits on the range and precision of numbers. - - When the default type is used, the maximal integer number that can be - stored is `9223372036854775807` (INT64_MAX) and the minimal integer number - that can be stored is `-9223372036854775808` (INT64_MIN). Integer numbers - that are out of range will yield over/underflow when used in a - constructor. During deserialization, too large or small integer numbers - will be automatically be stored as @ref number_unsigned_t or @ref - number_float_t. - - [RFC 7159](http://rfc7159.net/rfc7159) further states: - > Note that when such software is used, numbers that are integers and are - > in the range \f$[-2^{53}+1, 2^{53}-1]\f$ are interoperable in the sense - > that implementations will agree exactly on their numeric values. - - As this range is a subrange of the exactly supported range [INT64_MIN, - INT64_MAX], this class's integer type is interoperable. - - #### Storage - - Integer number values are stored directly inside a @ref basic_json type. - - @sa @ref number_float_t -- type for number values (floating-point) - - @sa @ref number_unsigned_t -- type for number values (unsigned integer) - - @since version 1.0.0 - */ - using number_integer_t = int64_t; - - /*! - @brief a type for a number (unsigned) - - [RFC 7159](http://rfc7159.net/rfc7159) describes numbers as follows: - > The representation of numbers is similar to that used in most - > programming languages. A number is represented in base 10 using decimal - > digits. It contains an integer component that may be prefixed with an - > optional minus sign, which may be followed by a fraction part and/or an - > exponent part. Leading zeros are not allowed. (...) Numeric values that - > cannot be represented in the grammar below (such as Infinity and NaN) - > are not permitted. - - This description includes both integer and floating-point numbers. - However, C++ allows more precise storage if it is known whether the number - is a signed integer, an unsigned integer or a floating-point number. - Therefore, three different types, @ref number_integer_t, @ref - number_unsigned_t and @ref number_float_t are used. - - #### Default type - - The default value for @a number_unsigned_t is: - - @code {.cpp} - uint64_t - @endcode - - #### Default behavior - - - The restrictions about leading zeros is not enforced in C++. Instead, - leading zeros in integer literals lead to an interpretation as octal - number. Internally, the value will be stored as decimal number. For - instance, the C++ integer literal `010` will be serialized to `8`. - During deserialization, leading zeros yield an error. - - Not-a-number (NaN) values will be serialized to `null`. - - #### Limits - - [RFC 7159](http://rfc7159.net/rfc7159) specifies: - > An implementation may set limits on the range and precision of numbers. - - When the default type is used, the maximal integer number that can be - stored is `18446744073709551615` (UINT64_MAX) and the minimal integer - number that can be stored is `0`. Integer numbers that are out of range - will yield over/underflow when used in a constructor. During - deserialization, too large or small integer numbers will be automatically - be stored as @ref number_integer_t or @ref number_float_t. - - [RFC 7159](http://rfc7159.net/rfc7159) further states: - > Note that when such software is used, numbers that are integers and are - > in the range \f$[-2^{53}+1, 2^{53}-1]\f$ are interoperable in the sense - > that implementations will agree exactly on their numeric values. - - As this range is a subrange (when considered in conjunction with the - number_integer_t type) of the exactly supported range [0, UINT64_MAX], - this class's integer type is interoperable. - - #### Storage - - Integer number values are stored directly inside a @ref basic_json type. - - @sa @ref number_float_t -- type for number values (floating-point) - @sa @ref number_integer_t -- type for number values (integer) - - @since version 2.0.0 - */ - using number_unsigned_t = uint64_t; - - /*! - @brief a type for a number (floating-point) - - [RFC 7159](http://rfc7159.net/rfc7159) describes numbers as follows: - > The representation of numbers is similar to that used in most - > programming languages. A number is represented in base 10 using decimal - > digits. It contains an integer component that may be prefixed with an - > optional minus sign, which may be followed by a fraction part and/or an - > exponent part. Leading zeros are not allowed. (...) Numeric values that - > cannot be represented in the grammar below (such as Infinity and NaN) - > are not permitted. - - This description includes both integer and floating-point numbers. - However, C++ allows more precise storage if it is known whether the number - is a signed integer, an unsigned integer or a floating-point number. - Therefore, three different types, @ref number_integer_t, @ref - number_unsigned_t and @ref number_float_t are used. - - #### Default type - - The default value for @a number_float_t is: - - @code {.cpp} - double - @endcode - - #### Default behavior - - - The restrictions about leading zeros is not enforced in C++. Instead, - leading zeros in floating-point literals will be ignored. Internally, - the value will be stored as decimal number. For instance, the C++ - floating-point literal `01.2` will be serialized to `1.2`. During - deserialization, leading zeros yield an error. - - Not-a-number (NaN) values will be serialized to `null`. - - #### Limits - - [RFC 7159](http://rfc7159.net/rfc7159) states: - > This specification allows implementations to set limits on the range and - > precision of numbers accepted. Since software that implements IEEE - > 754-2008 binary64 (double precision) numbers is generally available and - > widely used, good interoperability can be achieved by implementations - > that expect no more precision or range than these provide, in the sense - > that implementations will approximate JSON numbers within the expected - > precision. - - This implementation does exactly follow this approach, as it uses double - precision floating-point numbers. Note values smaller than - `-1.79769313486232e+308` and values greater than `1.79769313486232e+308` - will be stored as NaN internally and be serialized to `null`. - - #### Storage - - Floating-point number values are stored directly inside a @ref basic_json - type. - - @sa @ref number_integer_t -- type for number values (integer) - - @sa @ref number_unsigned_t -- type for number values (unsigned integer) - - @since version 1.0.0 - */ - using number_float_t = double; - /// @} private: @@ -1684,6 +2961,7 @@ class json static T* create(Args&& ... args) { std::allocator alloc; + auto deleter = [&](T * object) { alloc.deallocate(object, 1); @@ -1698,7 +2976,6 @@ class json // JSON value storage // //////////////////////// - public: /*! @brief a JSON value @@ -1712,8 +2989,8 @@ class json array | array | pointer to @ref array_t string | string | pointer to std::string boolean | boolean | bool - number | number_integer | std::int64_t - number | number_unsigned | std::uint64_t + number | number_integer | int64_t + number | number_unsigned | uint64_t number | number_float | double null | null | *no value is stored* @@ -1734,9 +3011,9 @@ class json /// boolean bool boolean; /// number (integer) - std::int64_t number_integer; + int64_t number_integer; /// number (unsigned integer) - std::uint64_t number_unsigned; + uint64_t number_unsigned; /// number (floating-point) double number_float; @@ -1745,26 +3022,59 @@ class json /// constructor for booleans json_value(bool v) noexcept : boolean(v) {} /// constructor for numbers (integer) - json_value(std::int64_t v) noexcept : number_integer(v) {} + json_value(int64_t v) noexcept : number_integer(v) {} /// constructor for numbers (unsigned) - json_value(std::uint64_t v) noexcept : number_unsigned(v) {} + json_value(uint64_t v) noexcept : number_unsigned(v) {} /// constructor for numbers (floating-point) json_value(double v) noexcept : number_float(v) {} /// constructor for empty values of a given type json_value(value_t t); /// constructor for strings - json_value(StringRef value); - json_value(const std::string& value); + json_value(StringRef value) + { + string = create(value); + } + + /// constructor for strings + json_value(const std::string& value) + { + string = create(value); + } + + /// constructor for rvalue strings + json_value(std::string&& value) + { + string = create(std::move(value)); + } /// constructor for objects - json_value(const object_t& value); + json_value(const object_t& value) + { + object = create(value); + } + + /// constructor for rvalue objects + json_value(object_t&& value) + { + object = create(std::move(value)); + } /// constructor for arrays - json_value(const array_t& value); + json_value(const array_t& value) + { + array = create(value); + } + + /// constructor for rvalue arrays + json_value(array_t&& value) + { + array = create(std::move(value)); + } + + void destroy(value_t t) noexcept; }; - private: /*! @brief checks the class invariants @@ -1774,11 +3084,11 @@ class json value is changed, because the invariant expresses a relationship between @a m_type and @a m_value. */ - void assert_invariant() const + void assert_invariant() const noexcept { - assert(m_type != value_t::object || m_value.object != nullptr); - assert(m_type != value_t::array || m_value.array != nullptr); - assert(m_type != value_t::string || m_value.string != nullptr); + assert(m_type != value_t::object or m_value.object != nullptr); + assert(m_type != value_t::array or m_value.array != nullptr); + assert(m_type != value_t::string or m_value.string != nullptr); } public: @@ -1787,14 +3097,19 @@ class json ////////////////////////// /*! - @brief JSON callback events + @brief parser event types - This enumeration lists the parser events that can trigger calling a - callback function of type @ref parser_callback_t during parsing. + The parser callback distinguishes the following events: + - `object_start`: the parser read `{` and started to process a JSON object + - `key`: the parser read a key of a value in an object + - `object_end`: the parser read `}` and finished processing a JSON object + - `array_start`: the parser read `[` and started to process a JSON array + - `array_end`: the parser read `]` and finished processing a JSON array + - `value`: the parser finished reading a JSON value @image html callback_events.png "Example when certain parse events are triggered" - @since version 1.0.0 + @sa @ref parser_callback_t for more information and examples */ enum class parse_event_t : uint8_t { @@ -1816,13 +3131,11 @@ class json @brief per-element parser callback type With a parser callback function, the result of parsing a JSON text can be - influenced. When passed to @ref parse(raw_istream&, const - parser_callback_t) or @ref parse(const CharT, const parser_callback_t), - it is called on certain events (passed as @ref parse_event_t via parameter - @a event) with a set recursion depth @a depth and context JSON value - @a parsed. The return value of the callback function is a boolean - indicating whether the element that emitted the callback shall be kept or - not. + influenced. When passed to @ref parse, it is called on certain events + (passed as @ref parse_event_t via parameter @a event) with a set recursion + depth @a depth and context JSON value @a parsed. The return value of the + callback function is a boolean indicating whether the element that emitted + the callback shall be kept or not. We distinguish six scenarios (determined by the event type) in which the callback function can be called. The following table describes the values @@ -1859,14 +3172,12 @@ class json should be kept (`true`) or not (`false`). In the latter case, it is either skipped completely or replaced by an empty discarded object. - @sa @ref parse(raw_istream&, parser_callback_t) or - @ref parse(const CharT, const parser_callback_t) for examples + @sa @ref parse for examples @since version 1.0.0 */ - using parser_callback_t = std::function; + using parser_callback_t = + std::function; ////////////////// @@ -1893,17 +3204,22 @@ class json object | `{}` array | `[]` - @param[in] value_type the type of the value to create + @param[in] v the type of the value to create @complexity Constant. + @exceptionsafety Strong guarantee: if an exception is thrown, there are no + changes to any JSON value. + @liveexample{The following code shows the constructor for different @ref value_t values,json__value_t} + @sa @ref clear() -- restores the postcondition of this constructor + @since version 1.0.0 */ - json(const value_t value_type) - : m_type(value_type), m_value(value_type) + json(const value_t v) + : m_type(v), m_value(v) { assert_invariant(); } @@ -1936,25 +3252,25 @@ class json @brief create a JSON value This is a "catch all" constructor for all compatible JSON types; that is, - types for which a `to_json()` method exsits. The constructor forwards the + types for which a `to_json()` method exists. The constructor forwards the parameter @a val to that method (to `json_serializer::to_json` method with `U = uncvref_t`, to be exact). Template type @a CompatibleType includes, but is not limited to, the following types: - **arrays**: @ref array_t and all kinds of compatible containers such as - `std::vector`, `std::deque`, `std::list`, `std::forward_list`, - `std::array`, `std::set`, `std::unordered_set`, `std::multiset`, and - `unordered_multiset` with a `value_type` from which a @ref json - value can be constructed. + `std::vector`, `std::deque`, `std::list`, + `std::array`, `std::set`, `std::unordered_set`, + `std::multiset`, and `std::unordered_multiset` with a `value_type` from + which a @ref json value can be constructed. - **objects**: @ref object_t and all kinds of compatible associative containers such as `std::map`, `std::unordered_map`, `std::multimap`, and `std::unordered_multimap` with a `key_type` compatible to - std::string and a `value_type` from which a @ref json value can + `std::string` and a `value_type` from which a @ref json value can be constructed. - - **strings**: std::string, string literals, and all compatible string + - **strings**: `std::string`, string literals, and all compatible string containers can be used. - - **numbers**: std::int64_t, std::uint64_t, + - **numbers**: int64_t, uint64_t, double, and all convertible number types such as `int`, `size_t`, `int64_t`, `float` or `double` can be used. - **boolean**: `bool` can be used. @@ -1962,37 +3278,42 @@ class json See the examples below. @tparam CompatibleType a type such that: - - @a CompatibleType is not derived from `raw_istream`, + - @a CompatibleType is not derived from `std::istream`, - @a CompatibleType is not @ref json (to avoid hijacking copy/move constructors), + - @a CompatibleType is not a different @ref json type (i.e. with different template arguments) - @a CompatibleType is not a @ref json nested type (e.g., @ref json_pointer, @ref iterator, etc ...) - @ref @ref json_serializer has a - `to_json(json&, CompatibleType&&)` method + `to_json(json_t&, CompatibleType&&)` method @tparam U = `uncvref_t` - @param[in] val the value to be forwarded + @param[in] val the value to be forwarded to the respective constructor @complexity Usually linear in the size of the passed @a val, also depending on the implementation of the called `to_json()` method. - @throw what `json_serializer::to_json()` throws + @exceptionsafety Depends on the called constructor. For types directly + supported by the library (i.e., all types for which no `to_json()` function + was provided), strong guarantee holds: if an exception is thrown, there are + no changes to any JSON value. @liveexample{The following code shows the constructor with several compatible types.,json__CompatibleType} @since version 2.1.0 */ - template, - detail::enable_if_t::value && - !std::is_same::value && - !detail::is_json_nested_type::value, - int> = 0> - json(CompatibleType && val) + template , + detail::enable_if_t< + detail::is_compatible_type::value, int> = 0> + json(CompatibleType && val) noexcept(noexcept( + adl_serializer::to_json(std::declval(), + std::forward(val)))) { - to_json(*this, std::forward(val)); + adl_serializer::to_json(*this, std::forward(val)); assert_invariant(); } @@ -2015,7 +3336,7 @@ class json 1. The empty initializer list is written as `{}` which is exactly an empty JSON object. - 2. C++ has now way of describing mapped types other than to list a list of + 2. C++ has no way of describing mapped types other than to list a list of pairs. As JSON requires that keys must be of type string, rule 2 is the weakest constraint one can pose on initializer lists to interpret them as an object. @@ -2025,10 +3346,10 @@ class json With the rules described above, the following JSON values cannot be expressed by an initializer list: - - the empty array (`[]`): use @ref array(std::initializer_list) + - the empty array (`[]`): use @ref array(initializer_list_t) with an empty initializer list in this case - arrays whose elements satisfy rule 2: use @ref - array(std::initializer_list) with the same initializer list + array(initializer_list_t) with the same initializer list in this case @note When used without parentheses around an empty initializer list, @ref @@ -2040,8 +3361,8 @@ class json @param[in] type_deduction internal parameter; when set to `true`, the type of the JSON value is deducted from the initializer list @a init; when set to `false`, the type provided via @a manual_type is forced. This mode is - used by the functions @ref array(std::initializer_list) and - @ref object(std::initializer_list). + used by the functions @ref array(initializer_list_t) and + @ref object(initializer_list_t). @param[in] manual_type internal parameter; when @a type_deduction is set to `false`, the created JSON value will use the provided type (only @ref @@ -2052,22 +3373,25 @@ class json `value_t::object`, but @a init contains an element which is not a pair whose first element is a string. In this case, the constructor could not create an object. If @a type_deduction would have be `true`, an array - would have been created. See @ref object(std::initializer_list) + would have been created. See @ref object(initializer_list_t) for an example. @complexity Linear in the size of the initializer list @a init. + @exceptionsafety Strong guarantee: if an exception is thrown, there are no + changes to any JSON value. + @liveexample{The example below shows how JSON values are created from initializer lists.,json__list_init_t} - @sa @ref array(std::initializer_list) -- create a JSON array + @sa @ref array(initializer_list_t) -- create a JSON array value from an initializer list - @sa @ref object(std::initializer_list) -- create a JSON object + @sa @ref object(initializer_list_t) -- create a JSON object value from an initializer list @since version 1.0.0 */ - json(std::initializer_list init, + json(initializer_list_t init, bool type_deduction = true, value_t manual_type = value_t::array); @@ -2080,7 +3404,7 @@ class json @note This function is only needed to express two edge cases that cannot be realized with the initializer list constructor (@ref - json(std::initializer_list, bool, value_t)). These cases + json(initializer_list_t, bool, value_t)). These cases are: 1. creating an array whose elements are all pairs whose first element is a string -- in this case, the initializer list constructor would create an @@ -2095,18 +3419,20 @@ class json @complexity Linear in the size of @a init. + @exceptionsafety Strong guarantee: if an exception is thrown, there are no + changes to any JSON value. + @liveexample{The following code shows an example for the `array` function.,array} - @sa @ref json(std::initializer_list, bool, value_t) -- + @sa @ref json(initializer_list_t, bool, value_t) -- create a JSON value from an initializer list - @sa @ref object(std::initializer_list) -- create a JSON object + @sa @ref object(initializer_list_t) -- create a JSON object value from an initializer list @since version 1.0.0 */ - static json array(std::initializer_list init = - std::initializer_list()) + static json array(initializer_list_t init = {}) { return json(init, false, value_t::array); } @@ -2119,10 +3445,10 @@ class json the initializer list is empty, the empty object `{}` is created. @note This function is only added for symmetry reasons. In contrast to the - related function @ref array(std::initializer_list), there are + related function @ref array(initializer_list_t), there are no cases which can only be expressed by this function. That is, any initializer list @a init can also be passed to the initializer list - constructor @ref json(std::initializer_list, bool, value_t). + constructor @ref json(initializer_list_t, bool, value_t). @param[in] init initializer list to create an object from (optional) @@ -2130,24 +3456,26 @@ class json @throw type_error.301 if @a init is not a list of pairs whose first elements are strings. In this case, no object can be created. When such a - value is passed to @ref json(std::initializer_list, bool, value_t), + value is passed to @ref json(initializer_list_t, bool, value_t), an array would have been created from the passed initializer list @a init. See example below. @complexity Linear in the size of @a init. + @exceptionsafety Strong guarantee: if an exception is thrown, there are no + changes to any JSON value. + @liveexample{The following code shows an example for the `object` function.,object} - @sa @ref json(std::initializer_list, bool, value_t) -- + @sa @ref json(initializer_list_t, bool, value_t) -- create a JSON value from an initializer list - @sa @ref array(std::initializer_list) -- create a JSON array + @sa @ref array(initializer_list_t) -- create a JSON array value from an initializer list @since version 1.0.0 */ - static json object(std::initializer_list init = - std::initializer_list()) + static json object(initializer_list_t init = {}) { return json(init, false, value_t::object); } @@ -2156,14 +3484,18 @@ class json @brief construct an array with count copies of given value Constructs a JSON array value by creating @a cnt copies of a passed value. - In case @a cnt is `0`, an empty array is created. As postcondition, - `std::distance(begin(),end()) == cnt` holds. + In case @a cnt is `0`, an empty array is created. @param[in] cnt the number of JSON copies of @a val to create @param[in] val the JSON value to copy + @post `std::distance(begin(),end()) == cnt` holds. + @complexity Linear in @a cnt. + @exceptionsafety Strong guarantee: if an exception is thrown, there are no + changes to any JSON value. + @liveexample{The following code shows examples for the @ref json(size_type\, const json&) constructor.,json__size_type_json} @@ -2177,12 +3509,13 @@ class json Constructs the JSON value with the contents of the range `[first, last)`. The semantics depends on the different types a JSON value can have: - - In case of primitive types (number, boolean, or string), @a first must - be `begin()` and @a last must be `end()`. In this case, the value is + - In case of a null type, invalid_iterator.206 is thrown. + - In case of other primitive types (number, boolean, or string), @a first + must be `begin()` and @a last must be `end()`. In this case, the value is copied. Otherwise, invalid_iterator.204 is thrown. - In case of structured types (array, object), the constructor behaves as - similar versions for `std::vector`. - - In case of a null type, invalid_iterator.206 is thrown. + similar versions for `std::vector` or `std::map`; that is, a JSON array + or object is constructed from the values in the range. @tparam InputIT an input iterator type (@ref iterator or @ref const_iterator) @@ -2191,11 +3524,20 @@ class json @param[in] last end of the range to copy from (excluded) @pre Iterators @a first and @a last must be initialized. **This - precondition is enforced with an assertion.** + precondition is enforced with an assertion (see warning).** If + assertions are switched off, a violation of this precondition yields + undefined behavior. @pre Range `[first, last)` is valid. Usually, this precondition cannot be checked efficiently. Only certain edge cases are detected; see the - description of the exceptions below. + description of the exceptions below. A violation of this precondition + yields undefined behavior. + + @warning A precondition is enforced with a runtime assertion that will + result in calling `std::abort` if this precondition is not met. + Assertions can be disabled by defining `NDEBUG` at compile time. + See http://en.cppreference.com/w/cpp/error/assert for more + information. @throw invalid_iterator.201 if iterators @a first and @a last are not compatible (i.e., do not belong to the same JSON value). In this case, @@ -2209,21 +3551,24 @@ class json @complexity Linear in distance between @a first and @a last. + @exceptionsafety Strong guarantee: if an exception is thrown, there are no + changes to any JSON value. + @liveexample{The example below shows several ways to create JSON values by specifying a subrange with iterators.,json__InputIt_InputIt} @since version 1.0.0 */ template::value || - std::is_same::value, int>::type = 0> + std::is_same::value or + std::is_same::value, int>::type = 0> json(InputIT first, InputIT last) { assert(first.m_object != nullptr); assert(last.m_object != nullptr); // make sure iterator fits the current value - if (first.m_object != last.m_object) + if (JSON_UNLIKELY(first.m_object != last.m_object)) { JSON_THROW(invalid_iterator::create(201, "iterators are not compatible")); } @@ -2240,7 +3585,8 @@ class json case value_t::number_unsigned: case value_t::string: { - if (!first.m_it.primitive_iterator.is_begin() || !last.m_it.primitive_iterator.is_end()) + if (JSON_UNLIKELY(not first.m_it.primitive_iterator.is_begin() + or not last.m_it.primitive_iterator.is_end())) { JSON_THROW(invalid_iterator::create(204, "iterators out of range")); } @@ -2248,9 +3594,7 @@ class json } default: - { break; - } } switch (m_type) @@ -2293,10 +3637,8 @@ class json } default: - { JSON_THROW(invalid_iterator::create(206, "cannot construct with iterators from " + - first.m_object->type_name())); - } + Twine(first.m_object->type_name()))); } assert_invariant(); @@ -2307,6 +3649,11 @@ class json // other constructors and destructor // /////////////////////////////////////// + /// @private + json(const detail::json_ref& ref) + : json(ref.moved_or_copied()) + {} + /*! @brief copy constructor @@ -2314,8 +3661,13 @@ class json @param[in] other the JSON value to copy + @post `*this == other` + @complexity Linear in the size of @a other. + @exceptionsafety Strong guarantee: if an exception is thrown, there are no + changes to any JSON value. + @requirement This function helps `json` satisfying the [Container](http://en.cppreference.com/w/cpp/concept/Container) requirements: @@ -2338,10 +3690,18 @@ class json @param[in,out] other value to move to this object - @post @a other is a JSON null value + @post `*this` has the same value as @a other before the call. + @post @a other is a JSON null value. @complexity Constant. + @exceptionsafety No-throw guarantee: this constructor never throws + exceptions. + + @requirement This function helps `json` satisfying the + [MoveConstructible](http://en.cppreference.com/w/cpp/concept/MoveConstructible) + requirements. + @liveexample{The code below shows the move constructor explicitly called via std::move.,json__moveconstructor} @@ -2366,7 +3726,7 @@ class json Copy assignment operator. Copies a JSON value via the "copy and swap" strategy: It is expressed in terms of the copy constructor, destructor, - and the swap() member function. + and the `swap()` member function. @param[in] other value to copy from @@ -2385,9 +3745,9 @@ class json @since version 1.0.0 */ reference& operator=(json other) noexcept ( - std::is_nothrow_move_constructible::value && - std::is_nothrow_move_assignable::value && - std::is_nothrow_move_constructible::value && + std::is_nothrow_move_constructible::value and + std::is_nothrow_move_assignable::value and + std::is_nothrow_move_constructible::value and std::is_nothrow_move_assignable::value ) { @@ -2417,7 +3777,11 @@ class json @since version 1.0.0 */ - ~json(); + ~json() noexcept + { + assert_invariant(); + m_value.destroy(m_type); + } /// @} @@ -2435,49 +3799,42 @@ class json Serialization function for JSON values. The function tries to mimic Python's `json.dumps()` function, and currently supports its @a indent - parameter. + and @a ensure_ascii parameters. @param[in] indent If indent is nonnegative, then array elements and object members will be pretty-printed with that indent level. An indent level of `0` will only insert newlines. `-1` (the default) selects the most compact representation. + @param[in] indent_char The character to use for indentation if @a indent is + greater than `0`. The default is ` ` (space). + @param[in] ensure_ascii If @a ensure_ascii is true, all non-ASCII characters + in the output are escaped with `\uXXXX` sequences, and the result consists + of ASCII characters only. @return string containing the serialization of the JSON value - @complexity Linear. - - @liveexample{The following example shows the effect of different @a indent - parameters to the result of the serialization.,dump} - - @see https://docs.python.org/2/library/json.html#json.dump - - @since version 1.0.0; indentaction character added in version 3.0.0 - */ - std::string dump(const int indent = -1) const; - - /*! - @brief serialization - - Serialization function for JSON values. The function tries to mimic - Python's `json.dumps()` function, and currently supports its @a indent - parameter. - - @param[in,out] os Output stream - @param[in] indent If indent is nonnegative, then array elements and object - members will be pretty-printed with that indent level. An indent level of - `0` will only insert newlines. `-1` (the default) selects the most compact - representation. + @throw type_error.316 if a string stored inside the JSON value is not + UTF-8 encoded @complexity Linear. - @liveexample{The following example shows the effect of different @a indent - parameters to the result of the serialization.,dump} + @exceptionsafety Strong guarantee: if an exception is thrown, there are no + changes in the JSON value. + + @liveexample{The following example shows the effect of different @a indent\, + @a indent_char\, and @a ensure_ascii parameters to the result of the + serialization.,dump} @see https://docs.python.org/2/library/json.html#json.dump - @since version 1.0.0; indentaction character added in version 3.0.0 + @since version 1.0.0; indentation character @a indent_char, option + @a ensure_ascii and exceptions added in version 3.0.0 */ - void dump(raw_ostream& os, int indent = -1) const; + std::string dump(const int indent = -1, const char indent_char = ' ', + const bool ensure_ascii = false) const; + + void dump(raw_ostream& os, int indent = -1, const char indent_char = ' ', + const bool ensure_ascii = false) const; /*! @brief return the type of the JSON value (explicit) @@ -2486,6 +3843,17 @@ class json enumeration. @return the type of the JSON value + Value type | return value + ------------------------- | ------------------------- + null | value_t::null + boolean | value_t::boolean + string | value_t::string + number (integer) | value_t::number_integer + number (unsigned integer) | value_t::number_unsigned + number (floating-point) | value_t::number_float + object | value_t::object + array | value_t::array + discarded | value_t::discarded @complexity Constant. @@ -2495,6 +3863,9 @@ class json @liveexample{The following code exemplifies `type()` for all JSON types.,type} + @sa @ref operator value_t() -- return the type of the JSON value (implicit) + @sa @ref type_name() -- return the type as string + @since version 1.0.0 */ value_t type() const noexcept @@ -2505,8 +3876,8 @@ class json /*! @brief return whether type is primitive - This function returns true iff the JSON type is primitive (string, number, - boolean, or null). + This function returns true if and only if the JSON type is primitive + (string, number, boolean, or null). @return `true` if type is primitive (string, number, boolean, or null), `false` otherwise. @@ -2529,14 +3900,14 @@ class json */ bool is_primitive() const noexcept { - return is_null() || is_string() || is_boolean() || is_number(); + return is_null() or is_string() or is_boolean() or is_number(); } /*! @brief return whether type is structured - This function returns true iff the JSON type is structured (array or - object). + This function returns true if and only if the JSON type is structured + (array or object). @return `true` if type is structured (array or object), `false` otherwise. @@ -2556,13 +3927,13 @@ class json */ bool is_structured() const noexcept { - return is_array() || is_object(); + return is_array() or is_object(); } /*! @brief return whether value is null - This function returns true iff the JSON value is null. + This function returns true if and only if the JSON value is null. @return `true` if type is null, `false` otherwise. @@ -2578,13 +3949,13 @@ class json */ bool is_null() const noexcept { - return m_type == value_t::null; + return (m_type == value_t::null); } /*! @brief return whether value is a boolean - This function returns true iff the JSON value is a boolean. + This function returns true if and only if the JSON value is a boolean. @return `true` if type is boolean, `false` otherwise. @@ -2600,14 +3971,14 @@ class json */ bool is_boolean() const noexcept { - return m_type == value_t::boolean; + return (m_type == value_t::boolean); } /*! @brief return whether value is a number - This function returns true iff the JSON value is a number. This includes - both integer and floating-point values. + This function returns true if and only if the JSON value is a number. This + includes both integer (signed and unsigned) and floating-point values. @return `true` if type is number (regardless whether integer, unsigned integer or floating-type), `false` otherwise. @@ -2630,14 +4001,14 @@ class json */ bool is_number() const noexcept { - return is_number_integer() || is_number_float(); + return is_number_integer() or is_number_float(); } /*! @brief return whether value is an integer number - This function returns true iff the JSON value is an integer or unsigned - integer number. This excludes floating-point values. + This function returns true if and only if the JSON value is a signed or + unsigned integer number. This excludes floating-point values. @return `true` if type is an integer or unsigned integer number, `false` otherwise. @@ -2659,14 +4030,14 @@ class json */ bool is_number_integer() const noexcept { - return m_type == value_t::number_integer || m_type == value_t::number_unsigned; + return (m_type == value_t::number_integer or m_type == value_t::number_unsigned); } /*! @brief return whether value is an unsigned integer number - This function returns true iff the JSON value is an unsigned integer - number. This excludes floating-point and (signed) integer values. + This function returns true if and only if the JSON value is an unsigned + integer number. This excludes floating-point and signed integer values. @return `true` if type is an unsigned integer number, `false` otherwise. @@ -2687,14 +4058,14 @@ class json */ bool is_number_unsigned() const noexcept { - return m_type == value_t::number_unsigned; + return (m_type == value_t::number_unsigned); } /*! @brief return whether value is a floating-point number - This function returns true iff the JSON value is a floating-point number. - This excludes integer and unsigned integer values. + This function returns true if and only if the JSON value is a + floating-point number. This excludes signed and unsigned integer values. @return `true` if type is a floating-point number, `false` otherwise. @@ -2715,13 +4086,13 @@ class json */ bool is_number_float() const noexcept { - return m_type == value_t::number_float; + return (m_type == value_t::number_float); } /*! @brief return whether value is an object - This function returns true iff the JSON value is an object. + This function returns true if and only if the JSON value is an object. @return `true` if type is object, `false` otherwise. @@ -2737,13 +4108,13 @@ class json */ bool is_object() const noexcept { - return m_type == value_t::object; + return (m_type == value_t::object); } /*! @brief return whether value is an array - This function returns true iff the JSON value is an array. + This function returns true if and only if the JSON value is an array. @return `true` if type is array, `false` otherwise. @@ -2759,13 +4130,13 @@ class json */ bool is_array() const noexcept { - return m_type == value_t::array; + return (m_type == value_t::array); } /*! @brief return whether value is a string - This function returns true iff the JSON value is a string. + This function returns true if and only if the JSON value is a string. @return `true` if type is string, `false` otherwise. @@ -2781,14 +4152,14 @@ class json */ bool is_string() const noexcept { - return m_type == value_t::string; + return (m_type == value_t::string); } /*! @brief return whether value is discarded - This function returns true iff the JSON value was discarded during parsing - with a callback function (see @ref parser_callback_t). + This function returns true if and only if the JSON value was discarded + during parsing with a callback function (see @ref parser_callback_t). @note This function will always be `false` for JSON values after parsing. That is, discarded values can only occur during parsing, but will be @@ -2808,7 +4179,7 @@ class json */ bool is_discarded() const noexcept { - return m_type == value_t::discarded; + return (m_type == value_t::discarded); } /*! @@ -2827,6 +4198,9 @@ class json @liveexample{The following code exemplifies the @ref value_t operator for all JSON types.,operator__value_t} + @sa @ref type() -- return the type of the JSON value (explicit) + @sa @ref type_name() -- return the type as string + @since version 1.0.0 */ operator value_t() const noexcept @@ -2844,12 +4218,12 @@ class json /// get a boolean (explicit) bool get_impl(bool* /*unused*/) const { - if (is_boolean()) + if (JSON_LIKELY(is_boolean())) { return m_value.boolean; } - JSON_THROW(type_error::create(302, "type must be boolean, but is " + type_name())); + JSON_THROW(type_error::create(302, "type must be boolean, but is " + Twine(type_name()))); } /// get a pointer to the value (object) @@ -2901,25 +4275,25 @@ class json } /// get a pointer to the value (integer number) - std::int64_t* get_impl_ptr(std::int64_t* /*unused*/) noexcept + int64_t* get_impl_ptr(int64_t* /*unused*/) noexcept { return is_number_integer() ? &m_value.number_integer : nullptr; } /// get a pointer to the value (integer number) - const std::int64_t* get_impl_ptr(const std::int64_t* /*unused*/) const noexcept + const int64_t* get_impl_ptr(const int64_t* /*unused*/) const noexcept { return is_number_integer() ? &m_value.number_integer : nullptr; } /// get a pointer to the value (unsigned number) - std::uint64_t* get_impl_ptr(std::uint64_t* /*unused*/) noexcept + uint64_t* get_impl_ptr(uint64_t* /*unused*/) noexcept { return is_number_unsigned() ? &m_value.number_unsigned : nullptr; } /// get a pointer to the value (unsigned number) - const std::uint64_t* get_impl_ptr(const std::uint64_t* /*unused*/) const noexcept + const uint64_t* get_impl_ptr(const uint64_t* /*unused*/) const noexcept { return is_number_unsigned() ? &m_value.number_unsigned : nullptr; } @@ -2939,7 +4313,7 @@ class json /*! @brief helper function to implement get_ref() - This funcion helps to implement get_ref() without code duplication for + This function helps to implement get_ref() without code duplication for const and non-const overloads @tparam ThisType will be deduced as `json` or `const json` @@ -2950,18 +4324,15 @@ class json template static ReferenceType get_ref_impl(ThisType& obj) { - // helper type - using PointerType = typename std::add_pointer::type; - // delegate the call to get_ptr<>() - auto ptr = obj.template get_ptr(); + auto ptr = obj.template get_ptr::type>(); - if (ptr != nullptr) + if (JSON_LIKELY(ptr != nullptr)) { return *ptr; } - JSON_THROW(type_error::create(303, "incompatible ReferenceType for get_ref, actual type is " + obj.type_name())); + JSON_THROW(type_error::create(303, "incompatible ReferenceType for get_ref, actual type is " + Twine(obj.type_name()))); } public: @@ -2983,11 +4354,9 @@ class json @since version 2.1.0 */ - template < - typename BasicJsonType, - detail::enable_if_t::type, - json>::value, - int> = 0 > + template::type, json_t>::value, + int> = 0> json get() const { return *this; @@ -3032,25 +4401,71 @@ class json @since version 2.1.0 */ - template < - typename ValueTypeCV, - typename ValueType = detail::uncvref_t, - detail::enable_if_t < !std::is_same::value, int > = 0 > - ValueType get() const + template, + detail::enable_if_t < + not detail::is_json::value and + detail::has_from_json::value and + not detail::has_non_default_from_json::value, + int> = 0> + ValueType get() const noexcept(noexcept( + adl_serializer::from_json(std::declval(), std::declval()))) { // we cannot static_assert on ValueTypeCV being non-const, because - // there is support for get(), which is why we + // there is support for get(), which is why we // still need the uncvref - static_assert(!std::is_reference::value, + static_assert(not std::is_reference::value, "get() cannot be used with reference types, you might want to use get_ref()"); static_assert(std::is_default_constructible::value, "types must be DefaultConstructible when used with get()"); ValueType ret; - from_json(*this, ret); + adl_serializer::from_json(*this, ret); return ret; } + /*! + @brief get a value (explicit); special case + + Explicit type conversion between the JSON value and a compatible value + which is **not** [CopyConstructible](http://en.cppreference.com/w/cpp/concept/CopyConstructible) + and **not** [DefaultConstructible](http://en.cppreference.com/w/cpp/concept/DefaultConstructible). + The value is converted by calling the @ref json_serializer + `from_json()` method. + + The function is equivalent to executing + @code {.cpp} + return adl_serializer::from_json(*this); + @endcode + + This overloads is chosen if: + - @a ValueType is not @ref json and + - @ref json_serializer has a `from_json()` method of the form + `ValueType from_json(const json&)` + + @note If @ref json_serializer has both overloads of + `from_json()`, this one is chosen. + + @tparam ValueTypeCV the provided value type + @tparam ValueType the returned value type + + @return copy of the JSON value, converted to @a ValueType + + @throw what @ref json_serializer `from_json()` method throws + + @since version 2.1.0 + */ + template, + detail::enable_if_t::value and + detail::has_non_default_from_json::value, + int> = 0> + ValueType get() const noexcept(noexcept( + adl_serializer::from_json(std::declval()))) + { + static_assert(not std::is_reference::value, + "get() cannot be used with reference types, you might want to use get_ref()"); + return adl_serializer::from_json(*this); + } + /*! @brief get a pointer value (explicit) @@ -3061,8 +4476,7 @@ class json changes. @tparam PointerType pointer type; must be a pointer to @ref array_t, @ref - object_t, std::string, bool, std::int64_t, - std::uint64_t, or double. + object_t, `std::string`, bool, int64_t, uint64_t, or double. @return pointer to the internally stored JSON value if the requested pointer type @a PointerType fits to the JSON value; `nullptr` otherwise @@ -3108,9 +4522,8 @@ class json state. @tparam PointerType pointer type; must be a pointer to @ref array_t, @ref - object_t, std::string, bool, std::int64_t, - std::uint64_t, or double. Enforced by a static - assertion. + object_t, `std::string`, bool, int64_t, + uint64_t, or double. Enforced by a static assertion. @return pointer to the internally stored JSON value if the requested pointer type @a PointerType fits to the JSON value; `nullptr` otherwise @@ -3135,12 +4548,12 @@ class json // make sure the type matches the allowed types static_assert( std::is_same::value - || std::is_same::value - || std::is_same::value - || std::is_same::value - || std::is_same::value - || std::is_same::value - || std::is_same::value + or std::is_same::value + or std::is_same::value + or std::is_same::value + or std::is_same::value + or std::is_same::value + or std::is_same::value , "incompatible pointer type"); // delegate the call to get_impl_ptr<>() @@ -3152,7 +4565,7 @@ class json @copydoc get_ptr() */ template::value && + std::is_pointer::value and std::is_const::type>::value, int>::type = 0> const PointerType get_ptr() const noexcept { @@ -3163,16 +4576,16 @@ class json // make sure the type matches the allowed types static_assert( std::is_same::value - || std::is_same::value - || std::is_same::value - || std::is_same::value - || std::is_same::value - || std::is_same::value - || std::is_same::value + or std::is_same::value + or std::is_same::value + or std::is_same::value + or std::is_same::value + or std::is_same::value + or std::is_same::value , "incompatible pointer type"); // delegate the call to get_impl_ptr<>() const - return get_impl_ptr(static_cast(nullptr)); + return get_impl_ptr(static_cast(nullptr)); } /*! @@ -3185,7 +4598,7 @@ class json state. @tparam ReferenceType reference type; must be a reference to @ref array_t, - @ref object_t, std::string, bool, std::int64_t, or + @ref object_t, std::string, bool, int64_t, or double. Enforced by static assertion. @return reference to the internally stored JSON value if the requested @@ -3214,7 +4627,7 @@ class json @copydoc get_ref() */ template::value && + std::is_reference::value and std::is_const::type>::value, int>::type = 0> ReferenceType get_ref() const { @@ -3230,7 +4643,7 @@ class json @tparam ValueType non-pointer type compatible to the JSON value, for instance `int` for JSON integer numbers, `bool` for JSON booleans, or - `std::vector` types for JSON arrays. The character type of std::string + `std::vector` types for JSON arrays. The character type of `std::string` as well as an initializer list of this type is excluded to avoid ambiguities as these types implicitly convert to `std::string`. @@ -3252,13 +4665,15 @@ class json @since version 1.0.0 */ template < typename ValueType, typename std::enable_if < - !std::is_pointer::value && - !std::is_same::value + not std::is_pointer::value and + not std::is_same>::value and + not std::is_same::value and + not detail::is_json::value #ifndef _MSC_VER // fix for issue #167 operator<< ambiguity under VS2015 - && !std::is_same>::value + and not std::is_same>::value #endif -#if (defined(__cplusplus) && __cplusplus >= 201703L) || (defined(_MSC_VER) && _MSC_VER >1900 && defined(_HAS_CXX17) && _HAS_CXX17 == 1) // fix for issue #464 - && !std::is_same::value +#if defined(JSON_HAS_CPP_17) + and not std::is_same::value #endif , int >::type = 0 > operator ValueType() const @@ -3354,7 +4769,7 @@ class json @complexity Logarithmic in the size of the container. - @sa @ref operator[](StringRef) for unchecked + @sa @ref operator[](const typename object_t::key_type&) for unchecked access by reference @sa @ref value() for access by value with a default value @@ -3386,7 +4801,7 @@ class json @complexity Logarithmic in the size of the container. - @sa @ref operator[](StringRef) for unchecked + @sa @ref operator[](const typename object_t::key_type&) for unchecked access by reference @sa @ref value() for access by value with a default value @@ -3434,7 +4849,7 @@ class json @return const reference to the element at index @a idx - @throw type_error.305 if the JSON value is not an array; in that cases, + @throw type_error.305 if the JSON value is not an array; in that case, using the [] operator with an index makes no sense. @complexity Constant. @@ -3467,7 +4882,7 @@ class json @liveexample{The example below shows how object elements can be read and written using the `[]` operator.,operatorarray__key_type} - @sa @ref at(StringRef) for access by reference + @sa @ref at(const typename object_t::key_type&) for access by reference with range checking @sa @ref value() for access by value with a default value @@ -3491,7 +4906,7 @@ class json @pre The element with key @a key must exist. **This precondition is enforced with an assertion.** - @throw type_error.305 if the JSON value is not an object; in that cases, + @throw type_error.305 if the JSON value is not an object; in that case, using the [] operator with a key makes no sense. @complexity Logarithmic in the size of the container. @@ -3499,7 +4914,7 @@ class json @liveexample{The example below shows how object elements can be read using the `[]` operator.,operatorarray__key_type_const} - @sa @ref at(StringRef) for access by reference + @sa @ref at(const typename object_t::key_type&) for access by reference with range checking @sa @ref value() for access by value with a default value @@ -3532,80 +4947,26 @@ class json with range checking @sa @ref value() for access by value with a default value - @since version 1.0.0 - */ - template - reference operator[](T * (&key)[n]) - { - return operator[](static_cast(key)); - } - - /*! - @brief read-only access specified object element - - Returns a const reference to the element at with specified key @a key. No - bounds checking is performed. - - @warning If the element with key @a key does not exist, the behavior is - undefined. - - @note This function is required for compatibility reasons with Clang. - - @param[in] key key of the element to access - - @return const reference to the element at key @a key - - @throw type_error.305 if the JSON value is not an object; in that cases, - using the [] operator with a key makes no sense. - - @complexity Logarithmic in the size of the container. - - @liveexample{The example below shows how object elements can be read using - the `[]` operator.,operatorarray__key_type_const} - - @sa @ref at(const typename object_t::key_type&) for access by reference - with range checking - @sa @ref value() for access by value with a default value - - @since version 1.0.0 - */ - template - const_reference operator[](T * (&key)[n]) const - { - return operator[](static_cast(key)); - } - - /*! - @brief access specified object element - - Returns a reference to the element at with specified key @a key. - - @note If @a key is not found in the object, then it is silently added to - the object and filled with a `null` value to make `key` a valid reference. - In case the value was `null` before, it is converted to an object. - - @param[in] key key of the element to access - - @return reference to the element at key @a key - - @throw type_error.305 if the JSON value is not an object or null; in that - cases, using the [] operator with a key makes no sense. - - @complexity Logarithmic in the size of the container. - - @liveexample{The example below shows how object elements can be read and - written using the `[]` operator.,operatorarray__key_type} - - @sa @ref at(const typename object_t::key_type&) for access by reference - with range checking - @sa @ref value() for access by value with a default value - @since version 1.1.0 */ template reference operator[](T* key) { - return this->operator[](StringRef(key)); + // implicitly convert null to object + if (is_null()) + { + m_type = value_t::object; + m_value = value_t::object; + assert_invariant(); + } + + // at only works for objects + if (JSON_LIKELY(is_object())) + { + return m_value.object->operator[](key); + } + + JSON_THROW(type_error::create(305, "cannot use operator[] with " + Twine(type_name()))); } /*! @@ -3624,7 +4985,7 @@ class json @pre The element with key @a key must exist. **This precondition is enforced with an assertion.** - @throw type_error.305 if the JSON value is not an object; in that cases, + @throw type_error.305 if the JSON value is not an object; in that case, using the [] operator with a key makes no sense. @complexity Logarithmic in the size of the container. @@ -3641,7 +5002,14 @@ class json template const_reference operator[](T* key) const { - return this->operator[](StringRef(key)); + // at only works for objects + if (JSON_LIKELY(is_object())) + { + assert(m_value.object->find(key) != m_value.object->end()); + return m_value.object->find(key)->second; + } + + JSON_THROW(type_error::create(305, "cannot use operator[] with " + Twine(type_name()))); } /*! @@ -3659,10 +5027,10 @@ class json } @endcode - @note Unlike @ref at(StringRef), this function + @note Unlike @ref at(const typename object_t::key_type&), this function does not throw if the given key @a key was not found. - @note Unlike @ref operator[](StringRef key), this + @note Unlike @ref operator[](const typename object_t::key_type& key), this function does not implicitly add an element to the position defined by @a key. This function is furthermore also applicable to const objects. @@ -3677,7 +5045,7 @@ class json @return copy of the element at key @a key or @a default_value if @a key is not found - @throw type_error.306 if the JSON value is not an objec; in that cases, + @throw type_error.306 if the JSON value is not an object; in that case, using `value()` with a key makes no sense. @complexity Logarithmic in the size of the container. @@ -3685,19 +5053,19 @@ class json @liveexample{The example below shows how object elements can be queried with a default value.,json__value} - @sa @ref at(StringRef) for access by reference + @sa @ref at(const typename object_t::key_type&) for access by reference with range checking - @sa @ref operator[](StringRef) for unchecked + @sa @ref operator[](const typename object_t::key_type&) for unchecked access by reference @since version 1.0.0 */ template::value, int>::type = 0> - ValueType value(StringRef key, ValueType default_value) const + std::is_convertible::value, int>::type = 0> + ValueType value(StringRef key, const ValueType& default_value) const { // at only works for objects - if (is_object()) + if (JSON_LIKELY(is_object())) { // if key is found, return value and given default value otherwise const auto it = find(key); @@ -3708,15 +5076,13 @@ class json return default_value; } - else - { - JSON_THROW(type_error::create(306, "cannot use value() with " + type_name())); - } + + JSON_THROW(type_error::create(306, "cannot use value() with " + Twine(type_name()))); } /*! @brief overload for a default value of type const char* - @copydoc json::value(StringRef, ValueType) const + @copydoc json::value(const typename object_t::key_type&, ValueType) const */ std::string value(StringRef key, const char* default_value) const { @@ -3752,7 +5118,7 @@ class json @return copy of the element at key @a key or @a default_value if @a key is not found - @throw type_error.306 if the JSON value is not an objec; in that cases, + @throw type_error.306 if the JSON value is not an object; in that case, using `value()` with a key makes no sense. @complexity Logarithmic in the size of the container. @@ -3765,11 +5131,11 @@ class json @since version 2.0.2 */ template::value, int>::type = 0> - ValueType value(const json_pointer& ptr, ValueType default_value) const + std::is_convertible::value, int>::type = 0> + ValueType value(const json_pointer& ptr, const ValueType& default_value) const { // at only works for objects - if (is_object()) + if (JSON_LIKELY(is_object())) { // if pointer resolves a value, return it or use default value JSON_TRY @@ -3782,7 +5148,7 @@ class json } } - JSON_THROW(type_error::create(306, "cannot use value() with " + type_name())); + JSON_THROW(type_error::create(306, "cannot use value() with " + Twine(type_name()))); } /*! @@ -3863,12 +5229,22 @@ class json @since version 1.0.0 */ - reference back(); + reference back() + { + auto tmp = end(); + --tmp; + return *tmp; + } /*! @copydoc json::back() */ - const_reference back() const; + const_reference back() const + { + auto tmp = cend(); + --tmp; + return *tmp; + } /*! @brief remove element given an iterator @@ -3915,13 +5291,13 @@ class json @since version 1.0.0 */ template::value || - std::is_same::value, int>::type + std::is_same::value or + std::is_same::value, int>::type = 0> void erase(IteratorType pos) { // make sure iterator fits the current value - if (this != pos.m_object) + if (JSON_UNLIKELY(this != pos.m_object)) { JSON_THROW(invalid_iterator::create(202, "iterator does not fit current value")); } @@ -3934,7 +5310,7 @@ class json case value_t::number_unsigned: case value_t::string: { - if (!pos.m_it.primitive_iterator.is_begin()) + if (JSON_UNLIKELY(not pos.m_it.primitive_iterator.is_begin())) { JSON_THROW(invalid_iterator::create(205, "iterator out of range")); } @@ -3965,9 +5341,7 @@ class json } default: - { - JSON_THROW(type_error::create(307, "cannot use erase() with " + type_name())); - } + JSON_THROW(type_error::create(307, "cannot use erase() with " + Twine(type_name()))); } } @@ -4010,7 +5384,7 @@ class json types.,erase__IteratorType_IteratorType} @sa @ref erase(IteratorType) -- removes the element at a given position - @sa @ref erase(StringRef) -- removes the element + @sa @ref erase(const typename object_t::key_type&) -- removes the element from an object at the given key @sa @ref erase(const size_type) -- removes the element from an array at the given index @@ -4018,17 +5392,19 @@ class json @since version 1.0.0 */ template::value || - std::is_same::value, int>::type + std::is_same::value or + std::is_same::value, int>::type = 0> - void erase(IteratorType first, IteratorType last) + IteratorType erase(IteratorType first, IteratorType last) { // make sure iterator fits the current value - if (this != first.m_object || this != last.m_object) + if (JSON_UNLIKELY(this != first.m_object or this != last.m_object)) { JSON_THROW(invalid_iterator::create(203, "iterators do not fit current value")); } + IteratorType result = end(); + switch (m_type) { case value_t::boolean: @@ -4037,7 +5413,8 @@ class json case value_t::number_unsigned: case value_t::string: { - if (!first.m_it.primitive_iterator.is_begin() || !last.m_it.primitive_iterator.is_end()) + if (JSON_LIKELY(not first.m_it.primitive_iterator.is_begin() + or not last.m_it.primitive_iterator.is_end())) { JSON_THROW(invalid_iterator::create(204, "iterators out of range")); } @@ -4057,16 +5434,16 @@ class json case value_t::array: { - m_value.array->erase(first.m_it.array_iterator, - last.m_it.array_iterator); + result.m_it.array_iterator = m_value.array->erase(first.m_it.array_iterator, + last.m_it.array_iterator); break; } default: - { - JSON_THROW(type_error::create(307, "cannot use erase() with " + type_name())); - } + JSON_THROW(type_error::create(307, "cannot use erase() with " + Twine(type_name()))); } + + return result; } /*! @@ -4076,8 +5453,8 @@ class json @param[in] key value of the elements to remove - @return Number of elements removed. - The return value will always be `0` (@a key was not + @return Number of elements removed. If @a ObjectType is the default + `std::map` type, the return value will always be `0` (@a key was not found) or `1` (@a key was found). @post References and iterators to the erased elements are invalidated. @@ -4119,7 +5496,7 @@ class json @sa @ref erase(IteratorType) -- removes the element at a given position @sa @ref erase(IteratorType, IteratorType) -- removes the elements in the given range - @sa @ref erase(StringRef) -- removes the element + @sa @ref erase(const typename object_t::key_type&) -- removes the element from an object at the given key @since version 1.0.0 @@ -4146,7 +5523,7 @@ class json @note This method always returns @ref end() when executed on a JSON type that is not an object. - @param[in] key key value of the element to search for + @param[in] key key value of the element to search for. @return Iterator to an element with key equivalent to @a key. If no such element is found or the JSON value is not an object, past-the-end (see @@ -4162,15 +5539,15 @@ class json /*! @brief find an element in a JSON object - @copydoc find(StringRef) + @copydoc find(KeyT&&) */ const_iterator find(StringRef key) const; /*! @brief returns the number of occurrences of a key in a JSON object - Returns the number of elements with key @a key. - The return value will always be `0` (@a key was + Returns the number of elements with key @a key. If ObjectType is the + default `std::map` type, the return value will always be `0` (@a key was not found) or `1` (@a key was found). @note This method always returns `0` when executed on a JSON type that is @@ -4187,11 +5564,7 @@ class json @since version 1.0.0 */ - size_type count(StringRef key) const - { - // return 0 for all nonobject types - return is_object() ? m_value.object->count(key) : 0; - } + size_type count(StringRef key) const; /// @} @@ -4345,35 +5718,201 @@ class json return result; } - private: - // forward declaration - template class iteration_proxy; + /*! + @brief returns an iterator to the reverse-beginning + + Returns an iterator to the reverse-beginning; that is, the last element. + + @image html range-rbegin-rend.svg "Illustration from cppreference.com" + + @complexity Constant. + + @requirement This function helps `json` satisfying the + [ReversibleContainer](http://en.cppreference.com/w/cpp/concept/ReversibleContainer) + requirements: + - The complexity is constant. + - Has the semantics of `reverse_iterator(end())`. + + @liveexample{The following code shows an example for `rbegin()`.,rbegin} + + @sa @ref crbegin() -- returns a const reverse iterator to the beginning + @sa @ref rend() -- returns a reverse iterator to the end + @sa @ref crend() -- returns a const reverse iterator to the end + + @since version 1.0.0 + */ + reverse_iterator rbegin() noexcept + { + return reverse_iterator(end()); + } + + /*! + @copydoc json::crbegin() + */ + const_reverse_iterator rbegin() const noexcept + { + return crbegin(); + } + + /*! + @brief returns an iterator to the reverse-end + + Returns an iterator to the reverse-end; that is, one before the first + element. + + @image html range-rbegin-rend.svg "Illustration from cppreference.com" + + @complexity Constant. + + @requirement This function helps `json` satisfying the + [ReversibleContainer](http://en.cppreference.com/w/cpp/concept/ReversibleContainer) + requirements: + - The complexity is constant. + - Has the semantics of `reverse_iterator(begin())`. + + @liveexample{The following code shows an example for `rend()`.,rend} + + @sa @ref crend() -- returns a const reverse iterator to the end + @sa @ref rbegin() -- returns a reverse iterator to the beginning + @sa @ref crbegin() -- returns a const reverse iterator to the beginning + + @since version 1.0.0 + */ + reverse_iterator rend() noexcept + { + return reverse_iterator(begin()); + } + + /*! + @copydoc json::crend() + */ + const_reverse_iterator rend() const noexcept + { + return crend(); + } + + /*! + @brief returns a const reverse iterator to the last element + + Returns a const iterator to the reverse-beginning; that is, the last + element. + + @image html range-rbegin-rend.svg "Illustration from cppreference.com" + + @complexity Constant. + + @requirement This function helps `json` satisfying the + [ReversibleContainer](http://en.cppreference.com/w/cpp/concept/ReversibleContainer) + requirements: + - The complexity is constant. + - Has the semantics of `const_cast(*this).rbegin()`. + + @liveexample{The following code shows an example for `crbegin()`.,crbegin} + + @sa @ref rbegin() -- returns a reverse iterator to the beginning + @sa @ref rend() -- returns a reverse iterator to the end + @sa @ref crend() -- returns a const reverse iterator to the end + + @since version 1.0.0 + */ + const_reverse_iterator crbegin() const noexcept + { + return const_reverse_iterator(cend()); + } + + /*! + @brief returns a const reverse iterator to one before the first + + Returns a const reverse iterator to the reverse-end; that is, one before + the first element. + + @image html range-rbegin-rend.svg "Illustration from cppreference.com" + + @complexity Constant. + + @requirement This function helps `json` satisfying the + [ReversibleContainer](http://en.cppreference.com/w/cpp/concept/ReversibleContainer) + requirements: + - The complexity is constant. + - Has the semantics of `const_cast(*this).rend()`. + + @liveexample{The following code shows an example for `crend()`.,crend} + + @sa @ref rend() -- returns a reverse iterator to the end + @sa @ref rbegin() -- returns a reverse iterator to the beginning + @sa @ref crbegin() -- returns a const reverse iterator to the beginning + + @since version 1.0.0 + */ + const_reverse_iterator crend() const noexcept + { + return const_reverse_iterator(cbegin()); + } public: /*! - @brief wrapper to access iterator member functions in range-based for + @brief helper to access iterator member functions in range-based for This function allows to access @ref iterator::key() and @ref iterator::value() during range-based for loops. In these loops, a reference to the JSON values is returned, so there is no access to the underlying iterator. - @liveexample{The following code shows how the wrapper is used,iterator_wrapper} + For loop without `items()` function: - @note The name of this function is not yet final and may change in the - future. - */ - static iteration_proxy iterator_wrapper(reference cont) + @code{cpp} + for (auto it = j_object.begin(); it != j_object.end(); ++it) { - return iteration_proxy(cont); + std::cout << "key: " << it.key() << ", value:" << it.value() << '\n'; + } + @endcode + + Range-based for loop without `items()` function: + + @code{cpp} + for (auto it : j_object) + { + // "it" is of type json::reference and has no key() member + std::cout << "value: " << it << '\n'; + } + @endcode + + Range-based for loop with `items()` function: + + @code{cpp} + for (auto it : j_object.items()) + { + std::cout << "key: " << it.key() << ", value:" << it.value() << '\n'; + } + @endcode + + @note When iterating over an array, `key()` will return the index of the + element as string (see example). For primitive types (e.g., numbers), + `key()` returns an empty string. + + @return iteration proxy object wrapping @a ref with an interface to use in + range-based for loops + + @liveexample{The following code shows how the function is used.,items} + + @exceptionsafety Strong guarantee: if an exception is thrown, there are no + changes in the JSON value. + + @complexity Constant. + + @since version 3.x.x. + */ + iteration_proxy items() noexcept + { + return iteration_proxy(*this); } /*! - @copydoc iterator_wrapper(reference) + @copydoc items() */ - static iteration_proxy iterator_wrapper(const_reference cont) + iteration_proxy items() const noexcept { - return iteration_proxy(cont); + return iteration_proxy(*this); } /// @} @@ -4387,9 +5926,9 @@ class json /// @{ /*! - @brief checks whether the container is empty + @brief checks whether the container is empty. - Checks if a JSON value has no elements. + Checks if a JSON value has no elements (i.e. whether its @ref size is `0`). @return The return value depends on the different types and is defined as follows: @@ -4402,23 +5941,27 @@ class json object | result of function `object_t::empty()` array | result of function `array_t::empty()` - @note This function does not return whether a string stored as JSON value - is empty - it returns whether the JSON container itself is empty which is - false in the case of a string. + @liveexample{The following code uses `empty()` to check if a JSON + object contains any elements.,empty} @complexity Constant, as long as @ref array_t and @ref object_t satisfy the Container concept; that is, their `empty()` functions have constant complexity. + @iterators No changes. + + @exceptionsafety No-throw guarantee: this function never throws exceptions. + + @note This function does not return whether a string stored as JSON value + is empty - it returns whether the JSON container itself is empty which is + false in the case of a string. + @requirement This function helps `json` satisfying the [Container](http://en.cppreference.com/w/cpp/concept/Container) requirements: - The complexity is constant. - Has the semantics of `begin() == end()`. - @liveexample{The following code uses `empty()` to check if a JSON - object contains any elements.,empty} - @sa @ref size() -- returns the number of elements @since version 1.0.0 @@ -4441,23 +5984,27 @@ class json object | result of function object_t::size() array | result of function array_t::size() - @note This function does not return the length of a string stored as JSON - value - it returns the number of elements in the JSON value which is 1 in - the case of a string. + @liveexample{The following code calls `size()` on the different value + types.,size} @complexity Constant, as long as @ref array_t and @ref object_t satisfy the Container concept; that is, their size() functions have constant complexity. + @iterators No changes. + + @exceptionsafety No-throw guarantee: this function never throws exceptions. + + @note This function does not return the length of a string stored as JSON + value - it returns the number of elements in the JSON value which is 1 in + the case of a string. + @requirement This function helps `json` satisfying the [Container](http://en.cppreference.com/w/cpp/concept/Container) requirements: - The complexity is constant. - Has the semantics of `std::distance(begin(), end())`. - @liveexample{The following code calls `size()` on the different value - types.,size} - @sa @ref empty() -- checks whether the container is empty @sa @ref max_size() -- returns the maximal number of elements @@ -4483,10 +6030,17 @@ class json object | result of function `object_t::max_size()` array | result of function `array_t::max_size()` + @liveexample{The following code calls `max_size()` on the different value + types. Note the output is implementation specific.,max_size} + @complexity Constant, as long as @ref array_t and @ref object_t satisfy the Container concept; that is, their `max_size()` functions have constant complexity. + @iterators No changes. + + @exceptionsafety No-throw guarantee: this function never throws exceptions. + @requirement This function helps `json` satisfying the [Container](http://en.cppreference.com/w/cpp/concept/Container) requirements: @@ -4494,9 +6048,6 @@ class json - Has the semantics of returning `b.size()` where `b` is the largest possible JSON value. - @liveexample{The following code calls `max_size()` on the different value - types. Note the output is implementation specific.,max_size} - @sa @ref size() -- returns the number of elements @since version 1.0.0 @@ -4517,7 +6068,8 @@ class json @brief clears the contents Clears the content of a JSON value and resets it to the default value as - if @ref json(value_t) would have been called: + if @ref json(value_t) would have been called with the current value + type from @ref type(): Value type | initial value ----------- | ------------- @@ -4528,11 +6080,24 @@ class json object | `{}` array | `[]` - @complexity Linear in the size of the JSON value. + @post Has the same effect as calling + @code {.cpp} + *this = json(type()); + @endcode @liveexample{The example below shows the effect of `clear()` to different JSON types.,clear} + @complexity Linear in the size of the JSON value. + + @iterators All iterators, pointers and references related to this container + are invalidated. + + @exceptionsafety No-throw guarantee: this function never throws exceptions. + + @sa @ref json(value_t) -- constructor that creates an object with the + same value than calling `clear()` + @since version 1.0.0 */ void clear() noexcept; @@ -4605,13 +6170,33 @@ class json @since version 1.0.0 */ - void push_back(const std::pair& val); + template + void push_back(const std::pair& val) + { + // push_back only works for null objects or objects + if (JSON_UNLIKELY(not(is_null() or is_object()))) + { + JSON_THROW(type_error::create(308, "cannot use push_back() with " + Twine(type_name()))); + } + + // transform null object into an object + if (is_null()) + { + m_type = value_t::object; + m_value = value_t::object; + assert_invariant(); + } + + // add element to array + m_value.object->emplace_second(val.first, std::move(val.second)); + } /*! @brief add an object to an object @copydoc push_back(const typename object_t::value_type&) */ - reference operator+=(const std::pair& val) + template + reference operator+=(const std::pair& val) { push_back(val); return *this; @@ -4642,13 +6227,13 @@ class json @liveexample{The example shows how initializer lists are treated as objects when possible.,push_back__initializer_list} */ - void push_back(std::initializer_list init); + void push_back(initializer_list_t init); /*! @brief add an object to an object - @copydoc push_back(std::initializer_list) + @copydoc push_back(initializer_list_t) */ - reference operator+=(std::initializer_list init) + reference operator+=(initializer_list_t init) { push_back(init); return *this; @@ -4679,9 +6264,9 @@ class json void emplace_back(Args&& ... args) { // emplace_back only works for null objects or arrays - if (!(is_null() || is_array())) + if (JSON_UNLIKELY(not(is_null() or is_array()))) { - JSON_THROW(type_error::create(311, "cannot use emplace_back() with " + type_name())); + JSON_THROW(type_error::create(311, "cannot use emplace_back() with " + Twine(type_name()))); } // transform null object into an array @@ -4727,9 +6312,9 @@ class json std::pair emplace(StringRef key, Args&& ... args) { // emplace only works for null objects or arrays - if (!(is_null() || is_object())) + if (JSON_UNLIKELY(not(is_null() or is_object()))) { - JSON_THROW(type_error::create(311, "cannot use emplace() with " + type_name())); + JSON_THROW(type_error::create(311, "cannot use emplace() with " + Twine(type_name()))); } // transform null object into an object @@ -4865,7 +6450,7 @@ class json @since version 1.0.0 */ - iterator insert(const_iterator pos, std::initializer_list ilist); + iterator insert(const_iterator pos, initializer_list_t ilist); /*! @brief inserts elements @@ -4892,6 +6477,55 @@ class json */ void insert(const_iterator first, const_iterator last); + /*! + @brief updates a JSON object from another object, overwriting existing keys + + Inserts all values from JSON object @a j and overwrites existing keys. + + @param[in] j JSON object to read values from + + @throw type_error.312 if called on JSON values other than objects; example: + `"cannot use update() with string"` + + @complexity O(N*log(size() + N)), where N is the number of elements to + insert. + + @liveexample{The example shows how `update()` is used.,update} + + @sa https://docs.python.org/3.6/library/stdtypes.html#dict.update + + @since version 3.0.0 + */ + void update(const_reference j); + + /*! + @brief updates a JSON object from another object, overwriting existing keys + + Inserts all values from from range `[first, last)` and overwrites existing + keys. + + @param[in] first begin of the range of elements to insert + @param[in] last end of the range of elements to insert + + @throw type_error.312 if called on JSON values other than objects; example: + `"cannot use update() with string"` + @throw invalid_iterator.202 if iterator @a first or @a last does does not + point to an object; example: `"iterators first and last must point to + objects"` + @throw invalid_iterator.210 if @a first and @a last do not belong to the + same JSON value; example: `"iterators do not fit"` + + @complexity O(N*log(size() + N)), where N is the number of elements to + insert. + + @liveexample{The example shows how `update()` is used__range.,update} + + @sa https://docs.python.org/3.6/library/stdtypes.html#dict.update + + @since version 3.0.0 + */ + void update(const_iterator first, const_iterator last); + /*! @brief exchanges the values @@ -4910,9 +6544,9 @@ class json @since version 1.0.0 */ void swap(reference other) noexcept ( - std::is_nothrow_move_constructible::value && - std::is_nothrow_move_assignable::value && - std::is_nothrow_move_constructible::value && + std::is_nothrow_move_constructible::value and + std::is_nothrow_move_assignable::value and + std::is_nothrow_move_constructible::value and std::is_nothrow_move_assignable::value ) { @@ -4937,20 +6571,20 @@ class json @complexity Constant. @liveexample{The example below shows how arrays can be swapped with - `swap()`.,swap__array_t} + `swap()`.,swap__std_vector_json} @since version 1.0.0 */ void swap(array_t& other) { // swap only works for arrays - if (is_array()) + if (JSON_LIKELY(is_array())) { std::swap(*(m_value.array), other); } else { - JSON_THROW(type_error::create(310, "cannot use swap() with " + type_name())); + JSON_THROW(type_error::create(310, "cannot use swap() with " + Twine(type_name()))); } } @@ -4977,13 +6611,13 @@ class json void swap(object_t& other) { // swap only works for objects - if (is_object()) + if (JSON_LIKELY(is_object())) { std::swap(*(m_value.object), other); } else { - JSON_THROW(type_error::create(310, "cannot use swap() with " + type_name())); + JSON_THROW(type_error::create(310, "cannot use swap() with " + Twine(type_name()))); } } @@ -5003,20 +6637,20 @@ class json @complexity Constant. @liveexample{The example below shows how strings can be swapped with - `swap()`.,swap__std::string} + `swap()`.,swap__std_string} @since version 1.0.0 */ void swap(std::string& other) { // swap only works for strings - if (is_string()) + if (JSON_LIKELY(is_string())) { std::swap(*(m_value.string), other); } else { - JSON_THROW(type_error::create(310, "cannot use swap() with " + type_name())); + JSON_THROW(type_error::create(310, "cannot use swap() with " + Twine(type_name()))); } } @@ -5038,18 +6672,30 @@ class json their stored values are the same according to their respective `operator==`. - Integer and floating-point numbers are automatically converted before - comparison. Floating-point numbers are compared indirectly: two - floating-point numbers `f1` and `f2` are considered equal if neither - `f1 > f2` nor `f2 > f1` holds. Note than two NaN values are always - treated as unequal. + comparison. Note than two NaN values are always treated as unequal. - Two JSON null values are equal. + @note Floating-point inside JSON values numbers are compared with + `double::operator==`. + To compare floating-point while respecting an epsilon, an alternative + [comparison function](https://github.com/mariokonrad/marnav/blob/master/src/marnav/math/floatingpoint.hpp#L34-#L39) + could be used, for instance + @code {.cpp} + template::value, T>::type> + inline bool is_same(T a, T b, T epsilon = std::numeric_limits::epsilon()) noexcept + { + return std::abs(a - b) <= epsilon; + } + @endcode + @note NaN values never compare equal to themselves or to other NaN values. @param[in] lhs first JSON value to consider @param[in] rhs second JSON value to consider @return whether the values @a lhs and @a rhs are equal + @exceptionsafety No-throw guarantee: this function never throws exceptions. + @complexity Linear. @liveexample{The example demonstrates comparing several JSON @@ -5092,6 +6738,8 @@ class json @complexity Linear. + @exceptionsafety No-throw guarantee: this function never throws exceptions. + @liveexample{The example demonstrates comparing several JSON types.,operator__notequal} @@ -5099,7 +6747,7 @@ class json */ friend bool operator!=(const_reference lhs, const_reference rhs) noexcept { - return !(lhs == rhs); + return not (lhs == rhs); } /*! @@ -5143,6 +6791,8 @@ class json @complexity Linear. + @exceptionsafety No-throw guarantee: this function never throws exceptions. + @liveexample{The example demonstrates comparing several JSON types.,operator__less} @@ -5184,6 +6834,8 @@ class json @complexity Linear. + @exceptionsafety No-throw guarantee: this function never throws exceptions. + @liveexample{The example demonstrates comparing several JSON types.,operator__greater} @@ -5191,7 +6843,7 @@ class json */ friend bool operator<=(const_reference lhs, const_reference rhs) noexcept { - return !(rhs < lhs); + return not (rhs < lhs); } /*! @@ -5228,6 +6880,8 @@ class json @complexity Linear. + @exceptionsafety No-throw guarantee: this function never throws exceptions. + @liveexample{The example demonstrates comparing several JSON types.,operator__lessequal} @@ -5235,7 +6889,7 @@ class json */ friend bool operator>(const_reference lhs, const_reference rhs) noexcept { - return !(lhs <= rhs); + return not (lhs <= rhs); } /*! @@ -5272,6 +6926,8 @@ class json @complexity Linear. + @exceptionsafety No-throw guarantee: this function never throws exceptions. + @liveexample{The example demonstrates comparing several JSON types.,operator__greaterequal} @@ -5279,7 +6935,7 @@ class json */ friend bool operator>=(const_reference lhs, const_reference rhs) noexcept { - return !(lhs < rhs); + return not (lhs < rhs); } /*! @@ -5313,7 +6969,6 @@ class json /// @name serialization /// @{ - public: /*! @brief serialize to stream @@ -5325,7 +6980,7 @@ class json `std::setw(4)` on @a o sets the indentation level to `4` and the serialization result is the same as calling `dump(4)`. - - The indentation characrer can be controlled with the member variable + - The indentation character can be controlled with the member variable `fill` of the output stream @a o. For instance, the manipulator `std::setfill('\\t')` sets indentation to use a tab character rather than the default space character. @@ -5335,12 +6990,15 @@ class json @return the stream @a o + @throw type_error.316 if a string stored inside the JSON value is not + UTF-8 encoded + @complexity Linear. @liveexample{The example below shows the serialization with different parameters to `width` to adjust the indentation level.,operator_serialize} - @since version 1.0.0; indentaction character added in version 3.0.0 + @since version 1.0.0; indentation character added in version 3.0.0 */ friend raw_ostream& operator<<(raw_ostream& o, const json& j); @@ -5355,17 +7013,44 @@ class json /// @{ /*! - @brief deserialize from string literal + @brief deserialize from a compatible input - @tparam CharT character/literal type with size of 1 byte - @param[in] s string literal to read a serialized JSON value from - @param[in] cb a parser callback function of type @ref parser_callback_t + This function reads from a compatible input. Examples are: + - an array of 1-byte values + - strings with character/literal type with size of 1 byte + - input streams + - container with contiguous storage of 1-byte values. Compatible container + types include `std::vector`, `std::string`, `std::array`, + and `std::initializer_list`. Furthermore, C-style + arrays can be used with `std::begin()`/`std::end()`. User-defined + containers can be used as long as they implement random-access iterators + and a contiguous storage. + + @pre Each element of the container has a size of 1 byte. Violating this + precondition yields undefined behavior. **This precondition is enforced + with a static assertion.** + + @pre The container storage is contiguous. Violating this precondition + yields undefined behavior. **This precondition is enforced with an + assertion.** + @pre Each element of the container has a size of 1 byte. Violating this + precondition yields undefined behavior. **This precondition is enforced + with a static assertion.** + + @warning There is no way to enforce all preconditions at compile-time. If + the function is called with a noncompliant container and with + assertions switched off, the behavior is undefined and will most + likely yield segmentation violation. + + @param[in] i input to read from + @param[in] cb a parser callback function of type @ref parser_callback_t which is used to control the deserialization by filtering unwanted values (optional) @return result of the deserialization - @throw parse_error.101 in case of an unexpected token + @throw parse_error.101 if a parse error occurs; example: `""unexpected end + of input; expected string literal""` @throw parse_error.102 if to_unicode fails or surrogate error @throw parse_error.103 if to_unicode fails @@ -5374,51 +7059,41 @@ class json @a cb has a super-linear complexity. @note A UTF-8 byte order mark is silently ignored. - @note String containers like `std::string` can be parsed - with @ref parse(const ContiguousContainer&, const parser_callback_t) + + @liveexample{The example below demonstrates the `parse()` function reading + from an array.,parse__array__parser_callback_t} @liveexample{The example below demonstrates the `parse()` function with and without callback function.,parse__string__parser_callback_t} - @sa @ref parse(raw_istream&, const parser_callback_t) for a version - that reads from an input stream + @liveexample{The example below demonstrates the `parse()` function with + and without callback function.,parse__istream__parser_callback_t} - @since version 1.0.0 (originally for std::string) + @liveexample{The example below demonstrates the `parse()` function reading + from a contiguous container.,parse__contiguouscontainer__parser_callback_t} + + @since version 2.0.3 (contiguous containers) */ static json parse(StringRef s, - const parser_callback_t cb = nullptr); + const parser_callback_t cb = nullptr, + const bool allow_exceptions = true); + + static json parse(ArrayRef arr, + const parser_callback_t cb = nullptr, + const bool allow_exceptions = true); /*! - @brief deserialize from stream - - @param[in,out] i stream to read a serialized JSON value from - @param[in] cb a parser callback function of type @ref parser_callback_t - which is used to control the deserialization by filtering unwanted values - (optional) - - @return result of the deserialization - - @throw parse_error.101 in case of an unexpected token - @throw parse_error.102 if to_unicode fails or surrogate error - @throw parse_error.103 if to_unicode fails - @throw parse_error.111 if input stream is in a bad state - - @complexity Linear in the length of the input. The parser is a predictive - LL(1) parser. The complexity can be higher if the parser callback function - @a cb has a super-linear complexity. - - @note A UTF-8 byte order mark is silently ignored. - - @liveexample{The example below demonstrates the `parse()` function with - and without callback function.,parse__raw_istream__parser_callback_t} - - @sa @ref parse(const CharT, const parser_callback_t) for a version - that reads from a string - - @since version 1.0.0 + @copydoc json parse(raw_istream&, const parser_callback_t) */ static json parse(raw_istream& i, - const parser_callback_t cb = nullptr); + const parser_callback_t cb = nullptr, + const bool allow_exceptions = true); + + static bool accept(StringRef s); + + static bool accept(ArrayRef arr); + + static bool accept(raw_istream& i); /*! @brief deserialize from stream @@ -5431,7 +7106,6 @@ class json @throw parse_error.101 in case of an unexpected token @throw parse_error.102 if to_unicode fails or surrogate error @throw parse_error.103 if to_unicode fails - @throw parse_error.111 if input stream is in a bad state @complexity Linear in the length of the input. The parser is a predictive LL(1) parser. @@ -5441,7 +7115,7 @@ class json @liveexample{The example below shows how a JSON value is constructed by reading a serialization from a stream.,operator_deserialize} - @sa parse(raw_istream&, const parser_callback_t) for a variant with a + @sa parse(std::istream&, const parser_callback_t) for a variant with a parser callback function to filter values while parsing @since version 1.0.0 @@ -5460,16 +7134,32 @@ class json Returns the type name as string to be used in error messages - usually to indicate that a function was called on a wrong JSON type. - @return basically a string representation of a the @a m_type member + @return a string representation of a the @a m_type member: + Value type | return value + ----------- | ------------- + null | `"null"` + boolean | `"boolean"` + string | `"string"` + number | `"number"` (for all number types) + object | `"object"` + array | `"array"` + discarded | `"discarded"` + + @exceptionsafety No-throw guarantee: this function never throws exceptions. @complexity Constant. @liveexample{The following code exemplifies `type_name()` for all JSON types.,type_name} - @since version 1.0.0, public since 2.1.0 + @sa @ref type() -- return the type of the JSON value + @sa @ref operator value_t() -- return the type of the JSON value (implicit) + + @since version 1.0.0, public since 2.1.0, `const char*` and `noexcept` + since 3.0.0 */ - std::string type_name() const; + const char* type_name() const noexcept; + private: ////////////////////// @@ -5482,879 +7172,6 @@ class json /// the value of the current element json_value m_value = {}; - - private: - /////////////// - // iterators // - /////////////// - - /*! - @brief an iterator for primitive JSON types - - This class models an iterator for primitive JSON types (boolean, number, - string). It's only purpose is to allow the iterator/const_iterator classes - to "iterate" over primitive values. Internally, the iterator is modeled by - a `difference_type` variable. Value begin_value (`0`) models the begin, - end_value (`1`) models past the end. - */ - class primitive_iterator_t - { - public: - - difference_type get_value() const noexcept - { - return m_it; - } - /// set iterator to a defined beginning - void set_begin() noexcept - { - m_it = begin_value; - } - - /// set iterator to a defined past the end - void set_end() noexcept - { - m_it = end_value; - } - - /// return whether the iterator can be dereferenced - constexpr bool is_begin() const noexcept - { - return (m_it == begin_value); - } - - /// return whether the iterator is at end - constexpr bool is_end() const noexcept - { - return (m_it == end_value); - } - - friend constexpr bool operator==(primitive_iterator_t lhs, primitive_iterator_t rhs) noexcept - { - return lhs.m_it == rhs.m_it; - } - - friend constexpr bool operator!=(primitive_iterator_t lhs, primitive_iterator_t rhs) noexcept - { - return !(lhs == rhs); - } - - friend constexpr bool operator<(primitive_iterator_t lhs, primitive_iterator_t rhs) noexcept - { - return lhs.m_it < rhs.m_it; - } - - friend constexpr bool operator<=(primitive_iterator_t lhs, primitive_iterator_t rhs) noexcept - { - return lhs.m_it <= rhs.m_it; - } - - friend constexpr bool operator>(primitive_iterator_t lhs, primitive_iterator_t rhs) noexcept - { - return lhs.m_it > rhs.m_it; - } - - friend constexpr bool operator>=(primitive_iterator_t lhs, primitive_iterator_t rhs) noexcept - { - return lhs.m_it >= rhs.m_it; - } - - primitive_iterator_t operator+(difference_type i) - { - auto result = *this; - result += i; - return result; - } - - friend constexpr difference_type operator-(primitive_iterator_t lhs, primitive_iterator_t rhs) noexcept - { - return lhs.m_it - rhs.m_it; - } - - friend raw_ostream& operator<<(raw_ostream& os, primitive_iterator_t it) - { - return os << it.m_it; - } - - primitive_iterator_t& operator++() - { - ++m_it; - return *this; - } - - primitive_iterator_t operator++(int) - { - auto result = *this; - m_it++; - return result; - } - - primitive_iterator_t& operator--() - { - --m_it; - return *this; - } - - primitive_iterator_t operator--(int) - { - auto result = *this; - m_it--; - return result; - } - - primitive_iterator_t& operator+=(difference_type n) - { - m_it += n; - return *this; - } - - primitive_iterator_t& operator-=(difference_type n) - { - m_it -= n; - return *this; - } - - private: - static constexpr difference_type begin_value = 0; - static constexpr difference_type end_value = begin_value + 1; - - /// iterator as signed integer type - difference_type m_it = std::numeric_limits::denorm_min(); - }; - - /*! - @brief an iterator value - - @note This structure could easily be a union, but MSVC currently does not - allow unions members with complex constructors, see - https://github.com/nlohmann/json/pull/105. - */ - struct internal_iterator - { - /// iterator for JSON objects - typename object_t::iterator object_iterator; - /// iterator for JSON arrays - typename array_t::iterator array_iterator; - /// generic iterator for all other types - primitive_iterator_t primitive_iterator; - - /// create an uninitialized internal_iterator - internal_iterator() noexcept - : object_iterator(), array_iterator(), primitive_iterator() - {} - }; - - /// proxy class for the iterator_wrapper functions - template - class iteration_proxy - { - private: - /// helper class for iteration - class iteration_proxy_internal - { - private: - /// the iterator - IteratorType anchor; - /// an index for arrays (used to create key names) - size_t array_index = 0; - - public: - explicit iteration_proxy_internal(IteratorType it) noexcept - : anchor(it) - {} - - /// dereference operator (needed for range-based for) - iteration_proxy_internal& operator*() - { - return *this; - } - - /// increment operator (needed for range-based for) - iteration_proxy_internal& operator++() - { - ++anchor; - ++array_index; - - return *this; - } - - /// inequality operator (needed for range-based for) - bool operator!= (const iteration_proxy_internal& o) const - { - return anchor != o.anchor; - } - - /// return key of the iterator - std::string key() const - { - assert(anchor.m_object != nullptr); - - switch (anchor.m_object->type()) - { - // use integer array index as key - case value_t::array: - { - return std::to_string(array_index); - } - - // use key from the object - case value_t::object: - { - return anchor.key(); - } - - // use an empty key for all primitive types - default: - { - return ""; - } - } - } - - /// return value of the iterator - typename IteratorType::reference value() const - { - return anchor.value(); - } - }; - - /// the container to iterate - typename IteratorType::reference container; - - public: - /// construct iteration proxy from a container - explicit iteration_proxy(typename IteratorType::reference cont) - : container(cont) - {} - - /// return iterator begin (needed for range-based for) - iteration_proxy_internal begin() noexcept - { - return iteration_proxy_internal(container.begin()); - } - - /// return iterator end (needed for range-based for) - iteration_proxy_internal end() noexcept - { - return iteration_proxy_internal(container.end()); - } - }; - - public: - /*! - @brief a template for a random access iterator for the @ref json class - - This class implements a both iterators (iterator and const_iterator) for the - @ref json class. - - @note An iterator is called *initialized* when a pointer to a JSON value - has been set (e.g., by a constructor or a copy assignment). If the - iterator is default-constructed, it is *uninitialized* and most - methods are undefined. **The library uses assertions to detect calls - on uninitialized iterators.** - - @requirement The class satisfies the following concept requirements: - - [RandomAccessIterator](http://en.cppreference.com/w/cpp/concept/RandomAccessIterator): - The iterator that can be moved to point (forward and backward) to any - element in constant time. - - @since version 1.0.0, simplified in version 2.0.9 - */ - template - class iter_impl : public std::iterator - { - /// allow json to access private members - friend class json; - friend class JsonTest; - - // make sure U is json or const json - static_assert(std::is_same::value - || std::is_same::value, - "iter_impl only accepts (const) json"); - - public: - /// the type of the values when the iterator is dereferenced - using value_type = typename json::value_type; - /// a type to represent differences between iterators - using difference_type = typename json::difference_type; - /// defines a pointer to the type iterated over (value_type) - using pointer = typename std::conditional::value, - typename json::const_pointer, - typename json::pointer>::type; - /// defines a reference to the type iterated over (value_type) - using reference = typename std::conditional::value, - typename json::const_reference, - typename json::reference>::type; - /// the category of the iterator - using iterator_category = std::bidirectional_iterator_tag; - - /// default constructor - iter_impl() noexcept : m_object(nullptr) {} - - /*! - @brief constructor for a given JSON instance - @param[in] object pointer to a JSON object for this iterator - @pre object != nullptr - @post The iterator is initialized; i.e. `m_object != nullptr`. - */ - explicit iter_impl(pointer object) noexcept - : m_object(object) - { - assert(m_object != nullptr); - - switch (m_object->m_type) - { - case json::value_t::object: - { - m_it.object_iterator = typename object_t::iterator(); - break; - } - - case json::value_t::array: - { - m_it.array_iterator = typename array_t::iterator(); - break; - } - - default: - { - m_it.primitive_iterator = primitive_iterator_t(); - break; - } - } - } - - /*! - @note The conventional copy constructor and copy assignment are - implicitly defined. - Combined with the following converting constructor and assigment, - they support: copy from iterator to iterator, - copy from const iterator to const iterator, - and conversion from iterator to const iterator. - However conversion from const iterator to iterator is not defined. - */ - - /*! - @brief converting constructor - @param[in] other non-const iterator to copy from - @note It is not checked whether @a other is initialized. - */ - iter_impl(const iter_impl& other) noexcept - : m_object(other.m_object), m_it(other.m_it) - {} - - /*! - @brief converting assignment - @param[in,out] other non-const iterator to copy from - @return const/non-const iterator - @note It is not checked whether @a other is initialized. - */ - iter_impl& operator=(const iter_impl& other) noexcept - { - m_object = other.m_object; - m_it = other.m_it; - return *this; - } - - private: - /*! - @brief set the iterator to the first value - @pre The iterator is initialized; i.e. `m_object != nullptr`. - */ - void set_begin() noexcept - { - assert(m_object != nullptr); - - switch (m_object->m_type) - { - case json::value_t::object: - { - m_it.object_iterator = m_object->m_value.object->begin(); - break; - } - - case json::value_t::array: - { - m_it.array_iterator = m_object->m_value.array->begin(); - break; - } - - case json::value_t::null: - { - // set to end so begin()==end() is true: null is empty - m_it.primitive_iterator.set_end(); - break; - } - - default: - { - m_it.primitive_iterator.set_begin(); - break; - } - } - } - - /*! - @brief set the iterator past the last value - @pre The iterator is initialized; i.e. `m_object != nullptr`. - */ - void set_end() noexcept - { - assert(m_object != nullptr); - - switch (m_object->m_type) - { - case json::value_t::object: - { - m_it.object_iterator = m_object->m_value.object->end(); - break; - } - - case json::value_t::array: - { - m_it.array_iterator = m_object->m_value.array->end(); - break; - } - - default: - { - m_it.primitive_iterator.set_end(); - break; - } - } - } - - public: - /*! - @brief return a reference to the value pointed to by the iterator - @pre The iterator is initialized; i.e. `m_object != nullptr`. - */ - reference operator*() const - { - assert(m_object != nullptr); - - switch (m_object->m_type) - { - case json::value_t::object: - { - assert(m_it.object_iterator != m_object->m_value.object->end()); - return m_it.object_iterator->second; - } - - case json::value_t::array: - { - assert(m_it.array_iterator != m_object->m_value.array->end()); - return *m_it.array_iterator; - } - - case json::value_t::null: - { - JSON_THROW(invalid_iterator::create(214, "cannot get value")); - } - - default: - { - if (m_it.primitive_iterator.is_begin()) - { - return *m_object; - } - - JSON_THROW(invalid_iterator::create(214, "cannot get value")); - } - } - } - - /*! - @brief dereference the iterator - @pre The iterator is initialized; i.e. `m_object != nullptr`. - */ - pointer operator->() const - { - assert(m_object != nullptr); - - switch (m_object->m_type) - { - case json::value_t::object: - { - assert(m_it.object_iterator != m_object->m_value.object->end()); - return &(m_it.object_iterator->second); - } - - case json::value_t::array: - { - assert(m_it.array_iterator != m_object->m_value.array->end()); - return &*m_it.array_iterator; - } - - default: - { - if (m_it.primitive_iterator.is_begin()) - { - return m_object; - } - - JSON_THROW(invalid_iterator::create(214, "cannot get value")); - } - } - } - - /*! - @brief post-increment (it++) - @pre The iterator is initialized; i.e. `m_object != nullptr`. - */ - iter_impl operator++(int) - { - auto result = *this; - ++(*this); - return result; - } - - /*! - @brief pre-increment (++it) - @pre The iterator is initialized; i.e. `m_object != nullptr`. - */ - iter_impl& operator++() - { - assert(m_object != nullptr); - - switch (m_object->m_type) - { - case json::value_t::object: - { - ++m_it.object_iterator; - break; - } - - case json::value_t::array: - { - ++m_it.array_iterator; - break; - } - - default: - { - ++m_it.primitive_iterator; - break; - } - } - - return *this; - } - - /*! - @brief post-decrement (it--) - @pre The iterator is initialized; i.e. `m_object != nullptr`. - */ - iter_impl operator--(int) - { - auto result = *this; - --(*this); - return result; - } - - /*! - @brief pre-decrement (--it) - @pre The iterator is initialized; i.e. `m_object != nullptr`. - */ - iter_impl& operator--() - { - assert(m_object != nullptr); - - switch (m_object->m_type) - { - case json::value_t::object: - { - JSON_THROW(invalid_iterator::create(209, "cannot decrement object iterators")); - break; - } - - case json::value_t::array: - { - std::advance(m_it.array_iterator, -1); - break; - } - - default: - { - --m_it.primitive_iterator; - break; - } - } - - return *this; - } - - /*! - @brief comparison: equal - @pre The iterator is initialized; i.e. `m_object != nullptr`. - */ - bool operator==(const iter_impl& other) const - { - // if objects are not the same, the comparison is undefined - if (m_object != other.m_object) - { - JSON_THROW(invalid_iterator::create(212, "cannot compare iterators of different containers")); - } - - assert(m_object != nullptr); - - switch (m_object->m_type) - { - case json::value_t::object: - { - return (m_it.object_iterator == other.m_it.object_iterator); - } - - case json::value_t::array: - { - return (m_it.array_iterator == other.m_it.array_iterator); - } - - default: - { - return (m_it.primitive_iterator == other.m_it.primitive_iterator); - } - } - } - - /*! - @brief comparison: not equal - @pre The iterator is initialized; i.e. `m_object != nullptr`. - */ - bool operator!=(const iter_impl& other) const - { - return !operator==(other); - } - - /*! - @brief comparison: smaller - @pre The iterator is initialized; i.e. `m_object != nullptr`. - */ - bool operator<(const iter_impl& other) const - { - // if objects are not the same, the comparison is undefined - if (m_object != other.m_object) - { - JSON_THROW(invalid_iterator::create(212, "cannot compare iterators of different containers")); - } - - assert(m_object != nullptr); - - switch (m_object->m_type) - { - case json::value_t::object: - { - JSON_THROW(invalid_iterator::create(213, "cannot compare order of object iterators")); - } - - case json::value_t::array: - { - return (m_it.array_iterator < other.m_it.array_iterator); - } - - default: - { - return (m_it.primitive_iterator < other.m_it.primitive_iterator); - } - } - } - - /*! - @brief comparison: less than or equal - @pre The iterator is initialized; i.e. `m_object != nullptr`. - */ - bool operator<=(const iter_impl& other) const - { - return !(other.operator < (*this)); - } - - /*! - @brief comparison: greater than - @pre The iterator is initialized; i.e. `m_object != nullptr`. - */ - bool operator>(const iter_impl& other) const - { - return !operator<=(other); - } - - /*! - @brief comparison: greater than or equal - @pre The iterator is initialized; i.e. `m_object != nullptr`. - */ - bool operator>=(const iter_impl& other) const - { - return !operator<(other); - } - - /*! - @brief add to iterator - @pre The iterator is initialized; i.e. `m_object != nullptr`. - */ - iter_impl& operator+=(difference_type i) - { - assert(m_object != nullptr); - - switch (m_object->m_type) - { - case json::value_t::object: - { - JSON_THROW(invalid_iterator::create(209, "cannot use offsets with object iterators")); - } - - case json::value_t::array: - { - std::advance(m_it.array_iterator, i); - break; - } - - default: - { - m_it.primitive_iterator += i; - break; - } - } - - return *this; - } - - /*! - @brief subtract from iterator - @pre The iterator is initialized; i.e. `m_object != nullptr`. - */ - iter_impl& operator-=(difference_type i) - { - return operator+=(-i); - } - - /*! - @brief add to iterator - @pre The iterator is initialized; i.e. `m_object != nullptr`. - */ - iter_impl operator+(difference_type i) const - { - auto result = *this; - result += i; - return result; - } - - /*! - @brief addition of distance and iterator - @pre The iterator is initialized; i.e. `m_object != nullptr`. - */ - friend iter_impl operator+(difference_type i, const iter_impl& it) - { - auto result = it; - result += i; - return result; - } - - /*! - @brief subtract from iterator - @pre The iterator is initialized; i.e. `m_object != nullptr`. - */ - iter_impl operator-(difference_type i) const - { - auto result = *this; - result -= i; - return result; - } - - /*! - @brief return difference - @pre The iterator is initialized; i.e. `m_object != nullptr`. - */ - difference_type operator-(const iter_impl& other) const - { - assert(m_object != nullptr); - - switch (m_object->m_type) - { - case json::value_t::object: - { - JSON_THROW(invalid_iterator::create(209, "cannot use offsets with object iterators")); - } - - case json::value_t::array: - { - return m_it.array_iterator - other.m_it.array_iterator; - } - - default: - { - return m_it.primitive_iterator - other.m_it.primitive_iterator; - } - } - } - - /*! - @brief access to successor - @pre The iterator is initialized; i.e. `m_object != nullptr`. - */ - reference operator[](difference_type n) const - { - assert(m_object != nullptr); - - switch (m_object->m_type) - { - case json::value_t::object: - { - JSON_THROW(invalid_iterator::create(208, "cannot use operator[] for object iterators")); - } - - case json::value_t::array: - { - return *std::next(m_it.array_iterator, n); - } - - case json::value_t::null: - { - JSON_THROW(invalid_iterator::create(214, "cannot get value")); - } - - default: - { - if (m_it.primitive_iterator.get_value() == -n) - { - return *m_object; - } - - JSON_THROW(invalid_iterator::create(214, "cannot get value")); - } - } - } - - /*! - @brief return the key of an object iterator - @pre The iterator is initialized; i.e. `m_object != nullptr`. - */ - StringRef key() const - { - assert(m_object != nullptr); - - if (m_object->is_object()) - { - return m_it.object_iterator->first(); - } - - JSON_THROW(invalid_iterator::create(207, "cannot use key() for non-object iterators")); - } - - /*! - @brief return the value of an iterator - @pre The iterator is initialized; i.e. `m_object != nullptr`. - */ - reference value() const - { - return operator*(); - } - - private: - /// associated JSON instance - pointer m_object; - /// the actual iterator of the associated instance - struct internal_iterator m_it = internal_iterator(); - }; - ////////////////////////////////////////// // binary serialization/deserialization // ////////////////////////////////////////// @@ -6376,62 +7193,66 @@ class json JSON value type | value/range | CBOR type | first byte --------------- | ------------------------------------------ | ---------------------------------- | --------------- - null | `null` | Null | 0xf6 - boolean | `true` | True | 0xf5 - boolean | `false` | False | 0xf4 - number_integer | -9223372036854775808..-2147483649 | Negative integer (8 bytes follow) | 0x3b - number_integer | -2147483648..-32769 | Negative integer (4 bytes follow) | 0x3a + null | `null` | Null | 0xF6 + boolean | `true` | True | 0xF5 + boolean | `false` | False | 0xF4 + number_integer | -9223372036854775808..-2147483649 | Negative integer (8 bytes follow) | 0x3B + number_integer | -2147483648..-32769 | Negative integer (4 bytes follow) | 0x3A number_integer | -32768..-129 | Negative integer (2 bytes follow) | 0x39 number_integer | -128..-25 | Negative integer (1 byte follow) | 0x38 number_integer | -24..-1 | Negative integer | 0x20..0x37 number_integer | 0..23 | Integer | 0x00..0x17 number_integer | 24..255 | Unsigned integer (1 byte follow) | 0x18 number_integer | 256..65535 | Unsigned integer (2 bytes follow) | 0x19 - number_integer | 65536..4294967295 | Unsigned integer (4 bytes follow) | 0x1a - number_integer | 4294967296..18446744073709551615 | Unsigned integer (8 bytes follow) | 0x1b + number_integer | 65536..4294967295 | Unsigned integer (4 bytes follow) | 0x1A + number_integer | 4294967296..18446744073709551615 | Unsigned integer (8 bytes follow) | 0x1B number_unsigned | 0..23 | Integer | 0x00..0x17 number_unsigned | 24..255 | Unsigned integer (1 byte follow) | 0x18 number_unsigned | 256..65535 | Unsigned integer (2 bytes follow) | 0x19 - number_unsigned | 65536..4294967295 | Unsigned integer (4 bytes follow) | 0x1a - number_unsigned | 4294967296..18446744073709551615 | Unsigned integer (8 bytes follow) | 0x1b - number_float | *any value* | Double-Precision Float | 0xfb + number_unsigned | 65536..4294967295 | Unsigned integer (4 bytes follow) | 0x1A + number_unsigned | 4294967296..18446744073709551615 | Unsigned integer (8 bytes follow) | 0x1B + number_float | *any value* | Double-Precision Float | 0xFB string | *length*: 0..23 | UTF-8 string | 0x60..0x77 string | *length*: 23..255 | UTF-8 string (1 byte follow) | 0x78 string | *length*: 256..65535 | UTF-8 string (2 bytes follow) | 0x79 - string | *length*: 65536..4294967295 | UTF-8 string (4 bytes follow) | 0x7a - string | *length*: 4294967296..18446744073709551615 | UTF-8 string (8 bytes follow) | 0x7b + string | *length*: 65536..4294967295 | UTF-8 string (4 bytes follow) | 0x7A + string | *length*: 4294967296..18446744073709551615 | UTF-8 string (8 bytes follow) | 0x7B array | *size*: 0..23 | array | 0x80..0x97 array | *size*: 23..255 | array (1 byte follow) | 0x98 array | *size*: 256..65535 | array (2 bytes follow) | 0x99 - array | *size*: 65536..4294967295 | array (4 bytes follow) | 0x9a - array | *size*: 4294967296..18446744073709551615 | array (8 bytes follow) | 0x9b - object | *size*: 0..23 | map | 0xa0..0xb7 - object | *size*: 23..255 | map (1 byte follow) | 0xb8 - object | *size*: 256..65535 | map (2 bytes follow) | 0xb9 - object | *size*: 65536..4294967295 | map (4 bytes follow) | 0xba - object | *size*: 4294967296..18446744073709551615 | map (8 bytes follow) | 0xbb + array | *size*: 65536..4294967295 | array (4 bytes follow) | 0x9A + array | *size*: 4294967296..18446744073709551615 | array (8 bytes follow) | 0x9B + object | *size*: 0..23 | map | 0xA0..0xB7 + object | *size*: 23..255 | map (1 byte follow) | 0xB8 + object | *size*: 256..65535 | map (2 bytes follow) | 0xB9 + object | *size*: 65536..4294967295 | map (4 bytes follow) | 0xBA + object | *size*: 4294967296..18446744073709551615 | map (8 bytes follow) | 0xBB @note The mapping is **complete** in the sense that any JSON value type can be converted to a CBOR value. - @note The following CBOR types are not used in the conversion: - - byte strings (0x40..0x5f) - - UTF-8 strings terminated by "break" (0x7f) - - arrays terminated by "break" (0x9f) - - maps terminated by "break" (0xbf) - - date/time (0xc0..0xc1) - - bignum (0xc2..0xc3) - - decimal fraction (0xc4) - - bigfloat (0xc5) - - tagged items (0xc6..0xd4, 0xd8..0xdb) - - expected conversions (0xd5..0xd7) - - simple values (0xe0..0xf3, 0xf8) - - undefined (0xf7) - - half and single-precision floats (0xf9-0xfa) - - break (0xff) + @note If NaN or Infinity are stored inside a JSON number, they are + serialized properly. This behavior differs from the @ref dump() + function which serializes NaN or Infinity to `null`. + + @note The following CBOR types are not used in the conversion: + - byte strings (0x40..0x5F) + - UTF-8 strings terminated by "break" (0x7F) + - arrays terminated by "break" (0x9F) + - maps terminated by "break" (0xBF) + - date/time (0xC0..0xC1) + - bignum (0xC2..0xC3) + - decimal fraction (0xC4) + - bigfloat (0xC5) + - tagged items (0xC6..0xD4, 0xD8..0xDB) + - expected conversions (0xD5..0xD7) + - simple values (0xE0..0xF3, 0xF8) + - undefined (0xF7) + - half and single-precision floats (0xF9-0xFA) + - break (0xFF) - @param[in,out] os output stream @param[in] j JSON value to serialize + @return MessagePack serialization as byte vector @complexity Linear in the size of the JSON value @a j. @@ -6439,15 +7260,18 @@ class json vector in CBOR format.,to_cbor} @sa http://cbor.io - @sa @ref from_cbor(const std::vector&, const size_t) for the + @sa @ref from_cbor(raw_istream&, const bool strict) for the analogous deserialization - @sa @ref to_msgpack(const json& for the related MessagePack format + @sa @ref to_msgpack(const json&) for the related MessagePack format + @sa @ref to_ubjson(const json&, const bool, const bool) for the + related UBJSON format @since version 2.0.9 */ + static std::vector to_cbor(const json& j); + static ArrayRef to_cbor(const json& j, std::vector& buf); + static ArrayRef to_cbor(const json& j, SmallVectorImpl& buf); static void to_cbor(raw_ostream& os, const json& j); - static StringRef to_cbor(const json& j, SmallVectorImpl buf); - static std::string to_cbor(const json& j); /*! @brief create a MessagePack serialization of a given JSON value @@ -6461,35 +7285,35 @@ class json JSON value type | value/range | MessagePack type | first byte --------------- | --------------------------------- | ---------------- | ---------- - null | `null` | nil | 0xc0 - boolean | `true` | true | 0xc3 - boolean | `false` | false | 0xc2 - number_integer | -9223372036854775808..-2147483649 | int64 | 0xd3 - number_integer | -2147483648..-32769 | int32 | 0xd2 - number_integer | -32768..-129 | int16 | 0xd1 - number_integer | -128..-33 | int8 | 0xd0 - number_integer | -32..-1 | negative fixint | 0xe0..0xff - number_integer | 0..127 | positive fixint | 0x00..0x7f - number_integer | 128..255 | uint 8 | 0xcc - number_integer | 256..65535 | uint 16 | 0xcd - number_integer | 65536..4294967295 | uint 32 | 0xce - number_integer | 4294967296..18446744073709551615 | uint 64 | 0xcf - number_unsigned | 0..127 | positive fixint | 0x00..0x7f - number_unsigned | 128..255 | uint 8 | 0xcc - number_unsigned | 256..65535 | uint 16 | 0xcd - number_unsigned | 65536..4294967295 | uint 32 | 0xce - number_unsigned | 4294967296..18446744073709551615 | uint 64 | 0xcf - number_float | *any value* | float 64 | 0xcb - string | *length*: 0..31 | fixstr | 0xa0..0xbf - string | *length*: 32..255 | str 8 | 0xd9 - string | *length*: 256..65535 | str 16 | 0xda - string | *length*: 65536..4294967295 | str 32 | 0xdb - array | *size*: 0..15 | fixarray | 0x90..0x9f - array | *size*: 16..65535 | array 16 | 0xdc - array | *size*: 65536..4294967295 | array 32 | 0xdd - object | *size*: 0..15 | fix map | 0x80..0x8f - object | *size*: 16..65535 | map 16 | 0xde - object | *size*: 65536..4294967295 | map 32 | 0xdf + null | `null` | nil | 0xC0 + boolean | `true` | true | 0xC3 + boolean | `false` | false | 0xC2 + number_integer | -9223372036854775808..-2147483649 | int64 | 0xD3 + number_integer | -2147483648..-32769 | int32 | 0xD2 + number_integer | -32768..-129 | int16 | 0xD1 + number_integer | -128..-33 | int8 | 0xD0 + number_integer | -32..-1 | negative fixint | 0xE0..0xFF + number_integer | 0..127 | positive fixint | 0x00..0x7F + number_integer | 128..255 | uint 8 | 0xCC + number_integer | 256..65535 | uint 16 | 0xCD + number_integer | 65536..4294967295 | uint 32 | 0xCE + number_integer | 4294967296..18446744073709551615 | uint 64 | 0xCF + number_unsigned | 0..127 | positive fixint | 0x00..0x7F + number_unsigned | 128..255 | uint 8 | 0xCC + number_unsigned | 256..65535 | uint 16 | 0xCD + number_unsigned | 65536..4294967295 | uint 32 | 0xCE + number_unsigned | 4294967296..18446744073709551615 | uint 64 | 0xCF + number_float | *any value* | float 64 | 0xCB + string | *length*: 0..31 | fixstr | 0xA0..0xBF + string | *length*: 32..255 | str 8 | 0xD9 + string | *length*: 256..65535 | str 16 | 0xDA + string | *length*: 65536..4294967295 | str 32 | 0xDB + array | *size*: 0..15 | fixarray | 0x90..0x9F + array | *size*: 16..65535 | array 16 | 0xDC + array | *size*: 65536..4294967295 | array 32 | 0xDD + object | *size*: 0..15 | fix map | 0x80..0x8F + object | *size*: 16..65535 | map 16 | 0xDE + object | *size*: 65536..4294967295 | map 32 | 0xDF @note The mapping is **complete** in the sense that any JSON value type can be converted to a MessagePack value. @@ -6500,16 +7324,20 @@ class json - objects with more than 4294967295 elements @note The following MessagePack types are not used in the conversion: - - bin 8 - bin 32 (0xc4..0xc6) - - ext 8 - ext 32 (0xc7..0xc9) - - float 32 (0xca) - - fixext 1 - fixext 16 (0xd4..0xd8) + - bin 8 - bin 32 (0xC4..0xC6) + - ext 8 - ext 32 (0xC7..0xC9) + - float 32 (0xCA) + - fixext 1 - fixext 16 (0xD4..0xD8) @note Any MessagePack output created @ref to_msgpack can be successfully parsed by @ref from_msgpack. - @param[in,out] os output stream + @note If NaN or Infinity are stored inside a JSON number, they are + serialized properly. This behavior differs from the @ref dump() + function which serializes NaN or Infinity to `null`. + @param[in] j JSON value to serialize + @return MessagePack serialization as byte vector @complexity Linear in the size of the JSON value @a j. @@ -6520,18 +7348,111 @@ class json @sa @ref from_msgpack(const std::vector&, const size_t) for the analogous deserialization @sa @ref to_cbor(const json& for the related CBOR format + @sa @ref to_ubjson(const json&, const bool, const bool) for the + related UBJSON format @since version 2.0.9 */ + static std::vector to_msgpack(const json& j); + static ArrayRef to_msgpack(const json& j, std::vector& buf); + static ArrayRef to_msgpack(const json& j, SmallVectorImpl& buf); static void to_msgpack(raw_ostream& os, const json& j); - static StringRef to_msgpack(const json& j, SmallVectorImpl buf); - static std::string to_msgpack(const json& j); /*! - @brief create a JSON value from a byte vector in CBOR format + @brief create a UBJSON serialization of a given JSON value - Deserializes a given byte vector @a v to a JSON value using the CBOR - (Concise Binary Object Representation) serialization format. + Serializes a given JSON value @a j to a byte vector using the UBJSON + (Universal Binary JSON) serialization format. UBJSON aims to be more compact + than JSON itself, yet more efficient to parse. + + The library uses the following mapping from JSON values types to + UBJSON types according to the UBJSON specification: + + JSON value type | value/range | UBJSON type | marker + --------------- | --------------------------------- | ----------- | ------ + null | `null` | null | `Z` + boolean | `true` | true | `T` + boolean | `false` | false | `F` + number_integer | -9223372036854775808..-2147483649 | int64 | `L` + number_integer | -2147483648..-32769 | int32 | `l` + number_integer | -32768..-129 | int16 | `I` + number_integer | -128..127 | int8 | `i` + number_integer | 128..255 | uint8 | `U` + number_integer | 256..32767 | int16 | `I` + number_integer | 32768..2147483647 | int32 | `l` + number_integer | 2147483648..9223372036854775807 | int64 | `L` + number_unsigned | 0..127 | int8 | `i` + number_unsigned | 128..255 | uint8 | `U` + number_unsigned | 256..32767 | int16 | `I` + number_unsigned | 32768..2147483647 | int32 | `l` + number_unsigned | 2147483648..9223372036854775807 | int64 | `L` + number_float | *any value* | float64 | `D` + string | *with shortest length indicator* | string | `S` + array | *see notes on optimized format* | array | `[` + object | *see notes on optimized format* | map | `{` + + @note The mapping is **complete** in the sense that any JSON value type + can be converted to a UBJSON value. + + @note The following values can **not** be converted to a UBJSON value: + - strings with more than 9223372036854775807 bytes (theoretical) + - unsigned integer numbers above 9223372036854775807 + + @note The following markers are not used in the conversion: + - `Z`: no-op values are not created. + - `C`: single-byte strings are serialized with `S` markers. + + @note Any UBJSON output created @ref to_ubjson can be successfully parsed + by @ref from_ubjson. + + @note If NaN or Infinity are stored inside a JSON number, they are + serialized properly. This behavior differs from the @ref dump() + function which serializes NaN or Infinity to `null`. + + @note The optimized formats for containers are supported: Parameter + @a use_size adds size information to the beginning of a container and + removes the closing marker. Parameter @a use_type further checks + whether all elements of a container have the same type and adds the + type marker to the beginning of the container. The @a use_type + parameter must only be used together with @a use_size = true. Note + that @a use_size = true alone may result in larger representations - + the benefit of this parameter is that the receiving side is + immediately informed on the number of elements of the container. + + @param[in] j JSON value to serialize + @param[in] use_size whether to add size annotations to container types + @param[in] use_type whether to add type annotations to container types + (must be combined with @a use_size = true) + @return UBJSON serialization as byte vector + + @complexity Linear in the size of the JSON value @a j. + + @liveexample{The example shows the serialization of a JSON value to a byte + vector in UBJSON format.,to_ubjson} + + @sa http://ubjson.org + @sa @ref from_ubjson(raw_istream&, const bool strict) for the + analogous deserialization + @sa @ref to_cbor(const json& for the related CBOR format + @sa @ref to_msgpack(const json&) for the related MessagePack format + + @since version 3.1.0 + */ + static std::vector to_ubjson(const json& j, + const bool use_size = false, + const bool use_type = false); + static ArrayRef to_ubjson(const json& j, std::vector& buf, + const bool use_size = false, const bool use_type = false); + static ArrayRef to_ubjson(const json& j, SmallVectorImpl& buf, + const bool use_size = false, const bool use_type = false); + static void to_ubjson(raw_ostream& os, const json& j, + const bool use_size = false, const bool use_type = false); + + /*! + @brief create a JSON value from an input in CBOR format + + Deserializes a given input @a i to a JSON value using the CBOR (Concise + Binary Object Representation) serialization format. The library maps CBOR types to JSON value types as follows: @@ -6540,51 +7461,51 @@ class json Integer | number_unsigned | 0x00..0x17 Unsigned integer | number_unsigned | 0x18 Unsigned integer | number_unsigned | 0x19 - Unsigned integer | number_unsigned | 0x1a - Unsigned integer | number_unsigned | 0x1b + Unsigned integer | number_unsigned | 0x1A + Unsigned integer | number_unsigned | 0x1B Negative integer | number_integer | 0x20..0x37 Negative integer | number_integer | 0x38 Negative integer | number_integer | 0x39 - Negative integer | number_integer | 0x3a - Negative integer | number_integer | 0x3b + Negative integer | number_integer | 0x3A + Negative integer | number_integer | 0x3B Negative integer | number_integer | 0x40..0x57 UTF-8 string | string | 0x60..0x77 UTF-8 string | string | 0x78 UTF-8 string | string | 0x79 - UTF-8 string | string | 0x7a - UTF-8 string | string | 0x7b - UTF-8 string | string | 0x7f + UTF-8 string | string | 0x7A + UTF-8 string | string | 0x7B + UTF-8 string | string | 0x7F array | array | 0x80..0x97 array | array | 0x98 array | array | 0x99 - array | array | 0x9a - array | array | 0x9b - array | array | 0x9f - map | object | 0xa0..0xb7 - map | object | 0xb8 - map | object | 0xb9 - map | object | 0xba - map | object | 0xbb - map | object | 0xbf - False | `false` | 0xf4 - True | `true` | 0xf5 - Nill | `null` | 0xf6 - Half-Precision Float | number_float | 0xf9 - Single-Precision Float | number_float | 0xfa - Double-Precision Float | number_float | 0xfb + array | array | 0x9A + array | array | 0x9B + array | array | 0x9F + map | object | 0xA0..0xB7 + map | object | 0xB8 + map | object | 0xB9 + map | object | 0xBA + map | object | 0xBB + map | object | 0xBF + False | `false` | 0xF4 + True | `true` | 0xF5 + Nill | `null` | 0xF6 + Half-Precision Float | number_float | 0xF9 + Single-Precision Float | number_float | 0xFA + Double-Precision Float | number_float | 0xFB @warning The mapping is **incomplete** in the sense that not all CBOR types can be converted to a JSON value. The following CBOR types are not supported and will yield parse errors (parse_error.112): - - byte strings (0x40..0x5f) - - date/time (0xc0..0xc1) - - bignum (0xc2..0xc3) - - decimal fraction (0xc4) - - bigfloat (0xc5) - - tagged items (0xc6..0xd4, 0xd8..0xdb) - - expected conversions (0xd5..0xd7) - - simple values (0xe0..0xf3, 0xf8) - - undefined (0xf7) + - byte strings (0x40..0x5F) + - date/time (0xC0..0xC1) + - bignum (0xC2..0xC3) + - decimal fraction (0xC4) + - bigfloat (0xC5) + - tagged items (0xC6..0xD4, 0xD8..0xDB) + - expected conversions (0xD5..0xD7) + - simple values (0xE0..0xF3, 0xF8) + - undefined (0xF7) @warning CBOR allows map keys of any type, whereas JSON only allows strings as keys in object values. Therefore, CBOR maps with keys @@ -6593,338 +7514,182 @@ class json @note Any CBOR output created @ref to_cbor can be successfully parsed by @ref from_cbor. - @param[in] is an input stream in CBOR format + @param[in] i an input in CBOR format convertible to an input adapter + @param[in] strict whether to expect the input to be consumed until EOF + (true by default) @return deserialized JSON value - @throw parse_error.110 if the given vector ends prematurely + @throw parse_error.110 if the given input ends prematurely or the end of + file was not reached when @a strict was set to true @throw parse_error.112 if unsupported features from CBOR were - used in the given vector @a v or if the input is not valid CBOR + used in the given input @a v or if the input is not valid CBOR @throw parse_error.113 if a string was expected as map key, but not found - @complexity Linear in the size of the byte vector @a v. + @complexity Linear in the size of the input @a i. @liveexample{The example shows the deserialization of a byte vector in CBOR format to a JSON value.,from_cbor} @sa http://cbor.io @sa @ref to_cbor(const json&) for the analogous serialization - @sa @ref from_msgpack(const std::vector&, const size_t) for the + @sa @ref from_msgpack(raw_istream&, const bool) for the related MessagePack format + @sa @ref from_ubjson(raw_istream&, const bool) for the related + UBJSON format - @since version 2.0.9, parameter @a start_index since 2.1.1 + @since version 2.0.9; parameter @a start_index since 2.1.1; changed to + consume input adapters, removed start_index parameter, and added + @a strict parameter since 3.0.0 */ - static json from_cbor(raw_istream& is); - static json from_cbor(StringRef s); + static json from_cbor(raw_istream& is, + const bool strict = true); /*! - @brief create a JSON value from a byte vector in MessagePack format + @copydoc from_cbor(raw_istream&, const bool) + */ + static json from_cbor(ArrayRef arr, const bool strict = true); - Deserializes a given byte vector @a v to a JSON value using the MessagePack + /*! + @brief create a JSON value from an input in MessagePack format + + Deserializes a given input @a i to a JSON value using the MessagePack serialization format. The library maps MessagePack types to JSON value types as follows: MessagePack type | JSON value type | first byte ---------------- | --------------- | ---------- - positive fixint | number_unsigned | 0x00..0x7f - fixmap | object | 0x80..0x8f - fixarray | array | 0x90..0x9f - fixstr | string | 0xa0..0xbf - nil | `null` | 0xc0 - false | `false` | 0xc2 - true | `true` | 0xc3 - float 32 | number_float | 0xca - float 64 | number_float | 0xcb - uint 8 | number_unsigned | 0xcc - uint 16 | number_unsigned | 0xcd - uint 32 | number_unsigned | 0xce - uint 64 | number_unsigned | 0xcf - int 8 | number_integer | 0xd0 - int 16 | number_integer | 0xd1 - int 32 | number_integer | 0xd2 - int 64 | number_integer | 0xd3 - str 8 | string | 0xd9 - str 16 | string | 0xda - str 32 | string | 0xdb - array 16 | array | 0xdc - array 32 | array | 0xdd - map 16 | object | 0xde - map 32 | object | 0xdf - negative fixint | number_integer | 0xe0-0xff + positive fixint | number_unsigned | 0x00..0x7F + fixmap | object | 0x80..0x8F + fixarray | array | 0x90..0x9F + fixstr | string | 0xA0..0xBF + nil | `null` | 0xC0 + false | `false` | 0xC2 + true | `true` | 0xC3 + float 32 | number_float | 0xCA + float 64 | number_float | 0xCB + uint 8 | number_unsigned | 0xCC + uint 16 | number_unsigned | 0xCD + uint 32 | number_unsigned | 0xCE + uint 64 | number_unsigned | 0xCF + int 8 | number_integer | 0xD0 + int 16 | number_integer | 0xD1 + int 32 | number_integer | 0xD2 + int 64 | number_integer | 0xD3 + str 8 | string | 0xD9 + str 16 | string | 0xDA + str 32 | string | 0xDB + array 16 | array | 0xDC + array 32 | array | 0xDD + map 16 | object | 0xDE + map 32 | object | 0xDF + negative fixint | number_integer | 0xE0-0xFF @warning The mapping is **incomplete** in the sense that not all MessagePack types can be converted to a JSON value. The following MessagePack types are not supported and will yield parse errors: - - bin 8 - bin 32 (0xc4..0xc6) - - ext 8 - ext 32 (0xc7..0xc9) - - fixext 1 - fixext 16 (0xd4..0xd8) + - bin 8 - bin 32 (0xC4..0xC6) + - ext 8 - ext 32 (0xC7..0xC9) + - fixext 1 - fixext 16 (0xD4..0xD8) @note Any MessagePack output created @ref to_msgpack can be successfully parsed by @ref from_msgpack. - @param[in] is an input stream in MessagePack format - @return deserialized JSON value + @param[in] i an input in MessagePack format convertible to an input + adapter + @param[in] strict whether to expect the input to be consumed until EOF + (true by default) - @throw parse_error.110 if the given vector ends prematurely + @throw parse_error.110 if the given input ends prematurely or the end of + file was not reached when @a strict was set to true @throw parse_error.112 if unsupported features from MessagePack were - used in the given vector @a v or if the input is not valid MessagePack + used in the given input @a i or if the input is not valid MessagePack @throw parse_error.113 if a string was expected as map key, but not found - @complexity Linear in the size of the byte vector @a v. + @complexity Linear in the size of the input @a i. @liveexample{The example shows the deserialization of a byte vector in MessagePack format to a JSON value.,from_msgpack} @sa http://msgpack.org @sa @ref to_msgpack(const json&) for the analogous serialization - @sa @ref from_cbor(const std::vector&, const size_t) for the - related CBOR format + @sa @ref from_cbor(raw_istream&, const bool) for the related CBOR + format + @sa @ref from_ubjson(raw_istream&, const bool) for the related + UBJSON format - @since version 2.0.9, parameter @a start_index since 2.1.1 + @since version 2.0.9; parameter @a start_index since 2.1.1; changed to + consume input adapters, removed start_index parameter, and added + @a strict parameter since 3.0.0 */ - static json from_msgpack(raw_istream& is); - static json from_msgpack(StringRef s); + static json from_msgpack(raw_istream& is, + const bool strict = true); + + /*! + @copydoc from_msgpack(raw_istream, const bool) + */ + static json from_msgpack(ArrayRef arr, const bool strict = true); + + /*! + @brief create a JSON value from an input in UBJSON format + + Deserializes a given input @a i to a JSON value using the UBJSON (Universal + Binary JSON) serialization format. + + The library maps UBJSON types to JSON value types as follows: + + UBJSON type | JSON value type | marker + ----------- | --------------------------------------- | ------ + no-op | *no value, next value is read* | `N` + null | `null` | `Z` + false | `false` | `F` + true | `true` | `T` + float32 | number_float | `d` + float64 | number_float | `D` + uint8 | number_unsigned | `U` + int8 | number_integer | `i` + int16 | number_integer | `I` + int32 | number_integer | `l` + int64 | number_integer | `L` + string | string | `S` + char | string | `C` + array | array (optimized values are supported) | `[` + object | object (optimized values are supported) | `{` + + @note The mapping is **complete** in the sense that any UBJSON value can + be converted to a JSON value. + + @param[in] i an input in UBJSON format convertible to an input adapter + @param[in] strict whether to expect the input to be consumed until EOF + (true by default) + + @throw parse_error.110 if the given input ends prematurely or the end of + file was not reached when @a strict was set to true + @throw parse_error.112 if a parse error occurs + @throw parse_error.113 if a string could not be parsed successfully + + @complexity Linear in the size of the input @a i. + + @liveexample{The example shows the deserialization of a byte vector in + UBJSON format to a JSON value.,from_ubjson} + + @sa http://ubjson.org + @sa @ref to_ubjson(const json&, const bool, const bool) for the + analogous serialization + @sa @ref from_cbor(raw_istream&, const bool) for the related CBOR + format + @sa @ref from_msgpack(raw_istream&, const bool) for the related + MessagePack format + + @since version 3.1.0 + */ + static json from_ubjson(raw_istream& is, + const bool strict = true); + + static json from_ubjson(ArrayRef arr, const bool strict = true); /// @} - public: - /*! - @brief JSON Pointer - - A JSON pointer defines a string syntax for identifying a specific value - within a JSON document. It can be used with functions `at` and - `operator[]`. Furthermore, JSON pointers are the base for JSON patches. - - @sa [RFC 6901](https://tools.ietf.org/html/rfc6901) - - @since version 2.0.0 - */ - class json_pointer - { - /// allow json to access private members - friend class json; - friend class JsonTest; - - public: - /*! - @brief create JSON pointer - - Create a JSON pointer according to the syntax described in - [Section 3 of RFC6901](https://tools.ietf.org/html/rfc6901#section-3). - - @param[in] s string representing the JSON pointer; if omitted, the - empty string is assumed which references the whole JSON - value - - @throw parse_error.107 if the given JSON pointer @a s is nonempty and - does not begin with a slash (`/`); see example below - - @throw parse_error.108 if a tilde (`~`) in the given JSON pointer @a s - is not followed by `0` (representing `~`) or `1` (representing `/`); - see example below - - @liveexample{The example shows the construction several valid JSON - pointers as well as the exceptional behavior.,json_pointer} - - @since version 2.0.0 - */ - explicit json_pointer(const std::string& s = "") - : reference_tokens(split(s)) - {} - - /*! - @brief return a string representation of the JSON pointer - - @invariant For each JSON pointer `ptr`, it holds: - @code {.cpp} - ptr == json_pointer(ptr.to_string()); - @endcode - - @return a string representation of the JSON pointer - - @liveexample{The example shows the result of `to_string`., - json_pointer__to_string} - - @since version 2.0.0 - */ - std::string to_string() const noexcept; - - /// @copydoc to_string() - operator std::string() const - { - return to_string(); - } - - private: - /*! - @brief remove and return last reference pointer - @throw out_of_range.405 if JSON pointer has no parent - */ - std::string pop_back() - { - if (is_root()) - { - JSON_THROW(out_of_range::create(405, "JSON pointer has no parent")); - } - - auto last = reference_tokens.back(); - reference_tokens.pop_back(); - return last; - } - - /// return whether pointer points to the root document - bool is_root() const - { - return reference_tokens.empty(); - } - - json_pointer top() const - { - if (is_root()) - { - JSON_THROW(out_of_range::create(405, "JSON pointer has no parent")); - } - - json_pointer result = *this; - result.reference_tokens = {reference_tokens[0]}; - return result; - } - - /*! - @brief create and return a reference to the pointed to value - - @complexity Linear in the number of reference tokens. - - @throw parse_error.109 if array index is not a number - @throw type_error.313 if value cannot be unflattened - */ - reference get_and_create(reference j) const; - - /*! - @brief return a reference to the pointed to value - - @note This version does not throw if a value is not present, but tries - to create nested values instead. For instance, calling this function - with pointer `"/this/that"` on a null value is equivalent to calling - `operator[]("this").operator[]("that")` on that value, effectively - changing the null value to an object. - - @param[in] ptr a JSON value - - @return reference to the JSON value pointed to by the JSON pointer - - @complexity Linear in the length of the JSON pointer. - - @throw parse_error.106 if an array index begins with '0' - @throw parse_error.109 if an array index was not a number - @throw out_of_range.404 if the JSON pointer can not be resolved - */ - reference get_unchecked(pointer ptr) const; - - /*! - @throw parse_error.106 if an array index begins with '0' - @throw parse_error.109 if an array index was not a number - @throw out_of_range.402 if the array index '-' is used - @throw out_of_range.404 if the JSON pointer can not be resolved - */ - reference get_checked(pointer ptr) const; - - /*! - @brief return a const reference to the pointed to value - - @param[in] ptr a JSON value - - @return const reference to the JSON value pointed to by the JSON - pointer - - @throw parse_error.106 if an array index begins with '0' - @throw parse_error.109 if an array index was not a number - @throw out_of_range.402 if the array index '-' is used - @throw out_of_range.404 if the JSON pointer can not be resolved - */ - const_reference get_unchecked(const_pointer ptr) const; - - /*! - @throw parse_error.106 if an array index begins with '0' - @throw parse_error.109 if an array index was not a number - @throw out_of_range.402 if the array index '-' is used - @throw out_of_range.404 if the JSON pointer can not be resolved - */ - const_reference get_checked(const_pointer ptr) const; - - /*! - @brief split the string input to reference tokens - - @note This function is only called by the json_pointer constructor. - All exceptions below are documented there. - - @throw parse_error.107 if the pointer is not empty or begins with '/' - @throw parse_error.108 if character '~' is not followed by '0' or '1' - */ - static std::vector split(const std::string& reference_string); - - /*! - @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 - */ - static void replace_substring(std::string& s, - const std::string& f, - const std::string& t); - - /// escape tilde and slash - static std::string escape(std::string s); - - /// unescape tilde and slash - static void unescape(std::string& s); - - /*! - @param[in] reference_string the reference string to the current value - @param[in] value the value to consider - @param[in,out] result the result object to insert values to - - @note Empty objects or arrays are flattened to `null`. - */ - static void flatten(const std::string& reference_string, - const json& value, - json& result); - - /*! - @param[in] value flattened JSON - - @return unflattened JSON - - @throw parse_error.109 if array index is not a number - @throw type_error.314 if value is not an object - @throw type_error.315 if object values are not primitive - @throw type_error.313 if value cannot be unflattened - */ - static json unflatten(const json& value); - - friend bool operator==(json_pointer const& lhs, - json_pointer const& rhs) noexcept - { - return lhs.reference_tokens == rhs.reference_tokens; - } - - friend bool operator!=(json_pointer const& lhs, - json_pointer const& rhs) noexcept - { - return !(lhs == rhs); - } - - /// the reference tokens - std::vector reference_tokens {}; - }; - ////////////////////////// // JSON Pointer support // ////////////////////////// @@ -6936,8 +7701,8 @@ class json @brief access specified element via JSON Pointer Uses a JSON pointer to retrieve a reference to the respective JSON value. - No bound checking is performed. Similar to @ref operator[]( - StringRef), `null` values are created in arrays and objects if + No bound checking is performed. Similar to @ref operator[](const typename + object_t::key_type&), `null` values are created in arrays and objects if necessary. In particular: @@ -7021,6 +7786,9 @@ class json pointer @a ptr. As `at` provides checked access (and no elements are implicitly inserted), the index '-' is always invalid. See example below. + @throw out_of_range.403 if the JSON pointer describes a key of an object + which cannot be found. See example below. + @throw out_of_range.404 if the JSON pointer @a ptr can not be resolved. See example below. @@ -7061,6 +7829,9 @@ class json pointer @a ptr. As `at` provides checked access (and no elements are implicitly inserted), the index '-' is always invalid. See example below. + @throw out_of_range.403 if the JSON pointer describes a key of an object + which cannot be found. See example below. + @throw out_of_range.404 if the JSON pointer @a ptr can not be resolved. See example below. @@ -7128,7 +7899,7 @@ class json @complexity Linear in the size the JSON value. @throw type_error.314 if value is not an object - @throw type_error.315 if object values are not primitve + @throw type_error.315 if object values are not primitive @liveexample{The following code shows how a flattened JSON object is unflattened into the original nested JSON object.,unflatten} @@ -7143,11 +7914,156 @@ class json } /// @} + + ////////////////////////// + // JSON Patch functions // + ////////////////////////// + + /// @name JSON Patch functions + /// @{ + + /*! + @brief applies a JSON patch + + [JSON Patch](http://jsonpatch.com) defines a JSON document structure for + expressing a sequence of operations to apply to a JSON) document. With + this function, a JSON Patch is applied to the current JSON value by + executing all operations from the patch. + + @param[in] json_patch JSON patch document + @return patched document + + @note The application of a patch is atomic: Either all operations succeed + and the patched document is returned or an exception is thrown. In + any case, the original value is not changed: the patch is applied + to a copy of the value. + + @throw parse_error.104 if the JSON patch does not consist of an array of + objects + + @throw parse_error.105 if the JSON patch is malformed (e.g., mandatory + attributes are missing); example: `"operation add must have member path"` + + @throw out_of_range.401 if an array index is out of range. + + @throw out_of_range.403 if a JSON pointer inside the patch could not be + resolved successfully in the current JSON value; example: `"key baz not + found"` + + @throw out_of_range.405 if JSON pointer has no parent ("add", "remove", + "move") + + @throw other_error.501 if "test" operation was unsuccessful + + @complexity Linear in the size of the JSON value and the length of the + JSON patch. As usually only a fraction of the JSON value is affected by + the patch, the complexity can usually be neglected. + + @liveexample{The following code shows how a JSON patch is applied to a + value.,patch} + + @sa @ref diff -- create a JSON patch by comparing two JSON values + + @sa [RFC 6902 (JSON Patch)](https://tools.ietf.org/html/rfc6902) + @sa [RFC 6901 (JSON Pointer)](https://tools.ietf.org/html/rfc6901) + + @since version 2.0.0 + */ + json patch(const json& json_patch) const; + + /*! + @brief creates a diff as a JSON patch + + Creates a [JSON Patch](http://jsonpatch.com) so that value @a source can + be changed into the value @a target by calling @ref patch function. + + @invariant For two JSON values @a source and @a target, the following code + yields always `true`: + @code {.cpp} + source.patch(diff(source, target)) == target; + @endcode + + @note Currently, only `remove`, `add`, and `replace` operations are + generated. + + @param[in] source JSON value to compare from + @param[in] target JSON value to compare against + @param[in] path helper value to create JSON pointers + + @return a JSON patch to convert the @a source to @a target + + @complexity Linear in the lengths of @a source and @a target. + + @liveexample{The following code shows how a JSON patch is created as a + diff for two JSON values.,diff} + + @sa @ref patch -- apply a JSON patch + @sa @ref merge_patch -- apply a JSON Merge Patch + + @sa [RFC 6902 (JSON Patch)](https://tools.ietf.org/html/rfc6902) + + @since version 2.0.0 + */ + static json diff(const json& source, const json& target, + const std::string& path = ""); + + /// @} + + //////////////////////////////// + // JSON Merge Patch functions // + //////////////////////////////// + + /// @name JSON Merge Patch functions + /// @{ + + /*! + @brief applies a JSON Merge Patch + + The merge patch format is primarily intended for use with the HTTP PATCH + method as a means of describing a set of modifications to a target + resource's content. This function applies a merge patch to the current + JSON value. + + The function implements the following algorithm from Section 2 of + [RFC 7396 (JSON Merge Patch)](https://tools.ietf.org/html/rfc7396): + + ``` + define MergePatch(Target, Patch): + if Patch is an Object: + if Target is not an Object: + Target = {} // Ignore the contents and set it to an empty Object + for each Name/Value pair in Patch: + if Value is null: + if Name exists in Target: + remove the Name/Value pair from Target + else: + Target[Name] = MergePatch(Target[Name], Value) + return Target + else: + return Patch + ``` + + Thereby, `Target` is the current object; that is, the patch is applied to + the current value. + + @param[in] patch the patch to apply + + @complexity Linear in the lengths of @a patch. + + @liveexample{The following code shows how a JSON Merge Patch is applied to + a JSON document.,merge_patch} + + @sa @ref patch -- apply a JSON patch + @sa [RFC 7396 (JSON Merge Patch)](https://tools.ietf.org/html/rfc7396) + + @since version 3.0.0 + */ + void merge_patch(const json& patch); + + /// @} }; - } // namespace wpi - /////////////////////// // nonmember support // /////////////////////// @@ -7163,7 +8079,7 @@ namespace std template<> inline void swap(wpi::json& j1, wpi::json& j2) noexcept( - is_nothrow_move_constructible::value && + is_nothrow_move_constructible::value and is_nothrow_move_assignable::value ) { @@ -7179,12 +8095,19 @@ struct hash @since version 1.0.0 */ - std::size_t operator()(const wpi::json& j) const; + std::size_t operator()(const wpi::json& j) const + { + // a naive hashing via the string representation + const auto& h = hash(); + return h(j.dump()); + } }; /// specialization for std::less -template <> -struct less<::wpi::detail::value_t> +/// @note: do not remove the space after '<', +/// see https://github.com/nlohmann/json/pull/679 +template<> +struct less< ::wpi::detail::value_t> { /*! @brief compare two value_t enum values @@ -7232,7 +8155,7 @@ object if no parse error occurred. */ inline wpi::json::json_pointer operator "" _json_pointer(const char* s, std::size_t n) { - return wpi::json::json_pointer(std::string(s, n)); + return wpi::json::json_pointer(wpi::StringRef(s, n)); } #ifndef WPI_JSON_IMPLEMENTATION @@ -7251,6 +8174,11 @@ inline wpi::json::json_pointer operator "" _json_pointer(const char* s, std::siz #undef JSON_TRY #undef JSON_LIKELY #undef JSON_UNLIKELY +#undef JSON_HAS_CPP_14 +#undef JSON_HAS_CPP_17 +#undef NLOHMANN_BASIC_JSON_TPL_DECLARATION +#undef NLOHMANN_BASIC_JSON_TPL +#undef NLOHMANN_JSON_HAS_HELPER #endif // WPI_JSON_IMPLEMENTATION diff --git a/wpiutil/src/test/native/cpp/json/unit-cbor.cpp b/wpiutil/src/test/native/cpp/json/unit-cbor.cpp index 14dc1fc3da..84e98eb202 100644 --- a/wpiutil/src/test/native/cpp/json/unit-cbor.cpp +++ b/wpiutil/src/test/native/cpp/json/unit-cbor.cpp @@ -50,7 +50,7 @@ TEST(CborDiscardedTest, Case) TEST(CborNullTest, Case) { json j = nullptr; - std::string expected = "\xf6"; + std::vector expected = {0xf6}; const auto result = json::to_cbor(j); EXPECT_EQ(result, expected); @@ -61,7 +61,7 @@ TEST(CborNullTest, Case) TEST(CborBooleanTest, True) { json j = true; - std::string expected = "\xf5"; + std::vector expected = {0xf5}; const auto result = json::to_cbor(j); EXPECT_EQ(result, expected); @@ -72,7 +72,7 @@ TEST(CborBooleanTest, True) TEST(CborBooleanTest, False) { json j = false; - std::string expected = "\xf4"; + std::vector expected = {0xf4}; const auto result = json::to_cbor(j); EXPECT_EQ(result, expected); @@ -91,17 +91,17 @@ TEST_P(CborSignedNeg8Test, Case) ASSERT_TRUE(j.is_number_integer()); // create expected byte vector - std::string expected; - expected.push_back(static_cast(0x3b)); + std::vector expected; + expected.push_back(static_cast(0x3b)); uint64_t positive = static_cast(-1 - GetParam()); - expected.push_back(static_cast((positive >> 56) & 0xff)); - expected.push_back(static_cast((positive >> 48) & 0xff)); - expected.push_back(static_cast((positive >> 40) & 0xff)); - expected.push_back(static_cast((positive >> 32) & 0xff)); - expected.push_back(static_cast((positive >> 24) & 0xff)); - expected.push_back(static_cast((positive >> 16) & 0xff)); - expected.push_back(static_cast((positive >> 8) & 0xff)); - expected.push_back(static_cast(positive & 0xff)); + expected.push_back(static_cast((positive >> 56) & 0xff)); + expected.push_back(static_cast((positive >> 48) & 0xff)); + expected.push_back(static_cast((positive >> 40) & 0xff)); + expected.push_back(static_cast((positive >> 32) & 0xff)); + expected.push_back(static_cast((positive >> 24) & 0xff)); + expected.push_back(static_cast((positive >> 16) & 0xff)); + expected.push_back(static_cast((positive >> 8) & 0xff)); + expected.push_back(static_cast(positive & 0xff)); // compare result + size const auto result = json::to_cbor(j); @@ -153,13 +153,13 @@ TEST_P(CborSignedNeg4Test, Case) ASSERT_TRUE(j.is_number_integer()); // create expected byte vector - std::string expected; - expected.push_back(static_cast(0x3a)); + std::vector expected; + expected.push_back(static_cast(0x3a)); uint32_t positive = static_cast(static_cast(-1 - GetParam()) & 0x00000000ffffffff); - expected.push_back(static_cast((positive >> 24) & 0xff)); - expected.push_back(static_cast((positive >> 16) & 0xff)); - expected.push_back(static_cast((positive >> 8) & 0xff)); - expected.push_back(static_cast(positive & 0xff)); + expected.push_back(static_cast((positive >> 24) & 0xff)); + expected.push_back(static_cast((positive >> 16) & 0xff)); + expected.push_back(static_cast((positive >> 8) & 0xff)); + expected.push_back(static_cast(positive & 0xff)); // compare result + size const auto result = json::to_cbor(j); @@ -205,11 +205,11 @@ TEST(CborSignedTest, Neg2) ASSERT_TRUE(j.is_number_integer()); // create expected byte vector - std::string expected; - expected.push_back(static_cast(0x39)); + std::vector expected; + expected.push_back(static_cast(0x39)); uint16_t positive = static_cast(-1 - i); - expected.push_back(static_cast((positive >> 8) & 0xff)); - expected.push_back(static_cast(positive & 0xff)); + expected.push_back(static_cast((positive >> 8) & 0xff)); + expected.push_back(static_cast(positive & 0xff)); // compare result + size const auto result = json::to_cbor(j); @@ -231,7 +231,7 @@ TEST(CborSignedTest, Neg2) TEST(CborSignedTest, NegInt16) { json j = -9263; - std::string expected = "\x39\x24\x2e"; + std::vector expected = {0x39,0x24,0x2e}; const auto result = json::to_cbor(j); ASSERT_EQ(result, expected); @@ -257,9 +257,9 @@ TEST(CborSignedTest, Neg1) ASSERT_TRUE(j.is_number_integer()); // create expected byte vector - std::string expected; - expected.push_back(0x38); - expected.push_back(static_cast(-1 - i)); + std::vector expected; + expected.push_back(static_cast(0x38)); + expected.push_back(static_cast(-1 - i)); // compare result + size const auto result = json::to_cbor(j); @@ -289,8 +289,8 @@ TEST(CborSignedTest, Neg0) ASSERT_TRUE(j.is_number_integer()); // create expected byte vector - std::string expected; - expected.push_back(static_cast(0x20 - 1 - static_cast(i))); + std::vector expected; + expected.push_back(static_cast(0x20 - 1 - static_cast(i))); // compare result + size const auto result = json::to_cbor(j); @@ -314,14 +314,14 @@ TEST(CborSignedTest, Pos0) // create JSON value with integer number json j = -1; - j.get_ref() = static_cast(i); + j.get_ref() = static_cast(i); // check type ASSERT_TRUE(j.is_number_integer()); // create expected byte vector - std::string expected; - expected.push_back(static_cast(i)); + std::vector expected; + expected.push_back(static_cast(i)); // compare result + size const auto result = json::to_cbor(j); @@ -329,7 +329,7 @@ TEST(CborSignedTest, Pos0) ASSERT_EQ(result.size(), 1u); // check individual bytes - EXPECT_EQ(result[0], static_cast(i)); + EXPECT_EQ(result[0], static_cast(i)); // roundtrip EXPECT_EQ(json::from_cbor(result), j); @@ -345,15 +345,15 @@ TEST(CborSignedTest, Pos1) // create JSON value with integer number json j = -1; - j.get_ref() = static_cast(i); + j.get_ref() = static_cast(i); // check type ASSERT_TRUE(j.is_number_integer()); // create expected byte vector - std::string expected; - expected.push_back(static_cast(0x18)); - expected.push_back(static_cast(i)); + std::vector expected; + expected.push_back(static_cast(0x18)); + expected.push_back(static_cast(i)); // compare result + size const auto result = json::to_cbor(j); @@ -362,7 +362,7 @@ TEST(CborSignedTest, Pos1) // check individual bytes EXPECT_EQ(result[0], 0x18); - EXPECT_EQ(result[1], static_cast(i)); + EXPECT_EQ(result[1], static_cast(i)); // roundtrip EXPECT_EQ(json::from_cbor(result), j); @@ -378,16 +378,16 @@ TEST(CborSignedTest, Pos2) // create JSON value with integer number json j = -1; - j.get_ref() = static_cast(i); + j.get_ref() = static_cast(i); // check type ASSERT_TRUE(j.is_number_integer()); // create expected byte vector - std::string expected; - expected.push_back(static_cast(0x19)); - expected.push_back(static_cast((i >> 8) & 0xff)); - expected.push_back(static_cast(i & 0xff)); + std::vector expected; + expected.push_back(static_cast(0x19)); + expected.push_back(static_cast((i >> 8) & 0xff)); + expected.push_back(static_cast(i & 0xff)); // compare result + size const auto result = json::to_cbor(j); @@ -410,19 +410,19 @@ TEST_P(CborSignedPos4Test, Case) { // create JSON value with integer number json j = -1; - j.get_ref() = - static_cast(GetParam()); + j.get_ref() = + static_cast(GetParam()); // check type ASSERT_TRUE(j.is_number_integer()); // create expected byte vector - std::string expected; + std::vector expected; expected.push_back(0x1a); - expected.push_back(static_cast((GetParam() >> 24) & 0xff)); - expected.push_back(static_cast((GetParam() >> 16) & 0xff)); - expected.push_back(static_cast((GetParam() >> 8) & 0xff)); - expected.push_back(static_cast(GetParam() & 0xff)); + expected.push_back(static_cast((GetParam() >> 24) & 0xff)); + expected.push_back(static_cast((GetParam() >> 16) & 0xff)); + expected.push_back(static_cast((GetParam() >> 8) & 0xff)); + expected.push_back(static_cast(GetParam() & 0xff)); // compare result + size const auto result = json::to_cbor(j); @@ -456,23 +456,23 @@ TEST_P(CborSignedPos8Test, Case) { // create JSON value with integer number json j = -1; - j.get_ref() = - static_cast(GetParam()); + j.get_ref() = + static_cast(GetParam()); // check type ASSERT_TRUE(j.is_number_integer()); // create expected byte vector - std::string expected; + std::vector expected; expected.push_back(0x1b); - expected.push_back(static_cast((GetParam() >> 070) & 0xff)); - expected.push_back(static_cast((GetParam() >> 060) & 0xff)); - expected.push_back(static_cast((GetParam() >> 050) & 0xff)); - expected.push_back(static_cast((GetParam() >> 040) & 0xff)); - expected.push_back(static_cast((GetParam() >> 030) & 0xff)); - expected.push_back(static_cast((GetParam() >> 020) & 0xff)); - expected.push_back(static_cast((GetParam() >> 010) & 0xff)); - expected.push_back(static_cast(GetParam() & 0xff)); + expected.push_back(static_cast((GetParam() >> 070) & 0xff)); + expected.push_back(static_cast((GetParam() >> 060) & 0xff)); + expected.push_back(static_cast((GetParam() >> 050) & 0xff)); + expected.push_back(static_cast((GetParam() >> 040) & 0xff)); + expected.push_back(static_cast((GetParam() >> 030) & 0xff)); + expected.push_back(static_cast((GetParam() >> 020) & 0xff)); + expected.push_back(static_cast((GetParam() >> 010) & 0xff)); + expected.push_back(static_cast(GetParam() & 0xff)); // compare result + size const auto result = json::to_cbor(j); @@ -517,10 +517,10 @@ INSTANTIATE_TEST_CASE_P(CborSignedPos8Tests, CborSignedPos8Test, ASSERT_TRUE(j.is_number_integer()); // create expected byte vector - std::string expected; + std::vector expected; expected.push_back(0xd1); - expected.push_back(static_cast((i >> 8) & 0xff)); - expected.push_back(static_cast(i & 0xff)); + expected.push_back(static_cast((i >> 8) & 0xff)); + expected.push_back(static_cast(i & 0xff)); // compare result + size const auto result = json::to_msgpack(j); @@ -552,8 +552,8 @@ TEST(CborUnsignedTest, Pos0) ASSERT_TRUE(j.is_number_unsigned()); // create expected byte vector - std::string expected; - expected.push_back(static_cast(i)); + std::vector expected; + expected.push_back(static_cast(i)); // compare result + size const auto result = json::to_cbor(j); @@ -561,7 +561,7 @@ TEST(CborUnsignedTest, Pos0) ASSERT_EQ(result.size(), 1u); // check individual bytes - EXPECT_EQ(result[0], static_cast(i)); + EXPECT_EQ(result[0], static_cast(i)); // roundtrip EXPECT_EQ(json::from_cbor(result), j); @@ -582,9 +582,9 @@ TEST(CborUnsignedTest, Pos1) ASSERT_TRUE(j.is_number_unsigned()); // create expected byte vector - std::string expected; + std::vector expected; expected.push_back(0x18); - expected.push_back(static_cast(i)); + expected.push_back(static_cast(i)); // compare result + size const auto result = json::to_cbor(j); @@ -615,10 +615,10 @@ TEST(CborUnsignedTest, Pos2) ASSERT_TRUE(j.is_number_unsigned()); // create expected byte vector - std::string expected; + std::vector expected; expected.push_back(0x19); - expected.push_back(static_cast((i >> 8) & 0xff)); - expected.push_back(static_cast(i & 0xff)); + expected.push_back(static_cast((i >> 8) & 0xff)); + expected.push_back(static_cast(i & 0xff)); // compare result + size const auto result = json::to_cbor(j); @@ -646,12 +646,12 @@ TEST_P(CborUnsignedPos4Test, Case) ASSERT_TRUE(j.is_number_unsigned()); // create expected byte vector - std::string expected; + std::vector expected; expected.push_back(0x1a); - expected.push_back(static_cast((GetParam() >> 24) & 0xff)); - expected.push_back(static_cast((GetParam() >> 16) & 0xff)); - expected.push_back(static_cast((GetParam() >> 8) & 0xff)); - expected.push_back(static_cast(GetParam() & 0xff)); + expected.push_back(static_cast((GetParam() >> 24) & 0xff)); + expected.push_back(static_cast((GetParam() >> 16) & 0xff)); + expected.push_back(static_cast((GetParam() >> 8) & 0xff)); + expected.push_back(static_cast(GetParam() & 0xff)); // compare result + size const auto result = json::to_cbor(j); @@ -684,16 +684,16 @@ TEST_P(CborUnsignedPos8Test, Case) ASSERT_TRUE(j.is_number_unsigned()); // create expected byte vector - std::string expected; + std::vector expected; expected.push_back(0x1b); - expected.push_back(static_cast((GetParam() >> 070) & 0xff)); - expected.push_back(static_cast((GetParam() >> 060) & 0xff)); - expected.push_back(static_cast((GetParam() >> 050) & 0xff)); - expected.push_back(static_cast((GetParam() >> 040) & 0xff)); - expected.push_back(static_cast((GetParam() >> 030) & 0xff)); - expected.push_back(static_cast((GetParam() >> 020) & 0xff)); - expected.push_back(static_cast((GetParam() >> 010) & 0xff)); - expected.push_back(static_cast(GetParam() & 0xff)); + expected.push_back(static_cast((GetParam() >> 070) & 0xff)); + expected.push_back(static_cast((GetParam() >> 060) & 0xff)); + expected.push_back(static_cast((GetParam() >> 050) & 0xff)); + expected.push_back(static_cast((GetParam() >> 040) & 0xff)); + expected.push_back(static_cast((GetParam() >> 030) & 0xff)); + expected.push_back(static_cast((GetParam() >> 020) & 0xff)); + expected.push_back(static_cast((GetParam() >> 010) & 0xff)); + expected.push_back(static_cast(GetParam() & 0xff)); // compare result + size const auto result = json::to_cbor(j); @@ -724,7 +724,7 @@ TEST(CborFloatTest, Number) { double v = 3.1415925; json j = v; - std::string expected = "\xfb\x40\x09\x21\xfb\x3f\xa6\xde\xfc"; + std::vector expected = {0xfb,0x40,0x09,0x21,0xfb,0x3f,0xa6,0xde,0xfc}; const auto result = json::to_cbor(j); EXPECT_EQ(result, expected); @@ -735,16 +735,16 @@ TEST(CborFloatTest, Number) TEST(CborFloatTest, HalfInfinity) { - json j = json::from_cbor(wpi::StringRef("\xf9\x7c\x00", 3)); - json::number_float_t d = j; + json j = json::from_cbor(std::vector({0xf9,0x7c,0x00})); + double d = j; EXPECT_FALSE(std::isfinite(d)); EXPECT_EQ(j.dump(), "null"); } TEST(CborFloatTest, HalfNaN) { - json j = json::from_cbor("\xf9\x7c\x01"); - json::number_float_t d = j; + json j = json::from_cbor(std::vector({0xf9,0x7c,0x01})); + double d = j; EXPECT_TRUE(std::isnan(d)); EXPECT_EQ(j.dump(), "null"); } @@ -761,9 +761,12 @@ TEST(CborStringTest, String1) json j = s; // create expected byte vector - std::string expected; - expected.push_back(static_cast(0x60 + N)); - expected.append(s); + std::vector expected; + expected.push_back(static_cast(0x60 + N)); + for (size_t i = 0; i < N; ++i) + { + expected.push_back('x'); + } // compare result + size const auto result = json::to_cbor(j); @@ -792,10 +795,13 @@ TEST(CborStringTest, String2) json j = s; // create expected byte vector - std::string expected; - expected.push_back(0x78); - expected.push_back(static_cast(N)); - expected.append(s); + std::vector expected; + expected.push_back(static_cast(0x78)); + expected.push_back(static_cast(N)); + for (size_t i = 0; i < N; ++i) + { + expected.push_back('x'); + } // compare result + size const auto result = json::to_cbor(j); @@ -818,11 +824,14 @@ TEST_P(CborString3Test, Case) json j = s; // create expected byte vector - std::string expected; + std::vector expected; expected.push_back(0x79); - expected.push_back(static_cast((GetParam() >> 8) & 0xff)); - expected.push_back(static_cast(GetParam() & 0xff)); - expected.append(s); + expected.push_back(static_cast((GetParam() >> 8) & 0xff)); + expected.push_back(static_cast(GetParam() & 0xff)); + for (size_t i = 0; i < GetParam(); ++i) + { + expected.push_back('x'); + } // compare result + size const auto result = json::to_cbor(j); @@ -856,13 +865,16 @@ TEST_P(CborString5Test, Case) json j = s; // create expected byte vector - std::string expected; + std::vector expected; expected.push_back(0x7a); - expected.push_back(static_cast((GetParam() >> 24) & 0xff)); - expected.push_back(static_cast((GetParam() >> 16) & 0xff)); - expected.push_back(static_cast((GetParam() >> 8) & 0xff)); - expected.push_back(static_cast(GetParam() & 0xff)); - expected.append(s); + expected.push_back(static_cast((GetParam() >> 24) & 0xff)); + expected.push_back(static_cast((GetParam() >> 16) & 0xff)); + expected.push_back(static_cast((GetParam() >> 8) & 0xff)); + expected.push_back(static_cast(GetParam() & 0xff)); + for (size_t i = 0; i < GetParam(); ++i) + { + expected.push_back('x'); + } // compare result + size const auto result = json::to_cbor(j); @@ -887,7 +899,7 @@ INSTANTIATE_TEST_CASE_P(CborString5Tests, CborString5Test, TEST(CborArrayTest, Empty) { json j = json::array(); - std::string expected = "\x80"; + std::vector expected = {0x80}; const auto result = json::to_cbor(j); EXPECT_EQ(result, expected); @@ -899,7 +911,7 @@ TEST(CborArrayTest, Empty) TEST(CborArrayTest, Null) { json j = {nullptr}; - std::string expected = "\x81\xf6"; + std::vector expected = {0x81,0xf6}; const auto result = json::to_cbor(j); EXPECT_EQ(result, expected); @@ -911,7 +923,7 @@ TEST(CborArrayTest, Null) TEST(CborArrayTest, Simple) { json j = json::parse("[1,2,3,4,5]"); - std::string expected = "\x85\x01\x02\x03\x04\x05"; + std::vector expected = {0x85,0x01,0x02,0x03,0x04,0x05}; const auto result = json::to_cbor(j); EXPECT_EQ(result, expected); @@ -923,7 +935,7 @@ TEST(CborArrayTest, Simple) TEST(CborArrayTest, NestEmpty) { json j = json::parse("[[[[]]]]"); - std::string expected = "\x81\x81\x81\x80"; + std::vector expected = {0x81,0x81,0x81,0x80}; const auto result = json::to_cbor(j); EXPECT_EQ(result, expected); @@ -935,7 +947,7 @@ TEST(CborArrayTest, NestEmpty) TEST(CborArrayTest, UInt16) { json j(257, nullptr); - std::string expected(j.size() + 3, static_cast(0xf6)); // all null + std::vector expected(j.size() + 3, 0xf6); // all null expected[0] = static_cast(0x99); // array 16 bit expected[1] = 0x01; // size (0x0101), byte 0 expected[2] = 0x01; // size (0x0101), byte 1 @@ -950,7 +962,7 @@ TEST(CborArrayTest, UInt16) TEST(CborArrayTest, UInt32) { json j(65793, nullptr); - std::string expected(j.size() + 5, static_cast(0xf6)); // all null + std::vector expected(j.size() + 5, 0xf6); // all null expected[0] = static_cast(0x9a); // array 32 bit expected[1] = 0x00; // size (0x00010101), byte 0 expected[2] = 0x01; // size (0x00010101), byte 1 @@ -968,7 +980,7 @@ TEST(CborArrayTest, UInt32) TEST(CborArrayTest, UInt64) { json j(4294967296, nullptr); - std::string expected(j.size() + 9, static_cast(0xf6)); // all null + std::vector expected(j.size() + 9, 0xf6); // all null expected[0] = 0x9b; // array 64 bit expected[1] = 0x00; // size (0x0000000100000000), byte 0 expected[2] = 0x00; // size (0x0000000100000000), byte 1 @@ -989,7 +1001,7 @@ TEST(CborArrayTest, UInt64) TEST(CborObjectTest, Empty) { json j = json::object(); - std::string expected = "\xa0"; + std::vector expected = {0xa0}; const auto result = json::to_cbor(j); EXPECT_EQ(result, expected); @@ -1001,7 +1013,7 @@ TEST(CborObjectTest, Empty) TEST(CborObjectTest, EmptyKey) { json j = {{"", nullptr}}; - std::string expected = "\xa1\x60\xf6"; + std::vector expected = {0xa1,0x60,0xf6}; const auto result = json::to_cbor(j); EXPECT_EQ(result, expected); @@ -1013,7 +1025,7 @@ TEST(CborObjectTest, EmptyKey) TEST(CborObjectTest, NestedEmpty) { json j = json::parse("{\"a\": {\"b\": {\"c\": {}}}}"); - std::string expected = "\xa1\x61\x61\xa1\x61\x62\xa1\x61\x63\xa0"; + std::vector expected = {0xa1,0x61,0x61,0xa1,0x61,0x62,0xa1,0x61,0x63,0xa0}; const auto result = json::to_cbor(j); EXPECT_EQ(result, expected); @@ -1042,8 +1054,8 @@ TEST(CborObjectTest, UInt8) // size and the overall size. The rest is then handled in the // roundtrip check. ASSERT_EQ(result.size(), 1787u); // 1 type, 1 size, 255*7 content - EXPECT_EQ(result[0], static_cast(0xb8)); // map 8 bit - EXPECT_EQ(result[1], static_cast(0xff)); // size byte (0xff) + EXPECT_EQ(result[0], static_cast(0xb8)); // map 8 bit + EXPECT_EQ(result[1], static_cast(0xff)); // size byte (0xff) // roundtrip EXPECT_EQ(json::from_cbor(result), j); } @@ -1069,7 +1081,7 @@ TEST(CborObjectTest, UInt16) // size and the overall size. The rest is then handled in the // roundtrip check. ASSERT_EQ(result.size(), 1795u); // 1 type, 2 size, 256*7 content - EXPECT_EQ(result[0], static_cast(0xb9)); // map 16 bit + EXPECT_EQ(result[0], static_cast(0xb9)); // map 16 bit EXPECT_EQ(result[1], 0x01); // byte 0 of size (0x0100) EXPECT_EQ(result[2], 0x00); // byte 1 of size (0x0100) @@ -1098,7 +1110,7 @@ TEST(CborObjectTest, UInt32) // size and the overall size. The rest is then handled in the // roundtrip check. ASSERT_EQ(result.size(), 458757u); // 1 type, 4 size, 65536*7 content - EXPECT_EQ(result[0], static_cast(0xba)); // map 32 bit + EXPECT_EQ(result[0], static_cast(0xba)); // map 32 bit EXPECT_EQ(result[1], 0x00); // byte 0 of size (0x00010000) EXPECT_EQ(result[2], 0x01); // byte 1 of size (0x00010000) EXPECT_EQ(result[3], 0x00); // byte 2 of size (0x00010000) @@ -1111,7 +1123,7 @@ TEST(CborObjectTest, UInt32) // 0x7b (string) TEST(CborAdditionalDeserializationTest, Case7b) { - std::string given("\x7b\x00\x00\x00\x00\x00\x00\x00\x01\x61", 10); + std::vector given{0x7b,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x01,0x61}; json j = json::from_cbor(given); EXPECT_EQ(j, "a"); } @@ -1119,7 +1131,7 @@ TEST(CborAdditionalDeserializationTest, Case7b) // 0x9b (array) TEST(CborAdditionalDeserializationTest, Case9b) { - std::string given("\x9b\x00\x00\x00\x00\x00\x00\x00\x01\xf4", 10); + std::vector given{0x9b,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x01,0xf4}; json j = json::from_cbor(given); EXPECT_EQ(j, json::parse("[false]")); } @@ -1127,101 +1139,101 @@ TEST(CborAdditionalDeserializationTest, Case9b) // 0xbb (map) TEST(CborAdditionalDeserializationTest, Casebb) { - std::string given("\xbb\x00\x00\x00\x00\x00\x00\x00\x01\x60\xf4", 11); + std::vector given{0xbb,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x01,0x60,0xf4}; json j = json::from_cbor(given); EXPECT_EQ(j, json::parse("{\"\": false}")); } TEST(CborErrorTest, TooShortByteVector) { - EXPECT_THROW_MSG(json::from_cbor("\x18"), json::parse_error, + EXPECT_THROW_MSG(json::from_cbor(std::vector({0x18})), json::parse_error, "[json.exception.parse_error.110] parse error at 2: unexpected end of input"); - EXPECT_THROW_MSG(json::from_cbor("\x19"), json::parse_error, + EXPECT_THROW_MSG(json::from_cbor(std::vector({0x19})), json::parse_error, "[json.exception.parse_error.110] parse error at 2: unexpected end of input"); - EXPECT_THROW_MSG(json::from_cbor(wpi::StringRef("\x19\x00", 2)), json::parse_error, + EXPECT_THROW_MSG(json::from_cbor(std::vector({0x19,0x00})), json::parse_error, "[json.exception.parse_error.110] parse error at 3: unexpected end of input"); - EXPECT_THROW_MSG(json::from_cbor("\x1a"), json::parse_error, + EXPECT_THROW_MSG(json::from_cbor(std::vector({0x1a})), json::parse_error, "[json.exception.parse_error.110] parse error at 2: unexpected end of input"); - EXPECT_THROW_MSG(json::from_cbor(wpi::StringRef("\x1a\x00", 2)), json::parse_error, + EXPECT_THROW_MSG(json::from_cbor(std::vector({0x1a,0x00})), json::parse_error, "[json.exception.parse_error.110] parse error at 3: unexpected end of input"); - EXPECT_THROW_MSG(json::from_cbor(wpi::StringRef("\x1a\x00\x00", 3)), json::parse_error, + EXPECT_THROW_MSG(json::from_cbor(std::vector({0x1a,0x00,0x00})), json::parse_error, "[json.exception.parse_error.110] parse error at 4: unexpected end of input"); - EXPECT_THROW_MSG(json::from_cbor(wpi::StringRef("\x1a\x00\x00\x00", 4)), json::parse_error, + EXPECT_THROW_MSG(json::from_cbor(std::vector({0x1a,0x00,0x00,0x00})), json::parse_error, "[json.exception.parse_error.110] parse error at 5: unexpected end of input"); - EXPECT_THROW_MSG(json::from_cbor("\x1b"), json::parse_error, + EXPECT_THROW_MSG(json::from_cbor(std::vector({0x1b})), json::parse_error, "[json.exception.parse_error.110] parse error at 2: unexpected end of input"); - EXPECT_THROW_MSG(json::from_cbor(wpi::StringRef("\x1b\x00", 2)), json::parse_error, + EXPECT_THROW_MSG(json::from_cbor(std::vector({0x1b,0x00})), json::parse_error, "[json.exception.parse_error.110] parse error at 3: unexpected end of input"); - EXPECT_THROW_MSG(json::from_cbor(wpi::StringRef("\x1b\x00\x00", 3)), json::parse_error, + EXPECT_THROW_MSG(json::from_cbor(std::vector({0x1b,0x00,0x00})), json::parse_error, "[json.exception.parse_error.110] parse error at 4: unexpected end of input"); - EXPECT_THROW_MSG(json::from_cbor(wpi::StringRef("\x1b\x00\x00\x00", 4)), json::parse_error, + EXPECT_THROW_MSG(json::from_cbor(std::vector({0x1b,0x00,0x00,0x00})), json::parse_error, "[json.exception.parse_error.110] parse error at 5: unexpected end of input"); - EXPECT_THROW_MSG(json::from_cbor(wpi::StringRef("\x1b\x00\x00\x00\x00", 5)), json::parse_error, + EXPECT_THROW_MSG(json::from_cbor(std::vector({0x1b,0x00,0x00,0x00,0x00})), json::parse_error, "[json.exception.parse_error.110] parse error at 6: unexpected end of input"); - EXPECT_THROW_MSG(json::from_cbor(wpi::StringRef("\x1b\x00\x00\x00\x00\x00", 6)), json::parse_error, + EXPECT_THROW_MSG(json::from_cbor(std::vector({0x1b,0x00,0x00,0x00,0x00,0x00})), json::parse_error, "[json.exception.parse_error.110] parse error at 7: unexpected end of input"); - EXPECT_THROW_MSG(json::from_cbor(wpi::StringRef("\x1b\x00\x00\x00\x00\x00\x00", 7)), json::parse_error, + EXPECT_THROW_MSG(json::from_cbor(std::vector({0x1b,0x00,0x00,0x00,0x00,0x00,0x00})), json::parse_error, "[json.exception.parse_error.110] parse error at 8: unexpected end of input"); - EXPECT_THROW_MSG(json::from_cbor(wpi::StringRef("\x1b\x00\x00\x00\x00\x00\x00\x00", 8)), json::parse_error, + EXPECT_THROW_MSG(json::from_cbor(std::vector({0x1b,0x00,0x00,0x00,0x00,0x00,0x00,0x00})), json::parse_error, "[json.exception.parse_error.110] parse error at 9: unexpected end of input"); } TEST(CborErrorTest, UnsupportedBytesConcrete) { - EXPECT_THROW_MSG(json::from_cbor("\x1c"), json::parse_error, + EXPECT_THROW_MSG(json::from_cbor(std::vector({0x1c})), json::parse_error, "[json.exception.parse_error.112] parse error at 1: error reading CBOR; last byte: 0x1c"); - EXPECT_THROW_MSG(json::from_cbor("\xf8"), json::parse_error, + EXPECT_THROW_MSG(json::from_cbor(std::vector({0xf8})), json::parse_error, "[json.exception.parse_error.112] parse error at 1: error reading CBOR; last byte: 0xf8"); } -class CborUnsupportedBytesTest : public ::testing::TestWithParam { +class CborUnsupportedBytesTest : public ::testing::TestWithParam { }; TEST_P(CborUnsupportedBytesTest, Case) { - EXPECT_THROW(json::from_cbor(GetParam()), json::parse_error); + EXPECT_THROW(json::from_cbor(std::vector({GetParam()})), json::parse_error); } -static const char* unsupported_bytes_cases[] = { +static const uint8_t unsupported_bytes_cases[] = { // ? - "\x1c\x1d\x1e\x1f", + 0x1c, 0x1d, 0x1e, 0x1f, // ? - "\x3c\x3d\x3e\x3f", + 0x3c, 0x3d, 0x3e, 0x3f, // byte strings - "\x40\x41\x42\x43\x44\x45\x46\x47\x48\x49\x50\x51\x52\x53\x54\x55\x56\x57", + 0x40, 0x41, 0x42, 0x43, 0x44, 0x45, 0x46, 0x47, 0x48, 0x49, 0x50, 0x51, 0x52, 0x53, 0x54, 0x55, 0x56, 0x57, // byte strings - "\x58\x59\x5a\x5b", + 0x58, 0x59, 0x5a, 0x5b, // ? - "\x5c\x5d\x5e", + 0x5c, 0x5d, 0x5e, // byte string - "\x5f", + 0x5f, // ? - "\x7c\x7d\x7e", + 0x7c, 0x7d, 0x7e, // ? - "\x9c\x9d\x9e", + 0x9c, 0x9d, 0x9e, // ? - "\xbc\xbd\xbe", + 0xbc, 0xbd, 0xbe, // date/time - "\xc0\xc1", + 0xc0, 0xc1, // bignum - "\xc2\xc3", + 0xc2, 0xc3, // fraction - "\xc4", + 0xc4, // bigfloat - "\xc5", + 0xc5, // tagged item - "\xc6\xc7\xc8\xc9\xca\xcb\xcc\xcd\xce\xcf\xd0\xd1\xd2\xd3\xd4", + 0xc6, 0xc7, 0xc8, 0xc9, 0xca, 0xcb, 0xcc, 0xcd, 0xce, 0xcf, 0xd0, 0xd1, 0xd2, 0xd3, 0xd4, // expected conversion - "\xd5\xd6\xd7", + 0xd5, 0xd6, 0xd7, // more tagged items - "\xd8\xd9\xda\xdb", + 0xd8, 0xd9, 0xda, 0xdb, // ? - "\xdc\xdd\xde\xdf", + 0xdc, 0xdd, 0xde, 0xdf, // (simple value) - "\xe0\xe1\xe2\xe3\xe4\xe5\xe6\xe7\xe8\xe9\xea\xeb\xec\xed\xee\xef\xf0\xf1\xf2\xf3", + 0xe0, 0xe1, 0xe2, 0xe3, 0xe4, 0xe5, 0xe6, 0xe7, 0xe8, 0xe9, 0xea, 0xeb, 0xec, 0xed, 0xee, 0xef, 0xf0, 0xf1, 0xf2, 0xf3, // undefined - "\xf7", + 0xf7, // simple value - "\xf8", + 0xf8, }; INSTANTIATE_TEST_CASE_P(CborUnsupportedBytesTests, CborUnsupportedBytesTest, @@ -1543,11 +1555,11 @@ TEST(CborFirstBytesTest, Unsupported) for (auto i = 0; i < 256; ++i) { - const auto byte = static_cast(i); + const auto byte = static_cast(i); try { - json::from_cbor(wpi::StringRef(&byte, 1)); + json::from_cbor(std::vector(1, byte)); } catch (const json::parse_error& e) { @@ -1571,7 +1583,7 @@ TEST(CborFirstBytesTest, Unsupported) namespace internal { struct CborRoundtripTestParam { const char* plain; - wpi::StringRef encoded; + std::vector encoded; bool test_encode; }; } // namespace internal @@ -1589,100 +1601,100 @@ TEST_P(CborRoundtripTest, Case) } static const internal::CborRoundtripTestParam rfc7049_appendix_a_numbers[] = { - {"0", wpi::StringRef("\x00", 1), true}, - {"1", "\x01", true}, - {"10", "\x0a", true}, - {"23", "\x17", true}, - {"24", "\x18\x18", true}, - {"25", "\x18\x19", true}, - {"100", "\x18\x64", true}, - {"1000", "\x19\x03\xe8", true}, - {"1000000", wpi::StringRef("\x1a\x00\x0f\x42\x40", 5), true}, - {"1000000000000", wpi::StringRef("\x1b\x00\x00\x00\xe8\xd4\xa5\x10\x00", 9), true}, - {"18446744073709551615", "\x1b\xff\xff\xff\xff\xff\xff\xff\xff", true}, + {"0", {0x00}, true}, + {"1", {0x01}, true}, + {"10", {0x0a}, true}, + {"23", {0x17}, true}, + {"24", {0x18,0x18}, true}, + {"25", {0x18,0x19}, true}, + {"100", {0x18,0x64}, true}, + {"1000", {0x19,0x03,0xe8}, true}, + {"1000000", {0x1a,0x00,0x0f,0x42,0x40}, true}, + {"1000000000000", {0x1b,0x00,0x00,0x00,0xe8,0xd4,0xa5,0x10,0x00}, true}, + {"18446744073709551615", {0x1b,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff}, true}, // positive bignum is not supported - //{"18446744073709551616", wpi::StringRef("\xc2\x49\x01\x00\x00\x00\x00\x00\x00\x00\x00", 11), true}, - //{"-18446744073709551616", "\x3b\xff\xff\xff\xff\xff\xff\xff\xff", true}, + //{"18446744073709551616", {0xc2,0x49,0x01,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00", 11), true}, + //{"-18446744073709551616", {0x3b,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff}, true}, // negative bignum is not supported - //{"-18446744073709551617", wpi::StringRef("\xc3\x49\x01\x00\x00\x00\x00\x00\x00\x00\x00", 11), true}, - {"-1", "\x20", true}, - {"-10", "\x29", true}, - {"-100", "\x38\x63", true}, - {"-1000", "\x39\x03\xe7", true}, + //{"-18446744073709551617", {0xc3,0x49,0x01,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00}, true}, + {"-1", {0x20}, true}, + {"-10", {0x29}, true}, + {"-100", {0x38,0x63}, true}, + {"-1000", {0x39,0x03,0xe7}, true}, // half-precision float - {"0.0", wpi::StringRef("\xf9\x00\x00", 3), false}, + {"0.0", {0xf9,0x00,0x00}, false}, // half-precision float - {"-0.0", wpi::StringRef("\xf9\x80\x00", 3), false}, + {"-0.0", {0xf9,0x80,0x00}, false}, // half-precision float - {"1.0", wpi::StringRef("\xf9\x3c\x00", 3), false}, - {"1.1", "\xfb\x3f\xf1\x99\x99\x99\x99\x99\x9a", true}, + {"1.0", {0xf9,0x3c,0x00}, false}, + {"1.1", {0xfb,0x3f,0xf1,0x99,0x99,0x99,0x99,0x99,0x9a}, true}, // half-precision float - {"1.5", wpi::StringRef("\xf9\x3e\x00", 3), false}, + {"1.5", {0xf9,0x3e,0x00}, false}, // half-precision float - {"65504.0", "\xf9\x7b\xff", false}, - {"100000.0", wpi::StringRef("\xfa\x47\xc3\x50\x00", 5), false}, - {"3.4028234663852886e+38", "\xfa\x7f\x7f\xff\xff", false}, - {"1.0e+300", wpi::StringRef("\xfb\x7e\x37\xe4\x3c\x88\x00\x75\x9c", 9), true}, + {"65504.0", {0xf9,0x7b,0xff}, false}, + {"100000.0", {0xfa,0x47,0xc3,0x50,0x00}, false}, + {"3.4028234663852886e+38", {0xfa,0x7f,0x7f,0xff,0xff}, false}, + {"1.0e+300", {0xfb,0x7e,0x37,0xe4,0x3c,0x88,0x00,0x75,0x9c}, true}, // half-precision float - {"5.960464477539063e-8", wpi::StringRef("\xf9\x00\x01", 3), false}, + {"5.960464477539063e-8", {0xf9,0x00,0x01}, false}, // half-precision float - {"0.00006103515625", wpi::StringRef("\xf9\x04\x00", 3), false}, + {"0.00006103515625", {0xf9,0x04,0x00}, false}, // half-precision float - {"-4.0", wpi::StringRef("\xf9\xc4\x00", 3), false}, - {"-4.1", "\xfb\xc0\x10\x66\x66\x66\x66\x66\x66", true}, + {"-4.0", {0xf9,0xc4,0x00}, false}, + {"-4.1", {0xfb,0xc0,0x10,0x66,0x66,0x66,0x66,0x66,0x66}, true}, }; INSTANTIATE_TEST_CASE_P(CborRfc7049AppendixANumberTests, CborRoundtripTest, ::testing::ValuesIn(rfc7049_appendix_a_numbers), ); static const internal::CborRoundtripTestParam rfc7049_appendix_a_simple_values[] = { - {"false", "\xf4", true}, - {"true", "\xf5", true}, + {"false", {0xf4}, true}, + {"true", {0xf5}, true}, }; INSTANTIATE_TEST_CASE_P(CborRfc7049AppendixASimpleValueTests, CborRoundtripTest, ::testing::ValuesIn(rfc7049_appendix_a_simple_values), ); static const internal::CborRoundtripTestParam rfc7049_appendix_a_strings[] = { - {"\"\"", "\x60", true}, - {"\"a\"", "\x61\x61", true}, - {"\"IETF\"", "\x64\x49\x45\x54\x46", true}, - {"\"\\u00fc\"", "\x62\xc3\xbc", true}, - {"\"\\u6c34\"", "\x63\xe6\xb0\xb4", true}, - {"\"\\ud800\\udd51\"", "\x64\xf0\x90\x85\x91", true}, + {"\"\"", {0x60}, true}, + {"\"a\"", {0x61,0x61}, true}, + {"\"IETF\"", {0x64,0x49,0x45,0x54,0x46}, true}, + {"\"\\u00fc\"", {0x62,0xc3,0xbc}, true}, + {"\"\\u6c34\"", {0x63,0xe6,0xb0,0xb4}, true}, + {"\"\\ud800\\udd51\"", {0x64,0xf0,0x90,0x85,0x91}, true}, // indefinite length strings - {"\"streaming\"", "\x7f\x73\x74\x72\x65\x61\x6d\x69\x6e\x67\xff", false}, + {"\"streaming\"", {0x7f,0x65,0x73,0x74,0x72,0x65,0x61,0x64,0x6d,0x69,0x6e,0x67,0xff}, false}, }; INSTANTIATE_TEST_CASE_P(CborRfc7049AppendixAStringTests, CborRoundtripTest, ::testing::ValuesIn(rfc7049_appendix_a_strings), ); static const internal::CborRoundtripTestParam rfc7049_appendix_a_arrays[] = { - {"[]", "\x80", true}, - {"[1, 2, 3]", "\x83\x01\x02\x03", true}, - {"[1, [2, 3], [4, 5]]", "\x83\x01\x82\x02\x03\x82\x04\x05", true}, - {"[1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25]", "\x98\x19\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0a\x0b\x0c\x0d\x0e\x0f\x10\x11\x12\x13\x14\x15\x16\x17\x18\x18\x18\x19", true}, + {"[]", {0x80}, true}, + {"[1, 2, 3]", {0x83,0x01,0x02,0x03}, true}, + {"[1, [2, 3], [4, 5]]", {0x83,0x01,0x82,0x02,0x03,0x82,0x04,0x05}, true}, + {"[1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25]", {0x98,0x19,0x01,0x02,0x03,0x04,0x05,0x06,0x07,0x08,0x09,0x0a,0x0b,0x0c,0x0d,0x0e,0x0f,0x10,0x11,0x12,0x13,0x14,0x15,0x16,0x17,0x18,0x18,0x18,0x19}, true}, // indefinite length arrays - {"[]", "\x9f\xff", false}, - {"[1, [2, 3], [4, 5]] ", "\x9f\x01\x82\x02\x03\x9f\x04\x05\xff\xff", false}, - {"[1, [2, 3], [4, 5]]", "\x9f\x01\x82\x02\x03\x82\x04\x05\xff", false}, - {"[1, [2, 3], [4, 5]]", "\x83\x01\x82\x02\x03\x9f\x04\x05\xff", false}, - {"[1, [2, 3], [4, 5]]", "\x83\x01\x9f\x02\x03\xff\x82\x04\x05", false}, - {"[1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25]", "\x9f\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0a\x0b\x0c\x0d\x0e\x0f\x10\x11\x12\x13\x14\x15\x16\x17\x18\x18\x18\x19\xff", false}, + {"[]", {0x9f,0xff}, false}, + {"[1, [2, 3], [4, 5]] ", {0x9f,0x01,0x82,0x02,0x03,0x9f,0x04,0x05,0xff,0xff}, false}, + {"[1, [2, 3], [4, 5]]", {0x9f,0x01,0x82,0x02,0x03,0x82,0x04,0x05,0xff}, false}, + {"[1, [2, 3], [4, 5]]", {0x83,0x01,0x82,0x02,0x03,0x9f,0x04,0x05,0xff}, false}, + {"[1, [2, 3], [4, 5]]", {0x83,0x01,0x9f,0x02,0x03,0xff,0x82,0x04,0x05}, false}, + {"[1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25]", {0x9f,0x01,0x02,0x03,0x04,0x05,0x06,0x07,0x08,0x09,0x0a,0x0b,0x0c,0x0d,0x0e,0x0f,0x10,0x11,0x12,0x13,0x14,0x15,0x16,0x17,0x18,0x18,0x18,0x19,0xff}, false}, }; INSTANTIATE_TEST_CASE_P(CborRfc7049AppendixAArrayTests, CborRoundtripTest, ::testing::ValuesIn(rfc7049_appendix_a_arrays), ); static const internal::CborRoundtripTestParam rfc7049_appendix_a_objects[] = { - {"{}", "\xa0", true}, - {"{\"a\": 1, \"b\": [2, 3]}", "\xa2\x61\x61\x01\x61\x62\x82\x02\x03", true}, - {"[\"a\", {\"b\": \"c\"}]", "\x82\x61\x61\xa1\x61\x62\x61\x63", true}, - {"{\"a\": \"A\", \"b\": \"B\", \"c\": \"C\", \"d\": \"D\", \"e\": \"E\"}", "\xa5\x61\x61\x61\x41\x61\x62\x61\x42\x61\x63\x61\x43\x61\x64\x61\x44\x61\x65\x61\x45", true}, + {"{}", {0xa0}, true}, + {"{\"a\": 1, \"b\": [2, 3]}", {0xa2,0x61,0x61,0x01,0x61,0x62,0x82,0x02,0x03}, true}, + {"[\"a\", {\"b\": \"c\"}]", {0x82,0x61,0x61,0xa1,0x61,0x62,0x61,0x63}, true}, + {"{\"a\": \"A\", \"b\": \"B\", \"c\": \"C\", \"d\": \"D\", \"e\": \"E\"}", {0xa5,0x61,0x61,0x61,0x41,0x61,0x62,0x61,0x42,0x61,0x63,0x61,0x43,0x61,0x64,0x61,0x44,0x61,0x65,0x61,0x45}, true}, // indefinite length objects - {"{\"a\": 1, \"b\": [2, 3]}", "\xbf\x61\x61\x01\x61\x62\x9f\x02\x03\xff\xff", false}, - {"[\"a\", {\"b\": \"c\"}]", "\x82\x61\x61\xbf\x61\x62\x61\x63\xff", false}, - {"{\"Fun\": true, \"Amt\": -2}", "\xbf\x63\x46\x75\x6e\xf5\x63\x41\x6d\x74\x21\xff", false}, + {"{\"a\": 1, \"b\": [2, 3]}", {0xbf,0x61,0x61,0x01,0x61,0x62,0x9f,0x02,0x03,0xff,0xff}, false}, + {"[\"a\", {\"b\": \"c\"}]", {0x82,0x61,0x61,0xbf,0x61,0x62,0x61,0x63,0xff}, false}, + {"{\"Fun\": true, \"Amt\": -2}", {0xbf,0x63,0x46,0x75,0x6e,0xf5,0x63,0x41,0x6d,0x74,0x21,0xff}, false}, }; INSTANTIATE_TEST_CASE_P(CborRfc7049AppendixAObjectTests, CborRoundtripTest, diff --git a/wpiutil/src/test/native/cpp/json/unit-constructor1.cpp b/wpiutil/src/test/native/cpp/json/unit-constructor1.cpp index 40d2b1337b..71c46781f6 100644 --- a/wpiutil/src/test/native/cpp/json/unit-constructor1.cpp +++ b/wpiutil/src/test/native/cpp/json/unit-constructor1.cpp @@ -106,10 +106,10 @@ class JsonConstructObjectImplicitTest : public ::testing::Test { json j_reference; }; -// std::map +// std::map TEST_F(JsonConstructObjectImplicitTest, StdMapStringJson) { - std::map o {{"a", json(1)}, {"b", json(1u)}, {"c", json(2.2)}, {"d", json(false)}, {"e", json("string")}, {"f", json()}}; + std::map o {{"a", json(1)}, {"b", json(1u)}, {"c", json(2.2)}, {"d", json(false)}, {"e", json("string")}, {"f", json()}}; json j(o); EXPECT_EQ(j.type(), json::value_t::object); EXPECT_EQ(j, j_reference); @@ -151,28 +151,28 @@ TEST_F(JsonConstructObjectImplicitTest, StdMapCharPointerJson) EXPECT_EQ(j, j_reference); } -// std::multimap +// std::multimap TEST_F(JsonConstructObjectImplicitTest, StdMultiMapStringJson) { - std::multimap o {{"a", json(1)}, {"b", json(1u)}, {"c", json(2.2)}, {"d", json(false)}, {"e", json("string")}, {"f", json()}}; + std::multimap o {{"a", json(1)}, {"b", json(1u)}, {"c", json(2.2)}, {"d", json(false)}, {"e", json("string")}, {"f", json()}}; json j(o); EXPECT_EQ(j.type(), json::value_t::object); EXPECT_EQ(j, j_reference); } -// std::unordered_map +// std::unordered_map TEST_F(JsonConstructObjectImplicitTest, StdUnorderedMapStringJson) { - std::unordered_map o {{"a", json(1)}, {"b", json(1u)}, {"c", json(2.2)}, {"d", json(false)}, {"e", json("string")}, {"f", json()}}; + std::unordered_map o {{"a", json(1)}, {"b", json(1u)}, {"c", json(2.2)}, {"d", json(false)}, {"e", json("string")}, {"f", json()}}; json j(o); EXPECT_EQ(j.type(), json::value_t::object); EXPECT_EQ(j, j_reference); } -// std::unordered_multimap +// std::unordered_multimap TEST_F(JsonConstructObjectImplicitTest, StdUnorderedMultiMapStringJson) { - std::unordered_multimap o {{"a", json(1)}, {"b", json(1u)}, {"c", json(2.2)}, {"d", json(false)}, {"e", json("string")}, {"f", json()}}; + std::unordered_multimap o {{"a", json(1)}, {"b", json(1u)}, {"c", json(2.2)}, {"d", json(false)}, {"e", json("string")}, {"f", json()}}; json j(o); EXPECT_EQ(j.type(), json::value_t::object); EXPECT_EQ(j, j_reference); @@ -264,14 +264,14 @@ TEST(JsonConstructArrayContainerTest, Case) TEST(JsonConstructStringExplicitTest, Empty) { - json::string_t s; + std::string s; json j(s); EXPECT_EQ(j.type(), json::value_t::string); } TEST(JsonConstructStringExplicitTest, Filled) { - json::string_t s {"Hello world"}; + std::string s {"Hello world"}; json j(s); EXPECT_EQ(j.type(), json::value_t::string); } @@ -281,7 +281,7 @@ class JsonConstructStringTest : public ::testing::Test { JsonConstructStringTest() : j_reference(s_reference) {} protected: - json::string_t s_reference {"Hello world"}; + std::string s_reference {"Hello world"}; json j_reference; }; @@ -322,7 +322,7 @@ TEST_F(JsonConstructStringTest, StringLiteral) TEST(JsonConstructBooleanExplicitTest, Empty) { - json::boolean_t b{}; + bool b{}; json j(b); EXPECT_EQ(j.type(), json::value_t::boolean); } @@ -341,14 +341,14 @@ TEST(JsonConstructBooleanExplicitTest, False) TEST(JsonConstructIntegerExplicitTest, Uninitialized) { - json::number_integer_t n{}; + int64_t n{}; json j(n); EXPECT_EQ(j.type(), json::value_t::number_integer); } TEST(JsonConstructIntegerExplicitTest, Initialized) { - json::number_integer_t n(42); + int64_t n(42); json j(n); EXPECT_EQ(j.type(), json::value_t::number_integer); } @@ -360,9 +360,9 @@ class JsonConstructIntegerTest : public ::testing::Test { : j_reference(n_reference), j_unsigned_reference(n_unsigned_reference) {} protected: - json::number_integer_t n_reference = 42; + int64_t n_reference = 42; json j_reference; - json::number_unsigned_t n_unsigned_reference = 42u; + uint64_t n_unsigned_reference = 42u; json j_unsigned_reference; }; @@ -429,9 +429,9 @@ class JsonConstructIntegerLiteralTest : public ::testing::Test { : j_reference(n_reference), j_unsigned_reference(n_unsigned_reference) {} protected: - json::number_integer_t n_reference = 42; + int64_t n_reference = 42; json j_reference; - json::number_unsigned_t n_unsigned_reference = 42u; + uint64_t n_unsigned_reference = 42u; json j_unsigned_reference; }; @@ -479,14 +479,14 @@ TEST_F(JsonConstructIntegerLiteralTest, ULL) TEST(JsonConstructFloatExplicitTest, Uninitialized) { - json::number_float_t n{}; + double n{}; json j(n); EXPECT_EQ(j.type(), json::value_t::number_float); } TEST(JsonConstructFloatExplicitTest, Initialized) { - json::number_float_t n(42.23); + double n(42.23); json j(n); EXPECT_EQ(j.type(), json::value_t::number_float); } @@ -494,12 +494,12 @@ TEST(JsonConstructFloatExplicitTest, Initialized) TEST(JsonConstructFloatExplicitTest, Infinity) { // infinity is stored properly, but serialized to null - json::number_float_t n(std::numeric_limits::infinity()); + double n(std::numeric_limits::infinity()); json j(n); EXPECT_EQ(j.type(), json::value_t::number_float); // check round trip of infinity - json::number_float_t d = j; + double d = j; EXPECT_EQ(d, n); // check that inf is serialized to null @@ -512,7 +512,7 @@ class JsonConstructFloatTest : public ::testing::Test { JsonConstructFloatTest() : j_reference(n_reference) {} protected: - json::number_float_t n_reference {42.23}; + double n_reference {42.23}; json j_reference; }; @@ -540,7 +540,7 @@ class JsonConstructFloatLiteralTest : public ::testing::Test { JsonConstructFloatLiteralTest() : j_reference(n_reference) {} protected: - json::number_float_t n_reference {42.23}; + double n_reference {42.23}; json j_reference; }; @@ -575,8 +575,7 @@ TEST_F(JsonConstructFloatLiteralTest, L) TEST(JsonConstructInitializerEmptyTest, Explicit) { - std::initializer_list l; - json j(l); + json j(json::initializer_list_t{}); EXPECT_EQ(j.type(), json::value_t::object); } @@ -725,17 +724,15 @@ TEST(JsonConstructInitializerExplicitTest, ObjectError) TEST(JsonConstructInitializerPairErrorTest, WrongFieldNumber) { json j{{"too", "much"}, {"string", "fields"}}; - EXPECT_THROW_MSG((j.get>()), json::other_error, - "[json.exception.other_error.502] conversion " - "to std::pair requires the object to have " - "exactly one field, but it has 2"); + EXPECT_THROW_MSG((j.get>()), json::type_error, + "[json.exception.type_error.304] cannot use at() with object"); } TEST(JsonConstructInitializerPairErrorTest, WrongJsonType) { json j(42); EXPECT_THROW_MSG((j.get>()), json::type_error, - "[json.exception.type_error.302] type must be object, but is number"); + "[json.exception.type_error.304] cannot use at() with number"); } TEST(JsonConstructInitializerTest, EmptyArray) diff --git a/wpiutil/src/test/native/cpp/json/unit-convenience.cpp b/wpiutil/src/test/native/cpp/json/unit-convenience.cpp deleted file mode 100644 index 1f4a0dcc34..0000000000 --- a/wpiutil/src/test/native/cpp/json/unit-convenience.cpp +++ /dev/null @@ -1,120 +0,0 @@ -/*----------------------------------------------------------------------------*/ -/* 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++ (test suite) -| | |__ | | | | | | version 2.1.1 -|_____|_____|_____|_|___| https://github.com/nlohmann/json - -Licensed under the MIT License . -Copyright (c) 2013-2017 Niels Lohmann . - -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 "gtest/gtest.h" - -#include "wpi/SmallString.h" - -#include "unit-json.h" -#include "json_serializer.h" - -using wpi::json; - -class JsonTypeNameTest - : public ::testing::TestWithParam> {}; -TEST_P(JsonTypeNameTest, Case) -{ - EXPECT_EQ(json(GetParam().first).type_name(), GetParam().second); -} - -static const std::pair type_name_cases[] = { - {json::value_t::null, "null"}, - {json::value_t::object, "object"}, - {json::value_t::array, "array"}, - {json::value_t::number_integer, "number"}, - {json::value_t::number_unsigned, "number"}, - {json::value_t::number_float, "number"}, - {json::value_t::boolean, "boolean"}, - {json::value_t::string, "string"}, - {json::value_t::discarded, "discarded"}, -}; - -INSTANTIATE_TEST_CASE_P(JsonTypeNameTests, JsonTypeNameTest, - ::testing::ValuesIn(type_name_cases), ); - -class JsonStringEscapeTest - : public ::testing::TestWithParam> {}; -TEST_P(JsonStringEscapeTest, Case) -{ - wpi::SmallString<32> buf; - wpi::raw_svector_ostream ss(buf); - json::serializer s(ss); - s.dump_escaped(GetParam().first); - EXPECT_EQ(ss.str(), wpi::StringRef(GetParam().second)); -} - -static const std::pair string_escape_cases[] = { - {"\"", "\\\""}, - {"\\", "\\\\"}, - {"\b", "\\b"}, - {"\f", "\\f"}, - {"\n", "\\n"}, - {"\r", "\\r"}, - {"\t", "\\t"}, - - {"\x01", "\\u0001"}, - {"\x02", "\\u0002"}, - {"\x03", "\\u0003"}, - {"\x04", "\\u0004"}, - {"\x05", "\\u0005"}, - {"\x06", "\\u0006"}, - {"\x07", "\\u0007"}, - {"\x08", "\\b"}, - {"\x09", "\\t"}, - {"\x0a", "\\n"}, - {"\x0b", "\\u000b"}, - {"\x0c", "\\f"}, - {"\x0d", "\\r"}, - {"\x0e", "\\u000e"}, - {"\x0f", "\\u000f"}, - {"\x10", "\\u0010"}, - {"\x11", "\\u0011"}, - {"\x12", "\\u0012"}, - {"\x13", "\\u0013"}, - {"\x14", "\\u0014"}, - {"\x15", "\\u0015"}, - {"\x16", "\\u0016"}, - {"\x17", "\\u0017"}, - {"\x18", "\\u0018"}, - {"\x19", "\\u0019"}, - {"\x1a", "\\u001a"}, - {"\x1b", "\\u001b"}, - {"\x1c", "\\u001c"}, - {"\x1d", "\\u001d"}, - {"\x1e", "\\u001e"}, - {"\x1f", "\\u001f"}, -}; - -INSTANTIATE_TEST_CASE_P(JsonStringEscapeTests, JsonStringEscapeTest, - ::testing::ValuesIn(string_escape_cases), ); diff --git a/wpiutil/src/test/native/cpp/json/unit-conversions.cpp b/wpiutil/src/test/native/cpp/json/unit-conversions.cpp index 08c04f4e78..e4981f27f6 100644 --- a/wpiutil/src/test/native/cpp/json/unit-conversions.cpp +++ b/wpiutil/src/test/native/cpp/json/unit-conversions.cpp @@ -57,10 +57,10 @@ class JsonGetObjectTest : public ::testing::Test { typedef ::testing::Types< json::object_t - , std::map - , std::multimap - , std::unordered_map - , std::unordered_multimap + , std::map + , std::multimap + , std::unordered_map + , std::unordered_multimap > JsonGetObjectTestTypes; TYPED_TEST_CASE(JsonGetObjectTest, JsonGetObjectTestTypes); @@ -195,11 +195,11 @@ class JsonGetStringTest : public ::testing::Test { JsonGetStringTest() : j(s_reference) {} protected: - json::string_t s_reference {"Hello world"}; + std::string s_reference {"Hello world"}; json j; }; -typedef ::testing::Types JsonGetStringTestTypes; +typedef ::testing::Types JsonGetStringTestTypes; TYPED_TEST_CASE(JsonGetStringTest, JsonGetStringTestTypes); TYPED_TEST(JsonGetStringTest, Explicit) @@ -217,19 +217,19 @@ TYPED_TEST(JsonGetStringTest, Implicit) // exception in case of a non-string type TEST(JsonGetStringExceptionTest, TypeError) { - EXPECT_THROW_MSG(json(json::value_t::null).get(), json::type_error, + EXPECT_THROW_MSG(json(json::value_t::null).get(), json::type_error, "[json.exception.type_error.302] type must be string, but is null"); - EXPECT_THROW_MSG(json(json::value_t::object).get(), json::type_error, + EXPECT_THROW_MSG(json(json::value_t::object).get(), json::type_error, "[json.exception.type_error.302] type must be string, but is object"); - EXPECT_THROW_MSG(json(json::value_t::array).get(), json::type_error, + EXPECT_THROW_MSG(json(json::value_t::array).get(), json::type_error, "[json.exception.type_error.302] type must be string, but is array"); - EXPECT_THROW_MSG(json(json::value_t::boolean).get(), json::type_error, + EXPECT_THROW_MSG(json(json::value_t::boolean).get(), json::type_error, "[json.exception.type_error.302] type must be string, but is boolean"); - EXPECT_THROW_MSG(json(json::value_t::number_integer).get(), json::type_error, + EXPECT_THROW_MSG(json(json::value_t::number_integer).get(), json::type_error, "[json.exception.type_error.302] type must be string, but is number"); - EXPECT_THROW_MSG(json(json::value_t::number_unsigned).get(), json::type_error, + EXPECT_THROW_MSG(json(json::value_t::number_unsigned).get(), json::type_error, "[json.exception.type_error.302] type must be string, but is number"); - EXPECT_THROW_MSG(json(json::value_t::number_float).get(), json::type_error, + EXPECT_THROW_MSG(json(json::value_t::number_float).get(), json::type_error, "[json.exception.type_error.302] type must be string, but is number"); } @@ -239,11 +239,11 @@ class JsonGetBooleanTest : public ::testing::Test { JsonGetBooleanTest() : j(b_reference) {} protected: - json::boolean_t b_reference {true}; + bool b_reference {true}; json j; }; -typedef ::testing::Types JsonGetBooleanTestTypes; +typedef ::testing::Types JsonGetBooleanTestTypes; TYPED_TEST_CASE(JsonGetBooleanTest, JsonGetBooleanTestTypes); TYPED_TEST(JsonGetBooleanTest, Explicit) @@ -261,19 +261,19 @@ TYPED_TEST(JsonGetBooleanTest, Implicit) // exception in case of a non-string type TEST(JsonGetBooleanExceptionTest, TypeError) { - EXPECT_THROW_MSG(json(json::value_t::null).get(), json::type_error, + EXPECT_THROW_MSG(json(json::value_t::null).get(), json::type_error, "[json.exception.type_error.302] type must be boolean, but is null"); - EXPECT_THROW_MSG(json(json::value_t::object).get(), json::type_error, + EXPECT_THROW_MSG(json(json::value_t::object).get(), json::type_error, "[json.exception.type_error.302] type must be boolean, but is object"); - EXPECT_THROW_MSG(json(json::value_t::array).get(), json::type_error, + EXPECT_THROW_MSG(json(json::value_t::array).get(), json::type_error, "[json.exception.type_error.302] type must be boolean, but is array"); - EXPECT_THROW_MSG(json(json::value_t::string).get(), json::type_error, + EXPECT_THROW_MSG(json(json::value_t::string).get(), json::type_error, "[json.exception.type_error.302] type must be boolean, but is string"); - EXPECT_THROW_MSG(json(json::value_t::number_integer).get(), json::type_error, + EXPECT_THROW_MSG(json(json::value_t::number_integer).get(), json::type_error, "[json.exception.type_error.302] type must be boolean, but is number"); - EXPECT_THROW_MSG(json(json::value_t::number_unsigned).get(), json::type_error, + EXPECT_THROW_MSG(json(json::value_t::number_unsigned).get(), json::type_error, "[json.exception.type_error.302] type must be boolean, but is number"); - EXPECT_THROW_MSG(json(json::value_t::number_float).get(), json::type_error, + EXPECT_THROW_MSG(json(json::value_t::number_float).get(), json::type_error, "[json.exception.type_error.302] type must be boolean, but is number"); } @@ -283,9 +283,9 @@ class JsonGetIntegerTest : public ::testing::Test { JsonGetIntegerTest() : j(n_reference), j_unsigned(n_unsigned_reference) {} protected: - json::number_integer_t n_reference {42}; + int64_t n_reference {42}; json j; - json::number_unsigned_t n_unsigned_reference {42u}; + uint64_t n_unsigned_reference {42u}; json j_unsigned; }; @@ -353,19 +353,19 @@ TYPED_TEST(JsonGetIntegerTest, Implicit) // exception in case of a non-number type TEST(JsonGetIntegerExceptionTest, TypeError) { - EXPECT_THROW_MSG(json(json::value_t::null).get(), json::type_error, + EXPECT_THROW_MSG(json(json::value_t::null).get(), json::type_error, "[json.exception.type_error.302] type must be number, but is null"); - EXPECT_THROW_MSG(json(json::value_t::object).get(), json::type_error, + EXPECT_THROW_MSG(json(json::value_t::object).get(), json::type_error, "[json.exception.type_error.302] type must be number, but is object"); - EXPECT_THROW_MSG(json(json::value_t::array).get(), json::type_error, + EXPECT_THROW_MSG(json(json::value_t::array).get(), json::type_error, "[json.exception.type_error.302] type must be number, but is array"); - EXPECT_THROW_MSG(json(json::value_t::string).get(), json::type_error, + EXPECT_THROW_MSG(json(json::value_t::string).get(), json::type_error, "[json.exception.type_error.302] type must be number, but is string"); - EXPECT_THROW_MSG(json(json::value_t::boolean).get(), json::type_error, + EXPECT_THROW_MSG(json(json::value_t::boolean).get(), json::type_error, "[json.exception.type_error.302] type must be number, but is boolean"); - EXPECT_NO_THROW(json(json::value_t::number_float).get()); - EXPECT_NO_THROW(json(json::value_t::number_float).get()); + EXPECT_NO_THROW(json(json::value_t::number_float).get()); + EXPECT_NO_THROW(json(json::value_t::number_float).get()); } template @@ -374,11 +374,11 @@ class JsonGetFloatTest : public ::testing::Test { JsonGetFloatTest() : j(n_reference) {} protected: - json::number_float_t n_reference {42.23}; + double n_reference {42.23}; json j; }; -typedef ::testing::Types +typedef ::testing::Types JsonGetFloatTestTypes; TYPED_TEST_CASE(JsonGetFloatTest, JsonGetFloatTestTypes); @@ -400,19 +400,19 @@ TYPED_TEST(JsonGetFloatTest, Implicit) // exception in case of a non-string type TEST(JsonGetFloatExceptionTest, TypeError) { - EXPECT_THROW_MSG(json(json::value_t::null).get(), json::type_error, + EXPECT_THROW_MSG(json(json::value_t::null).get(), json::type_error, "[json.exception.type_error.302] type must be number, but is null"); - EXPECT_THROW_MSG(json(json::value_t::object).get(), json::type_error, + EXPECT_THROW_MSG(json(json::value_t::object).get(), json::type_error, "[json.exception.type_error.302] type must be number, but is object"); - EXPECT_THROW_MSG(json(json::value_t::array).get(), json::type_error, + EXPECT_THROW_MSG(json(json::value_t::array).get(), json::type_error, "[json.exception.type_error.302] type must be number, but is array"); - EXPECT_THROW_MSG(json(json::value_t::string).get(), json::type_error, + EXPECT_THROW_MSG(json(json::value_t::string).get(), json::type_error, "[json.exception.type_error.302] type must be number, but is string"); - EXPECT_THROW_MSG(json(json::value_t::boolean).get(), json::type_error, + EXPECT_THROW_MSG(json(json::value_t::boolean).get(), json::type_error, "[json.exception.type_error.302] type must be number, but is boolean"); - EXPECT_NO_THROW(json(json::value_t::number_integer).get()); - EXPECT_NO_THROW(json(json::value_t::number_unsigned).get()); + EXPECT_NO_THROW(json(json::value_t::number_integer).get()); + EXPECT_NO_THROW(json(json::value_t::number_unsigned).get()); } TEST(JsonGetEnumTest, Case) diff --git a/wpiutil/src/test/native/cpp/json/unit-iterator_wrapper.cpp b/wpiutil/src/test/native/cpp/json/unit-iterator_wrapper.cpp deleted file mode 100644 index 8d1f18ed1e..0000000000 --- a/wpiutil/src/test/native/cpp/json/unit-iterator_wrapper.cpp +++ /dev/null @@ -1,745 +0,0 @@ -/*----------------------------------------------------------------------------*/ -/* 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++ (test suite) -| | |__ | | | | | | version 2.1.1 -|_____|_____|_____|_|___| https://github.com/nlohmann/json - -Licensed under the MIT License . -Copyright (c) 2013-2017 Niels Lohmann . - -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 "gtest/gtest.h" - -#include "unit-json.h" -using wpi::json; - -class JsonIteratorWrapperObjectTest : public ::testing::Test { - protected: - json j = {{"A", 1}, {"B", 2}}; -}; - -TEST_F(JsonIteratorWrapperObjectTest, Value) -{ - int counter = 1; - - for (auto i : json::iterator_wrapper(j)) - { - SCOPED_TRACE(counter); - switch (counter++) - { - case 1: - { - EXPECT_EQ(i.key(), "A"); - EXPECT_EQ(i.value(), json(1)); - break; - } - - case 2: - { - EXPECT_EQ(i.key(), "B"); - EXPECT_EQ(i.value(), json(2)); - break; - } - - default: - { - break; - } - } - } - - EXPECT_EQ(counter, 3); -} - -TEST_F(JsonIteratorWrapperObjectTest, Reference) -{ - int counter = 1; - - for (auto& i : json::iterator_wrapper(j)) - { - SCOPED_TRACE(counter); - switch (counter++) - { - case 1: - { - EXPECT_EQ(i.key(), "A"); - EXPECT_EQ(i.value(), json(1)); - - // change the value - i.value() = json(11); - EXPECT_EQ(i.value(), json(11)); - break; - } - - case 2: - { - EXPECT_EQ(i.key(), "B"); - EXPECT_EQ(i.value(), json(2)); - - // change the value - i.value() = json(22); - EXPECT_EQ(i.value(), json(22)); - break; - } - - default: - { - break; - } - } - } - - EXPECT_EQ(counter, 3); - - // check if values where changed - EXPECT_EQ(j, json({{"A", 11}, {"B", 22}})); -} - -TEST_F(JsonIteratorWrapperObjectTest, ConstValue) -{ - int counter = 1; - - for (const auto i : json::iterator_wrapper(j)) - { - SCOPED_TRACE(counter); - switch (counter++) - { - case 1: - { - EXPECT_EQ(i.key(), "A"); - EXPECT_EQ(i.value(), json(1)); - break; - } - - case 2: - { - EXPECT_EQ(i.key(), "B"); - EXPECT_EQ(i.value(), json(2)); - break; - } - - default: - { - break; - } - } - } - - EXPECT_EQ(counter, 3); -} - -TEST_F(JsonIteratorWrapperObjectTest, ConstReference) -{ - json j = {{"A", 1}, {"B", 2}}; - int counter = 1; - - for (const auto& i : json::iterator_wrapper(j)) - { - SCOPED_TRACE(counter); - switch (counter++) - { - case 1: - { - EXPECT_EQ(i.key(), "A"); - EXPECT_EQ(i.value(), json(1)); - break; - } - - case 2: - { - EXPECT_EQ(i.key(), "B"); - EXPECT_EQ(i.value(), json(2)); - break; - } - - default: - { - break; - } - } - } - - EXPECT_EQ(counter, 3); -} - -class JsonIteratorWrapperConstObjectTest : public ::testing::Test { - protected: - const json j = {{"A", 1}, {"B", 2}}; -}; - -TEST_F(JsonIteratorWrapperConstObjectTest, Value) -{ - int counter = 1; - - for (auto i : json::iterator_wrapper(j)) - { - SCOPED_TRACE(counter); - switch (counter++) - { - case 1: - { - EXPECT_EQ(i.key(), "A"); - EXPECT_EQ(i.value(), json(1)); - break; - } - - case 2: - { - EXPECT_EQ(i.key(), "B"); - EXPECT_EQ(i.value(), json(2)); - break; - } - - default: - { - break; - } - } - } - - EXPECT_EQ(counter, 3); -} - -TEST_F(JsonIteratorWrapperConstObjectTest, Reference) -{ - int counter = 1; - - for (auto& i : json::iterator_wrapper(j)) - { - SCOPED_TRACE(counter); - switch (counter++) - { - case 1: - { - EXPECT_EQ(i.key(), "A"); - EXPECT_EQ(i.value(), json(1)); - break; - } - - case 2: - { - EXPECT_EQ(i.key(), "B"); - EXPECT_EQ(i.value(), json(2)); - break; - } - - default: - { - break; - } - } - } - - EXPECT_EQ(counter, 3); -} - -TEST_F(JsonIteratorWrapperConstObjectTest, ConstValue) -{ - int counter = 1; - - for (const auto i : json::iterator_wrapper(j)) - { - SCOPED_TRACE(counter); - switch (counter++) - { - case 1: - { - EXPECT_EQ(i.key(), "A"); - EXPECT_EQ(i.value(), json(1)); - break; - } - - case 2: - { - EXPECT_EQ(i.key(), "B"); - EXPECT_EQ(i.value(), json(2)); - break; - } - - default: - { - break; - } - } - } - - EXPECT_EQ(counter, 3); -} - -TEST_F(JsonIteratorWrapperConstObjectTest, ConstReference) -{ - int counter = 1; - - for (const auto& i : json::iterator_wrapper(j)) - { - SCOPED_TRACE(counter); - switch (counter++) - { - case 1: - { - EXPECT_EQ(i.key(), "A"); - EXPECT_EQ(i.value(), json(1)); - break; - } - - case 2: - { - EXPECT_EQ(i.key(), "B"); - EXPECT_EQ(i.value(), json(2)); - break; - } - - default: - { - break; - } - } - } - - EXPECT_EQ(counter, 3); -} - -class JsonIteratorWrapperArrayTest : public ::testing::Test { - protected: - json j = {"A", "B"}; -}; - -TEST_F(JsonIteratorWrapperArrayTest, Value) -{ - int counter = 1; - - for (auto i : json::iterator_wrapper(j)) - { - SCOPED_TRACE(counter); - switch (counter++) - { - case 1: - { - EXPECT_EQ(i.key(), "0"); - EXPECT_EQ(i.value(), "A"); - break; - } - - case 2: - { - EXPECT_EQ(i.key(), "1"); - EXPECT_EQ(i.value(), "B"); - break; - } - - default: - { - break; - } - } - } - - EXPECT_EQ(counter, 3); -} - -TEST_F(JsonIteratorWrapperArrayTest, Reference) -{ - int counter = 1; - - for (auto& i : json::iterator_wrapper(j)) - { - SCOPED_TRACE(counter); - switch (counter++) - { - case 1: - { - EXPECT_EQ(i.key(), "0"); - EXPECT_EQ(i.value(), "A"); - - // change the value - i.value() = "AA"; - EXPECT_EQ(i.value(), "AA"); - break; - } - - case 2: - { - EXPECT_EQ(i.key(), "1"); - EXPECT_EQ(i.value(), "B"); - - // change the value - i.value() = "BB"; - EXPECT_EQ(i.value(), "BB"); - break; - } - - default: - { - break; - } - } - } - - EXPECT_EQ(counter, 3); - - // check if values where changed - EXPECT_EQ(j, json({"AA", "BB"})); -} - -TEST_F(JsonIteratorWrapperArrayTest, ConstValue) -{ - int counter = 1; - - for (const auto i : json::iterator_wrapper(j)) - { - SCOPED_TRACE(counter); - switch (counter++) - { - case 1: - { - EXPECT_EQ(i.key(), "0"); - EXPECT_EQ(i.value(), "A"); - break; - } - - case 2: - { - EXPECT_EQ(i.key(), "1"); - EXPECT_EQ(i.value(), "B"); - break; - } - - default: - { - break; - } - } - } - - EXPECT_EQ(counter, 3); -} - -TEST_F(JsonIteratorWrapperArrayTest, ConstReference) -{ - int counter = 1; - - for (const auto& i : json::iterator_wrapper(j)) - { - SCOPED_TRACE(counter); - switch (counter++) - { - case 1: - { - EXPECT_EQ(i.key(), "0"); - EXPECT_EQ(i.value(), "A"); - break; - } - - case 2: - { - EXPECT_EQ(i.key(), "1"); - EXPECT_EQ(i.value(), "B"); - break; - } - - default: - { - break; - } - } - } - - EXPECT_EQ(counter, 3); -} - -class JsonIteratorWrapperConstArrayTest : public ::testing::Test { - protected: - const json j = {"A", "B"}; -}; - -TEST_F(JsonIteratorWrapperConstArrayTest, Value) -{ - int counter = 1; - - for (auto i : json::iterator_wrapper(j)) - { - SCOPED_TRACE(counter); - switch (counter++) - { - case 1: - { - EXPECT_EQ(i.key(), "0"); - EXPECT_EQ(i.value(), "A"); - break; - } - - case 2: - { - EXPECT_EQ(i.key(), "1"); - EXPECT_EQ(i.value(), "B"); - break; - } - - default: - { - break; - } - } - } - - EXPECT_EQ(counter, 3); -} - -TEST_F(JsonIteratorWrapperConstArrayTest, Reference) -{ - int counter = 1; - - for (auto& i : json::iterator_wrapper(j)) - { - SCOPED_TRACE(counter); - switch (counter++) - { - case 1: - { - EXPECT_EQ(i.key(), "0"); - EXPECT_EQ(i.value(), "A"); - break; - } - - case 2: - { - EXPECT_EQ(i.key(), "1"); - EXPECT_EQ(i.value(), "B"); - break; - } - - default: - { - break; - } - } - } - - EXPECT_EQ(counter, 3); -} - -TEST_F(JsonIteratorWrapperConstArrayTest, ConstValue) -{ - int counter = 1; - - for (const auto i : json::iterator_wrapper(j)) - { - SCOPED_TRACE(counter); - switch (counter++) - { - case 1: - { - EXPECT_EQ(i.key(), "0"); - EXPECT_EQ(i.value(), "A"); - break; - } - - case 2: - { - EXPECT_EQ(i.key(), "1"); - EXPECT_EQ(i.value(), "B"); - break; - } - - default: - { - break; - } - } - } - - EXPECT_EQ(counter, 3); -} - -TEST_F(JsonIteratorWrapperConstArrayTest, ConstReference) -{ - int counter = 1; - - for (const auto& i : json::iterator_wrapper(j)) - { - SCOPED_TRACE(counter); - switch (counter++) - { - case 1: - { - EXPECT_EQ(i.key(), "0"); - EXPECT_EQ(i.value(), "A"); - break; - } - - case 2: - { - EXPECT_EQ(i.key(), "1"); - EXPECT_EQ(i.value(), "B"); - break; - } - - default: - { - break; - } - } - } - - EXPECT_EQ(counter, 3); -} - -class JsonIteratorWrapperPrimitiveTest : public ::testing::Test { - protected: - json j = 1; -}; - -TEST_F(JsonIteratorWrapperPrimitiveTest, Value) -{ - int counter = 1; - - for (auto i : json::iterator_wrapper(j)) - { - SCOPED_TRACE(counter); - ++counter; - EXPECT_EQ(i.key(), ""); - EXPECT_EQ(i.value(), json(1)); - } - - EXPECT_EQ(counter, 2); -} - -TEST_F(JsonIteratorWrapperPrimitiveTest, Reference) -{ - int counter = 1; - - for (auto& i : json::iterator_wrapper(j)) - { - SCOPED_TRACE(counter); - ++counter; - EXPECT_EQ(i.key(), ""); - EXPECT_EQ(i.value(), json(1)); - - // change value - i.value() = json(2); - } - - EXPECT_EQ(counter, 2); - - // check if value has changed - EXPECT_EQ(j, json(2)); -} - -TEST_F(JsonIteratorWrapperPrimitiveTest, ConstValue) -{ - int counter = 1; - - for (const auto i : json::iterator_wrapper(j)) - { - SCOPED_TRACE(counter); - ++counter; - EXPECT_EQ(i.key(), ""); - EXPECT_EQ(i.value(), json(1)); - } - - EXPECT_EQ(counter, 2); -} - -TEST_F(JsonIteratorWrapperPrimitiveTest, ConstReference) -{ - int counter = 1; - - for (const auto& i : json::iterator_wrapper(j)) - { - SCOPED_TRACE(counter); - ++counter; - EXPECT_EQ(i.key(), ""); - EXPECT_EQ(i.value(), json(1)); - } - - EXPECT_EQ(counter, 2); -} - -class JsonIteratorWrapperConstPrimitiveTest : public ::testing::Test { - protected: - const json j = 1; -}; - -TEST_F(JsonIteratorWrapperConstPrimitiveTest, Value) -{ - int counter = 1; - - for (auto i : json::iterator_wrapper(j)) - { - SCOPED_TRACE(counter); - ++counter; - EXPECT_EQ(i.key(), ""); - EXPECT_EQ(i.value(), json(1)); - } - - EXPECT_EQ(counter, 2); -} - -TEST_F(JsonIteratorWrapperConstPrimitiveTest, Reference) -{ - int counter = 1; - - for (auto& i : json::iterator_wrapper(j)) - { - SCOPED_TRACE(counter); - ++counter; - EXPECT_EQ(i.key(), ""); - EXPECT_EQ(i.value(), json(1)); - } - - EXPECT_EQ(counter, 2); -} - -TEST_F(JsonIteratorWrapperConstPrimitiveTest, ConstValue) -{ - int counter = 1; - - for (const auto i : json::iterator_wrapper(j)) - { - SCOPED_TRACE(counter); - ++counter; - EXPECT_EQ(i.key(), ""); - EXPECT_EQ(i.value(), json(1)); - } - - EXPECT_EQ(counter, 2); -} - -TEST_F(JsonIteratorWrapperConstPrimitiveTest, ConstReference) -{ - int counter = 1; - - for (const auto& i : json::iterator_wrapper(j)) - { - SCOPED_TRACE(counter); - ++counter; - EXPECT_EQ(i.key(), ""); - EXPECT_EQ(i.value(), json(1)); - } - - EXPECT_EQ(counter, 2); -} diff --git a/wpiutil/src/test/native/cpp/json/unit-meta.cpp b/wpiutil/src/test/native/cpp/json/unit-meta.cpp index ecf77baa80..45daf8f8ce 100644 --- a/wpiutil/src/test/native/cpp/json/unit-meta.cpp +++ b/wpiutil/src/test/native/cpp/json/unit-meta.cpp @@ -42,13 +42,13 @@ TEST(JsonVersionTest, Meta) json j = json::meta(); EXPECT_EQ(j["name"], "WPI version of JSON for Modern C++"); - EXPECT_EQ(j["copyright"], "(C) 2013-2017 Niels Lohmann, (C) 2017 FIRST"); - EXPECT_EQ(j["url"], "https://github.com/wpilibsuite/wpiutil"); + EXPECT_EQ(j["copyright"], "(C) 2013-2017 Niels Lohmann, (C) 2017-2018 FIRST"); + EXPECT_EQ(j["url"], "https://github.com/wpilibsuite/allwpilib"); EXPECT_EQ(j["version"], json( { - {"string", "2.1.1"}, - {"major", 2}, + {"string", "3.1.2"}, + {"major", 3}, {"minor", 1}, - {"patch", 1} + {"patch", 2} })); } diff --git a/wpiutil/src/test/native/cpp/json/unit-modifiers.cpp b/wpiutil/src/test/native/cpp/json/unit-modifiers.cpp index 4abc628894..4125858d4e 100644 --- a/wpiutil/src/test/native/cpp/json/unit-modifiers.cpp +++ b/wpiutil/src/test/native/cpp/json/unit-modifiers.cpp @@ -718,7 +718,7 @@ TEST(JsonSwapTest, NonObjectT) TEST(JsonSwapTest, StringT) { json j = "Hello world"; - json::string_t s = "Hallo Welt"; + std::string s = "Hallo Welt"; j.swap(s); @@ -732,7 +732,7 @@ TEST(JsonSwapTest, StringT) TEST(JsonSwapTest, NonStringT) { json j = 17; - json::string_t s = "Hallo Welt"; + std::string s = "Hallo Welt"; EXPECT_THROW_MSG(j.swap(s), json::type_error, "[json.exception.type_error.310] cannot use swap() with number"); diff --git a/wpiutil/src/test/native/cpp/json/unit-msgpack.cpp b/wpiutil/src/test/native/cpp/json/unit-msgpack.cpp index e4c63a7f3b..0c03ee06f3 100644 --- a/wpiutil/src/test/native/cpp/json/unit-msgpack.cpp +++ b/wpiutil/src/test/native/cpp/json/unit-msgpack.cpp @@ -50,7 +50,7 @@ TEST(MessagePackDiscardedTest, Case) TEST(MessagePackNullTest, Case) { json j = nullptr; - std::string expected = "\xc0"; + std::vector expected = {0xc0}; const auto result = json::to_msgpack(j); EXPECT_EQ(result, expected); @@ -61,7 +61,7 @@ TEST(MessagePackNullTest, Case) TEST(MessagePackBooleanTest, True) { json j = true; - std::string expected = "\xc3"; + std::vector expected = {0xc3}; const auto result = json::to_msgpack(j); EXPECT_EQ(result, expected); @@ -72,7 +72,7 @@ TEST(MessagePackBooleanTest, True) TEST(MessagePackBooleanTest, False) { json j = false; - std::string expected = "\xc2"; + std::vector expected = {0xc2}; const auto result = json::to_msgpack(j); EXPECT_EQ(result, expected); @@ -94,8 +94,8 @@ TEST(MessagePackSignedTest, Neg0) EXPECT_TRUE(j.is_number_integer()); // create expected byte vector - std::string expected; - expected.push_back(static_cast(i)); + std::vector expected; + expected.push_back(static_cast(i)); // compare result + size const auto result = json::to_msgpack(j); @@ -119,14 +119,14 @@ TEST(MessagePackSignedTest, Pos0) // create JSON value with integer number json j = -1; - j.get_ref() = static_cast(i); + j.get_ref() = static_cast(i); // check type EXPECT_TRUE(j.is_number_integer()); // create expected byte vector - std::string expected; - expected.push_back(static_cast(i)); + std::vector expected; + expected.push_back(static_cast(i)); // compare result + size const auto result = json::to_msgpack(j); @@ -134,7 +134,7 @@ TEST(MessagePackSignedTest, Pos0) EXPECT_EQ(result.size(), 1u); // check individual bytes - EXPECT_EQ(result[0], static_cast(i)); + EXPECT_EQ(result[0], static_cast(i)); // roundtrip EXPECT_EQ(json::from_msgpack(result), j); @@ -150,15 +150,15 @@ TEST(MessagePackSignedTest, Pos1) // create JSON value with integer number json j = -1; - j.get_ref() = static_cast(i); + j.get_ref() = static_cast(i); // check type EXPECT_TRUE(j.is_number_integer()); // create expected byte vector - std::string expected; - expected.push_back(static_cast(0xcc)); - expected.push_back(static_cast(i)); + std::vector expected; + expected.push_back(static_cast(0xcc)); + expected.push_back(static_cast(i)); // compare result + size const auto result = json::to_msgpack(j); @@ -166,7 +166,7 @@ TEST(MessagePackSignedTest, Pos1) EXPECT_EQ(result.size(), 2u); // check individual bytes - EXPECT_EQ(result[0], static_cast(0xcc)); + EXPECT_EQ(result[0], static_cast(0xcc)); uint8_t restored = static_cast(result[1]); EXPECT_EQ(restored, i); @@ -184,16 +184,16 @@ TEST(MessagePackSignedTest, Pos2) // create JSON value with integer number json j = -1; - j.get_ref() = static_cast(i); + j.get_ref() = static_cast(i); // check type EXPECT_TRUE(j.is_number_integer()); // create expected byte vector - std::string expected; - expected.push_back(static_cast(0xcd)); - expected.push_back(static_cast((i >> 8) & 0xff)); - expected.push_back(static_cast(i & 0xff)); + std::vector expected; + expected.push_back(static_cast(0xcd)); + expected.push_back(static_cast((i >> 8) & 0xff)); + expected.push_back(static_cast(i & 0xff)); // compare result + size const auto result = json::to_msgpack(j); @@ -201,7 +201,7 @@ TEST(MessagePackSignedTest, Pos2) EXPECT_EQ(result.size(), 3u); // check individual bytes - EXPECT_EQ(result[0], static_cast(0xcd)); + EXPECT_EQ(result[0], static_cast(0xcd)); uint16_t restored = static_cast(static_cast(result[1]) * 256 + static_cast(result[2])); EXPECT_EQ(restored, i); @@ -216,18 +216,18 @@ TEST_P(MessagePackSignedPos4Test, Case) { // create JSON value with integer number json j = -1; - j.get_ref() = static_cast(GetParam()); + j.get_ref() = static_cast(GetParam()); // check type EXPECT_TRUE(j.is_number_integer()); // create expected byte vector - std::string expected; - expected.push_back(static_cast(0xce)); - expected.push_back(static_cast((GetParam() >> 24) & 0xff)); - expected.push_back(static_cast((GetParam() >> 16) & 0xff)); - expected.push_back(static_cast((GetParam() >> 8) & 0xff)); - expected.push_back(static_cast(GetParam() & 0xff)); + std::vector expected; + expected.push_back(static_cast(0xce)); + expected.push_back(static_cast((GetParam() >> 24) & 0xff)); + expected.push_back(static_cast((GetParam() >> 16) & 0xff)); + expected.push_back(static_cast((GetParam() >> 8) & 0xff)); + expected.push_back(static_cast(GetParam() & 0xff)); // compare result + size const auto result = json::to_msgpack(j); @@ -235,7 +235,7 @@ TEST_P(MessagePackSignedPos4Test, Case) EXPECT_EQ(result.size(), 5u); // check individual bytes - EXPECT_EQ(result[0], static_cast(0xce)); + EXPECT_EQ(result[0], static_cast(0xce)); uint32_t restored = (static_cast(static_cast(result[1])) << 030) + (static_cast(static_cast(result[2])) << 020) + (static_cast(static_cast(result[3])) << 010) + @@ -262,22 +262,22 @@ TEST_P(MessagePackSignedPos8Test, Case) { // create JSON value with integer number json j = -1; - j.get_ref() = - static_cast(GetParam()); + j.get_ref() = + static_cast(GetParam()); // check type EXPECT_TRUE(j.is_number_integer()); // create expected byte vector - std::string expected; - expected.push_back(static_cast(0xcf)); - expected.push_back(static_cast((GetParam() >> 070) & 0xff)); - expected.push_back(static_cast((GetParam() >> 060) & 0xff)); - expected.push_back(static_cast((GetParam() >> 050) & 0xff)); - expected.push_back(static_cast((GetParam() >> 040) & 0xff)); - expected.push_back(static_cast((GetParam() >> 030) & 0xff)); - expected.push_back(static_cast((GetParam() >> 020) & 0xff)); - expected.push_back(static_cast((GetParam() >> 010) & 0xff)); + std::vector expected; + expected.push_back(static_cast(0xcf)); + expected.push_back(static_cast((GetParam() >> 070) & 0xff)); + expected.push_back(static_cast((GetParam() >> 060) & 0xff)); + expected.push_back(static_cast((GetParam() >> 050) & 0xff)); + expected.push_back(static_cast((GetParam() >> 040) & 0xff)); + expected.push_back(static_cast((GetParam() >> 030) & 0xff)); + expected.push_back(static_cast((GetParam() >> 020) & 0xff)); + expected.push_back(static_cast((GetParam() >> 010) & 0xff)); expected.push_back(static_cast(GetParam() & 0xff)); // compare result + size @@ -286,7 +286,7 @@ TEST_P(MessagePackSignedPos8Test, Case) EXPECT_EQ(result.size(), 9u); // check individual bytes - EXPECT_EQ(result[0], static_cast(0xcf)); + EXPECT_EQ(result[0], static_cast(0xcf)); uint64_t restored = (static_cast(static_cast(result[1])) << 070) + (static_cast(static_cast(result[2])) << 060) + (static_cast(static_cast(result[3])) << 050) + @@ -323,9 +323,9 @@ TEST(MessagePackSignedTest, Neg1) EXPECT_TRUE(j.is_number_integer()); // create expected byte vector - std::string expected; - expected.push_back(static_cast(0xd0)); - expected.push_back(static_cast(i)); + std::vector expected; + expected.push_back(static_cast(0xd0)); + expected.push_back(static_cast(i)); // compare result + size const auto result = json::to_msgpack(j); @@ -333,7 +333,7 @@ TEST(MessagePackSignedTest, Neg1) EXPECT_EQ(result.size(), 2u); // check individual bytes - EXPECT_EQ(result[0], static_cast(0xd0)); + EXPECT_EQ(result[0], static_cast(0xd0)); EXPECT_EQ(static_cast(result[1]), i); // roundtrip @@ -355,10 +355,10 @@ TEST(MessagePackSignedTest, Neg2) EXPECT_TRUE(j.is_number_integer()); // create expected byte vector - std::string expected; - expected.push_back(static_cast(0xd1)); - expected.push_back(static_cast((i >> 8) & 0xff)); - expected.push_back(static_cast(i & 0xff)); + std::vector expected; + expected.push_back(static_cast(0xd1)); + expected.push_back(static_cast((i >> 8) & 0xff)); + expected.push_back(static_cast(i & 0xff)); // compare result + size const auto result = json::to_msgpack(j); @@ -366,7 +366,7 @@ TEST(MessagePackSignedTest, Neg2) EXPECT_EQ(result.size(), 3u); // check individual bytes - EXPECT_EQ(result[0], static_cast(0xd1)); + EXPECT_EQ(result[0], static_cast(0xd1)); int16_t restored = static_cast((static_cast(result[1]) << 8) + static_cast(result[2])); EXPECT_EQ(restored, i); @@ -387,12 +387,12 @@ TEST_P(MessagePackSignedNeg4Test, Case) EXPECT_TRUE(j.is_number_integer()); // create expected byte vector - std::string expected; - expected.push_back(static_cast(0xd2)); - expected.push_back(static_cast((GetParam() >> 24) & 0xff)); - expected.push_back(static_cast((GetParam() >> 16) & 0xff)); - expected.push_back(static_cast((GetParam() >> 8) & 0xff)); - expected.push_back(static_cast(GetParam() & 0xff)); + std::vector expected; + expected.push_back(static_cast(0xd2)); + expected.push_back(static_cast((GetParam() >> 24) & 0xff)); + expected.push_back(static_cast((GetParam() >> 16) & 0xff)); + expected.push_back(static_cast((GetParam() >> 8) & 0xff)); + expected.push_back(static_cast(GetParam() & 0xff)); // compare result + size const auto result = json::to_msgpack(j); @@ -400,7 +400,7 @@ TEST_P(MessagePackSignedNeg4Test, Case) EXPECT_EQ(result.size(), 5u); // check individual bytes - EXPECT_EQ(result[0], static_cast(0xd2)); + EXPECT_EQ(result[0], static_cast(0xd2)); uint32_t restored = (static_cast(static_cast(result[1])) << 030) + (static_cast(static_cast(result[2])) << 020) + (static_cast(static_cast(result[3])) << 010) + @@ -433,16 +433,16 @@ TEST_P(MessagePackSignedNeg8Test, Case) EXPECT_TRUE(j.is_number_integer()); // create expected byte vector - std::string expected; - expected.push_back(static_cast(0xd3)); - expected.push_back(static_cast((GetParam() >> 070) & 0xff)); - expected.push_back(static_cast((GetParam() >> 060) & 0xff)); - expected.push_back(static_cast((GetParam() >> 050) & 0xff)); - expected.push_back(static_cast((GetParam() >> 040) & 0xff)); - expected.push_back(static_cast((GetParam() >> 030) & 0xff)); - expected.push_back(static_cast((GetParam() >> 020) & 0xff)); - expected.push_back(static_cast((GetParam() >> 010) & 0xff)); - expected.push_back(static_cast(GetParam() & 0xff)); + std::vector expected; + expected.push_back(static_cast(0xd3)); + expected.push_back(static_cast((GetParam() >> 070) & 0xff)); + expected.push_back(static_cast((GetParam() >> 060) & 0xff)); + expected.push_back(static_cast((GetParam() >> 050) & 0xff)); + expected.push_back(static_cast((GetParam() >> 040) & 0xff)); + expected.push_back(static_cast((GetParam() >> 030) & 0xff)); + expected.push_back(static_cast((GetParam() >> 020) & 0xff)); + expected.push_back(static_cast((GetParam() >> 010) & 0xff)); + expected.push_back(static_cast(GetParam() & 0xff)); // compare result + size const auto result = json::to_msgpack(j); @@ -450,7 +450,7 @@ TEST_P(MessagePackSignedNeg8Test, Case) EXPECT_EQ(result.size(), 9u); // check individual bytes - EXPECT_EQ(result[0], static_cast(0xd3)); + EXPECT_EQ(result[0], static_cast(0xd3)); int64_t restored = (static_cast(static_cast(result[1])) << 070) + (static_cast(static_cast(result[2])) << 060) + (static_cast(static_cast(result[3])) << 050) + @@ -487,8 +487,8 @@ TEST(MessagePackUnsignedTest, Pos0) EXPECT_TRUE(j.is_number_unsigned()); // create expected byte vector - std::string expected; - expected.push_back(static_cast(i)); + std::vector expected; + expected.push_back(static_cast(i)); // compare result + size const auto result = json::to_msgpack(j); @@ -496,7 +496,7 @@ TEST(MessagePackUnsignedTest, Pos0) EXPECT_EQ(result.size(), 1u); // check individual bytes - EXPECT_EQ(result[0], static_cast(i)); + EXPECT_EQ(result[0], static_cast(i)); // roundtrip EXPECT_EQ(json::from_msgpack(result), j); @@ -517,9 +517,9 @@ TEST(MessagePackUnsignedTest, Pos1) EXPECT_TRUE(j.is_number_unsigned()); // create expected byte vector - std::string expected; - expected.push_back(static_cast(0xcc)); - expected.push_back(static_cast(i)); + std::vector expected; + expected.push_back(static_cast(0xcc)); + expected.push_back(static_cast(i)); // compare result + size const auto result = json::to_msgpack(j); @@ -527,7 +527,7 @@ TEST(MessagePackUnsignedTest, Pos1) EXPECT_EQ(result.size(), 2u); // check individual bytes - EXPECT_EQ(result[0], static_cast(0xcc)); + EXPECT_EQ(result[0], static_cast(0xcc)); uint8_t restored = static_cast(result[1]); EXPECT_EQ(restored, i); @@ -550,10 +550,10 @@ TEST(MessagePackUnsignedTest, Pos2) EXPECT_TRUE(j.is_number_unsigned()); // create expected byte vector - std::string expected; - expected.push_back(static_cast(0xcd)); - expected.push_back(static_cast((i >> 8) & 0xff)); - expected.push_back(static_cast(i & 0xff)); + std::vector expected; + expected.push_back(static_cast(0xcd)); + expected.push_back(static_cast((i >> 8) & 0xff)); + expected.push_back(static_cast(i & 0xff)); // compare result + size const auto result = json::to_msgpack(j); @@ -561,7 +561,7 @@ TEST(MessagePackUnsignedTest, Pos2) EXPECT_EQ(result.size(), 3u); // check individual bytes - EXPECT_EQ(result[0], static_cast(0xcd)); + EXPECT_EQ(result[0], static_cast(0xcd)); uint16_t restored = static_cast(static_cast(result[1]) * 256 + static_cast(result[2])); EXPECT_EQ(restored, i); @@ -581,12 +581,12 @@ TEST_P(MessagePackUnsignedPos4Test, Case) EXPECT_TRUE(j.is_number_unsigned()); // create expected byte vector - std::string expected; - expected.push_back(static_cast(0xce)); - expected.push_back(static_cast((GetParam() >> 24) & 0xff)); - expected.push_back(static_cast((GetParam() >> 16) & 0xff)); - expected.push_back(static_cast((GetParam() >> 8) & 0xff)); - expected.push_back(static_cast(GetParam() & 0xff)); + std::vector expected; + expected.push_back(static_cast(0xce)); + expected.push_back(static_cast((GetParam() >> 24) & 0xff)); + expected.push_back(static_cast((GetParam() >> 16) & 0xff)); + expected.push_back(static_cast((GetParam() >> 8) & 0xff)); + expected.push_back(static_cast(GetParam() & 0xff)); // compare result + size const auto result = json::to_msgpack(j); @@ -594,7 +594,7 @@ TEST_P(MessagePackUnsignedPos4Test, Case) EXPECT_EQ(result.size(), 5u); // check individual bytes - EXPECT_EQ(result[0], static_cast(0xce)); + EXPECT_EQ(result[0], static_cast(0xce)); uint32_t restored = (static_cast(static_cast(result[1])) << 030) + (static_cast(static_cast(result[2])) << 020) + (static_cast(static_cast(result[3])) << 010) + @@ -620,16 +620,16 @@ TEST_P(MessagePackUnsignedPos8Test, Case) EXPECT_TRUE(j.is_number_unsigned()); // create expected byte vector - std::string expected; - expected.push_back(static_cast(0xcf)); - expected.push_back(static_cast((GetParam() >> 070) & 0xff)); - expected.push_back(static_cast((GetParam() >> 060) & 0xff)); - expected.push_back(static_cast((GetParam() >> 050) & 0xff)); - expected.push_back(static_cast((GetParam() >> 040) & 0xff)); - expected.push_back(static_cast((GetParam() >> 030) & 0xff)); - expected.push_back(static_cast((GetParam() >> 020) & 0xff)); - expected.push_back(static_cast((GetParam() >> 010) & 0xff)); - expected.push_back(static_cast(GetParam() & 0xff)); + std::vector expected; + expected.push_back(static_cast(0xcf)); + expected.push_back(static_cast((GetParam() >> 070) & 0xff)); + expected.push_back(static_cast((GetParam() >> 060) & 0xff)); + expected.push_back(static_cast((GetParam() >> 050) & 0xff)); + expected.push_back(static_cast((GetParam() >> 040) & 0xff)); + expected.push_back(static_cast((GetParam() >> 030) & 0xff)); + expected.push_back(static_cast((GetParam() >> 020) & 0xff)); + expected.push_back(static_cast((GetParam() >> 010) & 0xff)); + expected.push_back(static_cast(GetParam() & 0xff)); // compare result + size const auto result = json::to_msgpack(j); @@ -637,7 +637,7 @@ TEST_P(MessagePackUnsignedPos8Test, Case) EXPECT_EQ(result.size(), 9u); // check individual bytes - EXPECT_EQ(result[0], static_cast(0xcf)); + EXPECT_EQ(result[0], static_cast(0xcf)); uint64_t restored = (static_cast(static_cast(result[1])) << 070) + (static_cast(static_cast(result[2])) << 060) + (static_cast(static_cast(result[3])) << 050) + @@ -661,7 +661,7 @@ TEST(MessagePackFloatTest, Number) { double v = 3.1415925; json j = v; - std::string expected = "\xcb\x40\x09\x21\xfb\x3f\xa6\xde\xfc"; + std::vector expected = {0xcb,0x40,0x09,0x21,0xfb,0x3f,0xa6,0xde,0xfc}; const auto result = json::to_msgpack(j); EXPECT_EQ(result, expected); @@ -691,12 +691,15 @@ TEST(MessagePackStringTest, String1) json j = s; // create expected byte vector - std::string expected; - expected.push_back(static_cast(first_bytes[N])); - expected.append(s); + std::vector expected; + expected.push_back(static_cast(first_bytes[N])); + for (size_t i = 0; i < N; ++i) + { + expected.push_back('x'); + } // check first byte - EXPECT_EQ((first_bytes[N] & 0x1f), static_cast(N)); + EXPECT_EQ((first_bytes[N] & 0x1f), static_cast(N)); // compare result + size const auto result = json::to_msgpack(j); @@ -725,10 +728,13 @@ TEST(MessagePackStringTest, String2) json j = s; // create expected byte vector - std::string expected; - expected.push_back(static_cast(0xd9)); - expected.push_back(static_cast(N)); - expected.append(s); + std::vector expected; + expected.push_back(static_cast(0xd9)); + expected.push_back(static_cast(N)); + for (size_t i = 0; i < N; ++i) + { + expected.push_back('x'); + } // compare result + size const auto result = json::to_msgpack(j); @@ -751,11 +757,14 @@ TEST_P(MessagePackString3Test, Case) json j = s; // create expected byte vector (hack: create string first) - std::string expected; - expected.push_back(static_cast(0xda)); - expected.push_back(static_cast((GetParam() >> 8) & 0xff)); - expected.push_back(static_cast(GetParam() & 0xff)); - expected.append(s); + std::vector expected; + expected.push_back(static_cast(0xda)); + expected.push_back(static_cast((GetParam() >> 8) & 0xff)); + expected.push_back(static_cast(GetParam() & 0xff)); + for (size_t i = 0; i < GetParam(); ++i) + { + expected.push_back('x'); + } // compare result + size const auto result = json::to_msgpack(j); @@ -790,13 +799,16 @@ TEST_P(MessagePackString5Test, Case) json j = s; // create expected byte vector (hack: create string first) - std::string expected; - expected.push_back(static_cast(0xdb)); - expected.push_back(static_cast((GetParam() >> 24) & 0xff)); - expected.push_back(static_cast((GetParam() >> 16) & 0xff)); - expected.push_back(static_cast((GetParam() >> 8) & 0xff)); - expected.push_back(static_cast(GetParam() & 0xff)); - expected.append(s); + std::vector expected; + expected.push_back(static_cast(0xdb)); + expected.push_back(static_cast((GetParam() >> 24) & 0xff)); + expected.push_back(static_cast((GetParam() >> 16) & 0xff)); + expected.push_back(static_cast((GetParam() >> 8) & 0xff)); + expected.push_back(static_cast(GetParam() & 0xff)); + for (size_t i = 0; i < GetParam(); ++i) + { + expected.push_back('x'); + } // compare result + size const auto result = json::to_msgpack(j); @@ -821,7 +833,7 @@ INSTANTIATE_TEST_CASE_P(MessagePackString5Tests, MessagePackString5Test, TEST(MessagePackArrayTest, Empty) { json j = json::array(); - std::string expected = "\x90"; + std::vector expected = {0x90}; const auto result = json::to_msgpack(j); EXPECT_EQ(result, expected); @@ -833,7 +845,7 @@ TEST(MessagePackArrayTest, Empty) TEST(MessagePackArrayTest, Null) { json j = {nullptr}; - std::string expected = "\x91\xc0"; + std::vector expected = {0x91,0xc0}; const auto result = json::to_msgpack(j); EXPECT_EQ(result, expected); @@ -845,7 +857,7 @@ TEST(MessagePackArrayTest, Null) TEST(MessagePackArrayTest, Simple) { json j = json::parse("[1,2,3,4,5]"); - std::string expected = "\x95\x01\x02\x03\x04\x05"; + std::vector expected = {0x95,0x01,0x02,0x03,0x04,0x05}; const auto result = json::to_msgpack(j); EXPECT_EQ(result, expected); @@ -857,7 +869,7 @@ TEST(MessagePackArrayTest, Simple) TEST(MessagePackArrayTest, NestEmpty) { json j = json::parse("[[[[]]]]"); - std::string expected = "\x91\x91\x91\x90"; + std::vector expected = {0x91,0x91,0x91,0x90}; const auto result = json::to_msgpack(j); EXPECT_EQ(result, expected); @@ -869,8 +881,8 @@ TEST(MessagePackArrayTest, NestEmpty) TEST(MessagePackArrayTest, UInt16) { json j(16, nullptr); - std::string expected(j.size() + 3, static_cast(0xc0)); // all null - expected[0] = static_cast(0xdc); // array 16 + std::vector expected(j.size() + 3, static_cast(0xc0)); // all null + expected[0] = static_cast(0xdc); // array 16 expected[1] = 0x00; // size (0x0010), byte 0 expected[2] = 0x10; // size (0x0010), byte 1 const auto result = json::to_msgpack(j); @@ -884,8 +896,8 @@ TEST(MessagePackArrayTest, UInt16) TEST(MessagePackArrayTest, UInt32) { json j(65536, nullptr); - std::string expected(j.size() + 5, static_cast(0xc0)); // all null - expected[0] = static_cast(0xdd); // array 32 + std::vector expected(j.size() + 5, static_cast(0xc0)); // all null + expected[0] = static_cast(0xdd); // array 32 expected[1] = 0x00; // size (0x00100000), byte 0 expected[2] = 0x01; // size (0x00100000), byte 1 expected[3] = 0x00; // size (0x00100000), byte 2 @@ -907,7 +919,7 @@ TEST(MessagePackArrayTest, UInt32) TEST(MessagePackObjectTest, Empty) { json j = json::object(); - std::string expected = "\x80"; + std::vector expected = {0x80}; const auto result = json::to_msgpack(j); EXPECT_EQ(result, expected); @@ -919,7 +931,7 @@ TEST(MessagePackObjectTest, Empty) TEST(MessagePackObjectTest, EmptyKey) { json j = {{"", nullptr}}; - std::string expected = "\x81\xa0\xc0"; + std::vector expected = {0x81,0xa0,0xc0}; const auto result = json::to_msgpack(j); EXPECT_EQ(result, expected); @@ -931,7 +943,7 @@ TEST(MessagePackObjectTest, EmptyKey) TEST(MessagePackObjectTest, NestedEmpty) { json j = json::parse("{\"a\": {\"b\": {\"c\": {}}}}"); - std::string expected = "\x81\xa1\x61\x81\xa1\x62\x81\xa1\x63\x80"; + std::vector expected = {0x81,0xa1,0x61,0x81,0xa1,0x62,0x81,0xa1,0x63,0x80}; const auto result = json::to_msgpack(j); EXPECT_EQ(result, expected); @@ -955,7 +967,7 @@ TEST(MessagePackObjectTest, UInt16) // size and the overall size. The rest is then handled in the // roundtrip check. EXPECT_EQ(result.size(), 67u); // 1 type, 2 size, 16*4 content - EXPECT_EQ(result[0], static_cast(0xde)); // map 16 + EXPECT_EQ(result[0], static_cast(0xde)); // map 16 EXPECT_EQ(result[1], 0x00); // byte 0 of size (0x0010) EXPECT_EQ(result[2], 0x10); // byte 1 of size (0x0010) @@ -984,7 +996,7 @@ TEST(MessagePackObjectTest, UInt32) // size and the overall size. The rest is then handled in the // roundtrip check. EXPECT_EQ(result.size(), 458757u); // 1 type, 4 size, 65536*7 content - EXPECT_EQ(result[0], static_cast(0xdf)); // map 32 + EXPECT_EQ(result[0], static_cast(0xdf)); // map 32 EXPECT_EQ(result[1], 0x00); // byte 0 of size (0x00010000) EXPECT_EQ(result[2], 0x01); // byte 1 of size (0x00010000) EXPECT_EQ(result[3], 0x00); // byte 2 of size (0x00010000) @@ -997,50 +1009,50 @@ TEST(MessagePackObjectTest, UInt32) // from float32 TEST(MessagePackFloat32Test, Case) { - auto given = std::string("\xca\x41\xc8\x00\x01", 5); + auto given = std::vector({0xca,0x41,0xc8,0x00,0x01}); json j = json::from_msgpack(given); EXPECT_LT(std::fabs(j.get() - 25), 0.001); } TEST(MessagePackErrorTest, TooShortByteVector) { - EXPECT_THROW_MSG(json::from_msgpack("\xcc"), json::parse_error, + EXPECT_THROW_MSG(json::from_msgpack(std::vector({0xcc})), json::parse_error, "[json.exception.parse_error.110] parse error at 2: unexpected end of input"); - EXPECT_THROW_MSG(json::from_msgpack("\xcd"), json::parse_error, + EXPECT_THROW_MSG(json::from_msgpack(std::vector({0xcd})), json::parse_error, "[json.exception.parse_error.110] parse error at 2: unexpected end of input"); - EXPECT_THROW_MSG(json::from_msgpack(wpi::StringRef("\xcd\x00", 2)), json::parse_error, + EXPECT_THROW_MSG(json::from_msgpack(std::vector({0xcd,0x00})), json::parse_error, "[json.exception.parse_error.110] parse error at 3: unexpected end of input"); - EXPECT_THROW_MSG(json::from_msgpack("\xce"), json::parse_error, + EXPECT_THROW_MSG(json::from_msgpack(std::vector({0xce})), json::parse_error, "[json.exception.parse_error.110] parse error at 2: unexpected end of input"); - EXPECT_THROW_MSG(json::from_msgpack(wpi::StringRef("\xce\x00", 2)), json::parse_error, + EXPECT_THROW_MSG(json::from_msgpack(std::vector({0xce,0x00})), json::parse_error, "[json.exception.parse_error.110] parse error at 3: unexpected end of input"); - EXPECT_THROW_MSG(json::from_msgpack(wpi::StringRef("\xce\x00\x00", 3)), json::parse_error, + EXPECT_THROW_MSG(json::from_msgpack(std::vector({0xce,0x00,0x00})), json::parse_error, "[json.exception.parse_error.110] parse error at 4: unexpected end of input"); - EXPECT_THROW_MSG(json::from_msgpack(wpi::StringRef("\xce\x00\x00\x00", 4)), json::parse_error, + EXPECT_THROW_MSG(json::from_msgpack(std::vector({0xce,0x00,0x00,0x00})), json::parse_error, "[json.exception.parse_error.110] parse error at 5: unexpected end of input"); - EXPECT_THROW_MSG(json::from_msgpack("\xcf"), json::parse_error, + EXPECT_THROW_MSG(json::from_msgpack(std::vector({0xcf})), json::parse_error, "[json.exception.parse_error.110] parse error at 2: unexpected end of input"); - EXPECT_THROW_MSG(json::from_msgpack(wpi::StringRef("\xcf\x00", 2)), json::parse_error, + EXPECT_THROW_MSG(json::from_msgpack(std::vector({0xcf,0x00})), json::parse_error, "[json.exception.parse_error.110] parse error at 3: unexpected end of input"); - EXPECT_THROW_MSG(json::from_msgpack(wpi::StringRef("\xcf\x00\x00", 3)), json::parse_error, + EXPECT_THROW_MSG(json::from_msgpack(std::vector({0xcf,0x00,0x00})), json::parse_error, "[json.exception.parse_error.110] parse error at 4: unexpected end of input"); - EXPECT_THROW_MSG(json::from_msgpack(wpi::StringRef("\xcf\x00\x00\x00", 4)), json::parse_error, + EXPECT_THROW_MSG(json::from_msgpack(std::vector({0xcf,0x00,0x00,0x00})), json::parse_error, "[json.exception.parse_error.110] parse error at 5: unexpected end of input"); - EXPECT_THROW_MSG(json::from_msgpack(wpi::StringRef("\xcf\x00\x00\x00\x00", 5)), json::parse_error, + EXPECT_THROW_MSG(json::from_msgpack(std::vector({0xcf,0x00,0x00,0x00,0x00})), json::parse_error, "[json.exception.parse_error.110] parse error at 6: unexpected end of input"); - EXPECT_THROW_MSG(json::from_msgpack(wpi::StringRef("\xcf\x00\x00\x00\x00\x00", 6)), json::parse_error, + EXPECT_THROW_MSG(json::from_msgpack(std::vector({0xcf,0x00,0x00,0x00,0x00,0x00})), json::parse_error, "[json.exception.parse_error.110] parse error at 7: unexpected end of input"); - EXPECT_THROW_MSG(json::from_msgpack(wpi::StringRef("\xcf\x00\x00\x00\x00\x00\x00", 7)), json::parse_error, + EXPECT_THROW_MSG(json::from_msgpack(std::vector({0xcf,0x00,0x00,0x00,0x00,0x00,0x00})), json::parse_error, "[json.exception.parse_error.110] parse error at 8: unexpected end of input"); - EXPECT_THROW_MSG(json::from_msgpack(wpi::StringRef("\xcf\x00\x00\x00\x00\x00\x00\x00", 8)), json::parse_error, + EXPECT_THROW_MSG(json::from_msgpack(std::vector({0xcf,0x00,0x00,0x00,0x00,0x00,0x00,0x00})), json::parse_error, "[json.exception.parse_error.110] parse error at 9: unexpected end of input"); } TEST(MessagePackErrorTest, UnsupportedBytesConcrete) { - EXPECT_THROW_MSG(json::from_msgpack("\xc1"), json::parse_error, + EXPECT_THROW_MSG(json::from_msgpack(std::vector({0xc1})), json::parse_error, "[json.exception.parse_error.112] parse error at 1: error reading MessagePack; last byte: 0xc1"); - EXPECT_THROW_MSG(json::from_msgpack("\xc6"), json::parse_error, + EXPECT_THROW_MSG(json::from_msgpack(std::vector({0xc6})), json::parse_error, "[json.exception.parse_error.112] parse error at 1: error reading MessagePack; last byte: 0xc6"); } @@ -1058,7 +1070,7 @@ TEST(MessagePackErrorTest, UnsupportedBytesAll) 0xd4, 0xd5, 0xd6, 0xd7, 0xd8 }) { - EXPECT_THROW(json::from_msgpack(std::string(1, static_cast(byte))), json::parse_error); + EXPECT_THROW(json::from_msgpack(std::vector({static_cast(byte)})), json::parse_error); } } #if 0 diff --git a/wpiutil/src/test/native/cpp/json/unit-pointer_access.cpp b/wpiutil/src/test/native/cpp/json/unit-pointer_access.cpp index a6a87fcc3e..a1a7fa4feb 100644 --- a/wpiutil/src/test/native/cpp/json/unit-pointer_access.cpp +++ b/wpiutil/src/test/native/cpp/json/unit-pointer_access.cpp @@ -78,11 +78,11 @@ TEST(JsonPointerTest, ObjectT) // check if null pointers are returned correctly EXPECT_NE(value.get_ptr(), nullptr); EXPECT_EQ(value.get_ptr(), nullptr); - EXPECT_EQ(value.get_ptr(), nullptr); - EXPECT_EQ(value.get_ptr(), nullptr); - EXPECT_EQ(value.get_ptr(), nullptr); - EXPECT_EQ(value.get_ptr(), nullptr); - EXPECT_EQ(value.get_ptr(), nullptr); + EXPECT_EQ(value.get_ptr(), nullptr); + EXPECT_EQ(value.get_ptr(), nullptr); + EXPECT_EQ(value.get_ptr(), nullptr); + EXPECT_EQ(value.get_ptr(), nullptr); + EXPECT_EQ(value.get_ptr(), nullptr); } // pointer access to const object_t @@ -107,11 +107,11 @@ TEST(JsonPointerTest, ConstObjectT) // check if null pointers are returned correctly EXPECT_NE(value.get_ptr(), nullptr); EXPECT_EQ(value.get_ptr(), nullptr); - EXPECT_EQ(value.get_ptr(), nullptr); - EXPECT_EQ(value.get_ptr(), nullptr); - EXPECT_EQ(value.get_ptr(), nullptr); - EXPECT_EQ(value.get_ptr(), nullptr); - EXPECT_EQ(value.get_ptr(), nullptr); + EXPECT_EQ(value.get_ptr(), nullptr); + EXPECT_EQ(value.get_ptr(), nullptr); + EXPECT_EQ(value.get_ptr(), nullptr); + EXPECT_EQ(value.get_ptr(), nullptr); + EXPECT_EQ(value.get_ptr(), nullptr); } // pointer access to array_t @@ -136,11 +136,11 @@ TEST(JsonPointerTest, ArrayT) // check if null pointers are returned correctly EXPECT_EQ(value.get_ptr(), nullptr); EXPECT_NE(value.get_ptr(), nullptr); - EXPECT_EQ(value.get_ptr(), nullptr); - EXPECT_EQ(value.get_ptr(), nullptr); - EXPECT_EQ(value.get_ptr(), nullptr); - EXPECT_EQ(value.get_ptr(), nullptr); - EXPECT_EQ(value.get_ptr(), nullptr); + EXPECT_EQ(value.get_ptr(), nullptr); + EXPECT_EQ(value.get_ptr(), nullptr); + EXPECT_EQ(value.get_ptr(), nullptr); + EXPECT_EQ(value.get_ptr(), nullptr); + EXPECT_EQ(value.get_ptr(), nullptr); } // pointer access to const array_t @@ -165,17 +165,17 @@ TEST(JsonPointerTest, ConstArrayT) // check if null pointers are returned correctly EXPECT_EQ(value.get_ptr(), nullptr); EXPECT_NE(value.get_ptr(), nullptr); - EXPECT_EQ(value.get_ptr(), nullptr); - EXPECT_EQ(value.get_ptr(), nullptr); - EXPECT_EQ(value.get_ptr(), nullptr); - EXPECT_EQ(value.get_ptr(), nullptr); - EXPECT_EQ(value.get_ptr(), nullptr); + EXPECT_EQ(value.get_ptr(), nullptr); + EXPECT_EQ(value.get_ptr(), nullptr); + EXPECT_EQ(value.get_ptr(), nullptr); + EXPECT_EQ(value.get_ptr(), nullptr); + EXPECT_EQ(value.get_ptr(), nullptr); } // pointer access to string_t TEST(JsonPointerTest, StringT) { - using test_type = json::string_t; + using test_type = std::string; json value = "hello"; // check if pointers are returned correctly @@ -194,17 +194,17 @@ TEST(JsonPointerTest, StringT) // check if null pointers are returned correctly EXPECT_EQ(value.get_ptr(), nullptr); EXPECT_EQ(value.get_ptr(), nullptr); - EXPECT_NE(value.get_ptr(), nullptr); - EXPECT_EQ(value.get_ptr(), nullptr); - EXPECT_EQ(value.get_ptr(), nullptr); - EXPECT_EQ(value.get_ptr(), nullptr); - EXPECT_EQ(value.get_ptr(), nullptr); + EXPECT_NE(value.get_ptr(), nullptr); + EXPECT_EQ(value.get_ptr(), nullptr); + EXPECT_EQ(value.get_ptr(), nullptr); + EXPECT_EQ(value.get_ptr(), nullptr); + EXPECT_EQ(value.get_ptr(), nullptr); } // pointer access to const string_t TEST(JsonPointerTest, ConstStringT) { - using test_type = const json::string_t; + using test_type = const std::string; const json value = "hello"; // check if pointers are returned correctly @@ -223,17 +223,17 @@ TEST(JsonPointerTest, ConstStringT) // check if null pointers are returned correctly EXPECT_EQ(value.get_ptr(), nullptr); EXPECT_EQ(value.get_ptr(), nullptr); - EXPECT_NE(value.get_ptr(), nullptr); - EXPECT_EQ(value.get_ptr(), nullptr); - EXPECT_EQ(value.get_ptr(), nullptr); - EXPECT_EQ(value.get_ptr(), nullptr); - EXPECT_EQ(value.get_ptr(), nullptr); + EXPECT_NE(value.get_ptr(), nullptr); + EXPECT_EQ(value.get_ptr(), nullptr); + EXPECT_EQ(value.get_ptr(), nullptr); + EXPECT_EQ(value.get_ptr(), nullptr); + EXPECT_EQ(value.get_ptr(), nullptr); } // pointer access to boolean_t TEST(JsonPointerTest, BooleanT) { - using test_type = json::boolean_t; + using test_type = bool; json value = false; // check if pointers are returned correctly @@ -252,17 +252,17 @@ TEST(JsonPointerTest, BooleanT) // check if null pointers are returned correctly EXPECT_EQ(value.get_ptr(), nullptr); EXPECT_EQ(value.get_ptr(), nullptr); - EXPECT_EQ(value.get_ptr(), nullptr); - EXPECT_NE(value.get_ptr(), nullptr); - EXPECT_EQ(value.get_ptr(), nullptr); - EXPECT_EQ(value.get_ptr(), nullptr); - EXPECT_EQ(value.get_ptr(), nullptr); + EXPECT_EQ(value.get_ptr(), nullptr); + EXPECT_NE(value.get_ptr(), nullptr); + EXPECT_EQ(value.get_ptr(), nullptr); + EXPECT_EQ(value.get_ptr(), nullptr); + EXPECT_EQ(value.get_ptr(), nullptr); } // pointer access to const boolean_t TEST(JsonPointerTest, ConstBooleanT) { - using test_type = const json::boolean_t; + using test_type = const bool; const json value = false; // check if pointers are returned correctly @@ -281,17 +281,17 @@ TEST(JsonPointerTest, ConstBooleanT) // check if null pointers are returned correctly EXPECT_EQ(value.get_ptr(), nullptr); EXPECT_EQ(value.get_ptr(), nullptr); - EXPECT_EQ(value.get_ptr(), nullptr); - EXPECT_NE(value.get_ptr(), nullptr); - EXPECT_EQ(value.get_ptr(), nullptr); - EXPECT_EQ(value.get_ptr(), nullptr); - EXPECT_EQ(value.get_ptr(), nullptr); + EXPECT_EQ(value.get_ptr(), nullptr); + EXPECT_NE(value.get_ptr(), nullptr); + EXPECT_EQ(value.get_ptr(), nullptr); + EXPECT_EQ(value.get_ptr(), nullptr); + EXPECT_EQ(value.get_ptr(), nullptr); } // pointer access to number_integer_t TEST(JsonPointerTest, IntegerT) { - using test_type = json::number_integer_t; + using test_type = int64_t; json value = 23; // check if pointers are returned correctly @@ -310,17 +310,17 @@ TEST(JsonPointerTest, IntegerT) // check if null pointers are returned correctly EXPECT_EQ(value.get_ptr(), nullptr); EXPECT_EQ(value.get_ptr(), nullptr); - EXPECT_EQ(value.get_ptr(), nullptr); - EXPECT_EQ(value.get_ptr(), nullptr); - EXPECT_NE(value.get_ptr(), nullptr); - EXPECT_EQ(value.get_ptr(), nullptr); - EXPECT_EQ(value.get_ptr(), nullptr); + EXPECT_EQ(value.get_ptr(), nullptr); + EXPECT_EQ(value.get_ptr(), nullptr); + EXPECT_NE(value.get_ptr(), nullptr); + EXPECT_EQ(value.get_ptr(), nullptr); + EXPECT_EQ(value.get_ptr(), nullptr); } // pointer access to const number_integer_t TEST(JsonPointerTest, ConstIntegerT) { - using test_type = const json::number_integer_t; + using test_type = const int64_t; const json value = 23; // check if pointers are returned correctly @@ -339,17 +339,17 @@ TEST(JsonPointerTest, ConstIntegerT) // check if null pointers are returned correctly EXPECT_EQ(value.get_ptr(), nullptr); EXPECT_EQ(value.get_ptr(), nullptr); - EXPECT_EQ(value.get_ptr(), nullptr); - EXPECT_EQ(value.get_ptr(), nullptr); - EXPECT_NE(value.get_ptr(), nullptr); - EXPECT_EQ(value.get_ptr(), nullptr); - EXPECT_EQ(value.get_ptr(), nullptr); + EXPECT_EQ(value.get_ptr(), nullptr); + EXPECT_EQ(value.get_ptr(), nullptr); + EXPECT_NE(value.get_ptr(), nullptr); + EXPECT_EQ(value.get_ptr(), nullptr); + EXPECT_EQ(value.get_ptr(), nullptr); } // pointer access to number_unsigned_t TEST(JsonPointerTest, UnsignedT) { - using test_type = json::number_unsigned_t; + using test_type = uint64_t; json value = 23u; // check if pointers are returned correctly @@ -368,17 +368,17 @@ TEST(JsonPointerTest, UnsignedT) // check if null pointers are returned correctly EXPECT_EQ(value.get_ptr(), nullptr); EXPECT_EQ(value.get_ptr(), nullptr); - EXPECT_EQ(value.get_ptr(), nullptr); - EXPECT_EQ(value.get_ptr(), nullptr); - EXPECT_NE(value.get_ptr(), nullptr); - EXPECT_NE(value.get_ptr(), nullptr); - EXPECT_EQ(value.get_ptr(), nullptr); + EXPECT_EQ(value.get_ptr(), nullptr); + EXPECT_EQ(value.get_ptr(), nullptr); + EXPECT_NE(value.get_ptr(), nullptr); + EXPECT_NE(value.get_ptr(), nullptr); + EXPECT_EQ(value.get_ptr(), nullptr); } // pointer access to const number_unsigned_t TEST(JsonPointerTest, ConstUnsignedT) { - using test_type = const json::number_unsigned_t; + using test_type = const uint64_t; const json value = 23u; // check if pointers are returned correctly @@ -397,17 +397,17 @@ TEST(JsonPointerTest, ConstUnsignedT) // check if null pointers are returned correctly EXPECT_EQ(value.get_ptr(), nullptr); EXPECT_EQ(value.get_ptr(), nullptr); - EXPECT_EQ(value.get_ptr(), nullptr); - EXPECT_EQ(value.get_ptr(), nullptr); - EXPECT_NE(value.get_ptr(), nullptr); - EXPECT_NE(value.get_ptr(), nullptr); - EXPECT_EQ(value.get_ptr(), nullptr); + EXPECT_EQ(value.get_ptr(), nullptr); + EXPECT_EQ(value.get_ptr(), nullptr); + EXPECT_NE(value.get_ptr(), nullptr); + EXPECT_NE(value.get_ptr(), nullptr); + EXPECT_EQ(value.get_ptr(), nullptr); } // pointer access to number_float_t TEST(JsonPointerTest, FloatT) { - using test_type = json::number_float_t; + using test_type = double; json value = 42.23; // check if pointers are returned correctly @@ -426,17 +426,17 @@ TEST(JsonPointerTest, FloatT) // check if null pointers are returned correctly EXPECT_EQ(value.get_ptr(), nullptr); EXPECT_EQ(value.get_ptr(), nullptr); - EXPECT_EQ(value.get_ptr(), nullptr); - EXPECT_EQ(value.get_ptr(), nullptr); - EXPECT_EQ(value.get_ptr(), nullptr); - EXPECT_EQ(value.get_ptr(), nullptr); - EXPECT_NE(value.get_ptr(), nullptr); + EXPECT_EQ(value.get_ptr(), nullptr); + EXPECT_EQ(value.get_ptr(), nullptr); + EXPECT_EQ(value.get_ptr(), nullptr); + EXPECT_EQ(value.get_ptr(), nullptr); + EXPECT_NE(value.get_ptr(), nullptr); } // pointer access to const number_float_t TEST(JsonPointerTest, ConstFloatT) { - using test_type = const json::number_float_t; + using test_type = const double; const json value = 42.23; // check if pointers are returned correctly @@ -455,9 +455,9 @@ TEST(JsonPointerTest, ConstFloatT) // check if null pointers are returned correctly EXPECT_EQ(value.get_ptr(), nullptr); EXPECT_EQ(value.get_ptr(), nullptr); - EXPECT_EQ(value.get_ptr(), nullptr); - EXPECT_EQ(value.get_ptr(), nullptr); - EXPECT_EQ(value.get_ptr(), nullptr); - EXPECT_EQ(value.get_ptr(), nullptr); - EXPECT_NE(value.get_ptr(), nullptr); + EXPECT_EQ(value.get_ptr(), nullptr); + EXPECT_EQ(value.get_ptr(), nullptr); + EXPECT_EQ(value.get_ptr(), nullptr); + EXPECT_EQ(value.get_ptr(), nullptr); + EXPECT_NE(value.get_ptr(), nullptr); } diff --git a/wpiutil/src/test/native/cpp/json/unit-readme.cpp b/wpiutil/src/test/native/cpp/json/unit-readme.cpp index eb7a212dff..ecb7f79ff1 100644 --- a/wpiutil/src/test/native/cpp/json/unit-readme.cpp +++ b/wpiutil/src/test/native/cpp/json/unit-readme.cpp @@ -45,6 +45,8 @@ using wpi::json; #include #include +#include "wpi/raw_ostream.h" + TEST(JsonReadmeTest, Basic) { // create an empty structure (null) diff --git a/wpiutil/src/test/native/cpp/json/unit-reference_access.cpp b/wpiutil/src/test/native/cpp/json/unit-reference_access.cpp index 1cea68946d..7c3cfb1379 100644 --- a/wpiutil/src/test/native/cpp/json/unit-reference_access.cpp +++ b/wpiutil/src/test/native/cpp/json/unit-reference_access.cpp @@ -55,10 +55,10 @@ TEST(JsonReferenceTest, ObjectT) // check if mismatching references throw correctly EXPECT_NO_THROW(value.get_ref()); EXPECT_ANY_THROW(value.get_ref()); - EXPECT_ANY_THROW(value.get_ref()); - EXPECT_ANY_THROW(value.get_ref()); - EXPECT_ANY_THROW(value.get_ref()); - EXPECT_ANY_THROW(value.get_ref()); + EXPECT_ANY_THROW(value.get_ref()); + EXPECT_ANY_THROW(value.get_ref()); + EXPECT_ANY_THROW(value.get_ref()); + EXPECT_ANY_THROW(value.get_ref()); } // const reference access to const object_t @@ -94,16 +94,16 @@ TEST(JsonReferenceTest, ArrayT) // check if mismatching references throw correctly EXPECT_ANY_THROW(value.get_ref()); EXPECT_NO_THROW(value.get_ref()); - EXPECT_ANY_THROW(value.get_ref()); - EXPECT_ANY_THROW(value.get_ref()); - EXPECT_ANY_THROW(value.get_ref()); - EXPECT_ANY_THROW(value.get_ref()); + EXPECT_ANY_THROW(value.get_ref()); + EXPECT_ANY_THROW(value.get_ref()); + EXPECT_ANY_THROW(value.get_ref()); + EXPECT_ANY_THROW(value.get_ref()); } // reference access to string_t TEST(JsonReferenceTest, StringT) { - using test_type = json::string_t; + using test_type = std::string; json value = "hello"; // check if references are returned correctly @@ -118,16 +118,16 @@ TEST(JsonReferenceTest, StringT) // check if mismatching references throw correctly EXPECT_ANY_THROW(value.get_ref()); EXPECT_ANY_THROW(value.get_ref()); - EXPECT_NO_THROW(value.get_ref()); - EXPECT_ANY_THROW(value.get_ref()); - EXPECT_ANY_THROW(value.get_ref()); - EXPECT_ANY_THROW(value.get_ref()); + EXPECT_NO_THROW(value.get_ref()); + EXPECT_ANY_THROW(value.get_ref()); + EXPECT_ANY_THROW(value.get_ref()); + EXPECT_ANY_THROW(value.get_ref()); } // reference access to boolean_t TEST(JsonReferenceTest, BooleanT) { - using test_type = json::boolean_t; + using test_type = bool; json value = false; // check if references are returned correctly @@ -142,16 +142,16 @@ TEST(JsonReferenceTest, BooleanT) // check if mismatching references throw correctly EXPECT_ANY_THROW(value.get_ref()); EXPECT_ANY_THROW(value.get_ref()); - EXPECT_ANY_THROW(value.get_ref()); - EXPECT_NO_THROW(value.get_ref()); - EXPECT_ANY_THROW(value.get_ref()); - EXPECT_ANY_THROW(value.get_ref()); + EXPECT_ANY_THROW(value.get_ref()); + EXPECT_NO_THROW(value.get_ref()); + EXPECT_ANY_THROW(value.get_ref()); + EXPECT_ANY_THROW(value.get_ref()); } // reference access to number_integer_t TEST(JsonReferenceTest, IntegerT) { - using test_type = json::number_integer_t; + using test_type = int64_t; json value = 23; // check if references are returned correctly @@ -166,16 +166,16 @@ TEST(JsonReferenceTest, IntegerT) // check if mismatching references throw correctly EXPECT_ANY_THROW(value.get_ref()); EXPECT_ANY_THROW(value.get_ref()); - EXPECT_ANY_THROW(value.get_ref()); - EXPECT_ANY_THROW(value.get_ref()); - EXPECT_NO_THROW(value.get_ref()); - EXPECT_ANY_THROW(value.get_ref()); + EXPECT_ANY_THROW(value.get_ref()); + EXPECT_ANY_THROW(value.get_ref()); + EXPECT_NO_THROW(value.get_ref()); + EXPECT_ANY_THROW(value.get_ref()); } // reference access to number_float_t TEST(JsonReferenceTest, FloatT) { - using test_type = json::number_float_t; + using test_type = double; json value = 42.23; // check if references are returned correctly @@ -190,8 +190,8 @@ TEST(JsonReferenceTest, FloatT) // check if mismatching references throw correctly EXPECT_ANY_THROW(value.get_ref()); EXPECT_ANY_THROW(value.get_ref()); - EXPECT_ANY_THROW(value.get_ref()); - EXPECT_ANY_THROW(value.get_ref()); - EXPECT_ANY_THROW(value.get_ref()); - EXPECT_NO_THROW(value.get_ref()); + EXPECT_ANY_THROW(value.get_ref()); + EXPECT_ANY_THROW(value.get_ref()); + EXPECT_ANY_THROW(value.get_ref()); + EXPECT_NO_THROW(value.get_ref()); } diff --git a/wpiutil/src/test/native/cpp/json/unit-unicode.cpp b/wpiutil/src/test/native/cpp/json/unit-unicode.cpp index a8c6cac74f..e0179b62f8 100644 --- a/wpiutil/src/test/native/cpp/json/unit-unicode.cpp +++ b/wpiutil/src/test/native/cpp/json/unit-unicode.cpp @@ -39,6 +39,7 @@ using wpi::json; #include "wpi/Format.h" #include "wpi/StringExtras.h" +#include "wpi/raw_ostream.h" #include