| /*----------------------------------------------------------------------------*/ |
| /* 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. */ |
| /*----------------------------------------------------------------------------*/ |
| /* |
| __ _____ _____ _____ |
| __| | __| | | | JSON for Modern C++ |
| | | |__ | | | | | | version 3.1.2 |
| |_____|_____|_____|_|___| https://github.com/nlohmann/json |
| |
| Licensed under the MIT License <http://opensource.org/licenses/MIT>. |
| Copyright (c) 2013-2018 Niels Lohmann <http://nlohmann.me>. |
| |
| Permission is hereby granted, free of charge, to any person obtaining a copy |
| of this software and associated documentation files (the "Software"), to deal |
| in the Software without restriction, including without limitation the rights |
| to use, copy, modify, merge, publish, distribute, sublicense, and/or sell |
| copies of the Software, and to permit persons to whom the Software is |
| furnished to do so, subject to the following conditions: |
| |
| The above copyright notice and this permission notice shall be included in all |
| copies or substantial portions of the Software. |
| |
| THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR |
| IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, |
| FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE |
| AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER |
| LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, |
| OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE |
| SOFTWARE. |
| */ |
| #define WPI_JSON_IMPLEMENTATION |
| #include "wpi/json.h" |
| |
| #include "wpi/raw_ostream.h" |
| |
| namespace wpi { |
| namespace detail { |
| |
| 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 parse_error(id_, byte_, "[json.exception.parse_error." + Twine(id_) + "] parse error" + |
| (byte_ != 0 ? (" at " + Twine(byte_)) : Twine("")) + |
| ": " + what_arg); |
| } |
| |
| invalid_iterator invalid_iterator::create(int id_, const Twine& what_arg) |
| { |
| return invalid_iterator(id_, "[json.exception.invalid_iterator." + Twine(id_) + "] " + what_arg); |
| } |
| |
| type_error type_error::create(int id_, const Twine& what_arg) |
| { |
| return type_error(id_, "[json.exception.type_error." + Twine(id_) + "] " + what_arg); |
| } |
| |
| out_of_range out_of_range::create(int id_, const Twine& what_arg) |
| { |
| return out_of_range(id_, "[json.exception.out_of_range." + Twine(id_) + "] " + what_arg); |
| } |
| |
| other_error other_error::create(int id_, const Twine& what_arg) |
| { |
| return other_error(id_, "[json.exception.other_error." + Twine(id_) + "] " + what_arg); |
| } |
| |
| } // namespace detail |
| |
| json::json_value::json_value(value_t t) |
| { |
| switch (t) |
| { |
| case value_t::object: |
| { |
| object = create<object_t>(); |
| break; |
| } |
| |
| case value_t::array: |
| { |
| array = create<array_t>(); |
| break; |
| } |
| |
| case value_t::string: |
| { |
| string = create<std::string>(""); |
| break; |
| } |
| |
| case value_t::boolean: |
| { |
| boolean = false; |
| break; |
| } |
| |
| case value_t::number_integer: |
| { |
| number_integer = 0; |
| break; |
| } |
| |
| case value_t::number_unsigned: |
| { |
| number_unsigned = 0u; |
| break; |
| } |
| |
| case value_t::number_float: |
| { |
| number_float = 0.0; |
| break; |
| } |
| |
| 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 3.1.2")); // LCOV_EXCL_LINE |
| } |
| break; |
| } |
| } |
| } |
| |
| void json::json_value::destroy(value_t t) noexcept |
| { |
| switch (t) |
| { |
| case value_t::object: |
| { |
| std::allocator<object_t> alloc; |
| std::allocator_traits<decltype(alloc)>::destroy(alloc, object); |
| std::allocator_traits<decltype(alloc)>::deallocate(alloc, object, 1); |
| break; |
| } |
| |
| case value_t::array: |
| { |
| std::allocator<array_t> alloc; |
| std::allocator_traits<decltype(alloc)>::destroy(alloc, array); |
| std::allocator_traits<decltype(alloc)>::deallocate(alloc, array, 1); |
| break; |
| } |
| |
| case value_t::string: |
| { |
| std::allocator<std::string> alloc; |
| std::allocator_traits<decltype(alloc)>::destroy(alloc, string); |
| std::allocator_traits<decltype(alloc)>::deallocate(alloc, string, 1); |
| break; |
| } |
| |
| default: |
| { |
| break; |
| } |
| } |
| } |
| |
| 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 detail::json_ref<json>& element_ref) |
| { |
| 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 (not type_deduction) |
| { |
| // if array is wanted, do not create an object though possible |
| if (manual_type == value_t::array) |
| { |
| is_an_object = false; |
| } |
| |
| // if object is wanted but impossible, throw an exception |
| 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")); |
| } |
| } |
| |
| if (is_an_object) |
| { |
| // the initializer list is a list of pairs -> create object |
| m_type = value_t::object; |
| m_value = value_t::object; |
| |
| std::for_each(init.begin(), init.end(), [this](const detail::json_ref<json>& element_ref) |
| { |
| auto element = element_ref.moved_or_copied(); |
| m_value.object->try_emplace( |
| *((*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<array_t>(init.begin(), init.end()); |
| } |
| |
| assert_invariant(); |
| } |
| |
| json::json(size_type cnt, const json& val) |
| : m_type(value_t::array) |
| { |
| m_value.array = create<array_t>(cnt, val); |
| assert_invariant(); |
| } |
| |
| json::json(const json& other) |
| : m_type(other.m_type) |
| { |
| // check of passed value is valid |
| other.assert_invariant(); |
| |
| switch (m_type) |
| { |
| case value_t::object: |
| { |
| m_value = *other.m_value.object; |
| break; |
| } |
| |
| case value_t::array: |
| { |
| m_value = *other.m_value.array; |
| break; |
| } |
| |
| case value_t::string: |
| { |
| m_value = *other.m_value.string; |
| break; |
| } |
| |
| case value_t::boolean: |
| { |
| m_value = other.m_value.boolean; |
| break; |
| } |
| |
| case value_t::number_integer: |
| { |
| m_value = other.m_value.number_integer; |
| break; |
| } |
| |
| case value_t::number_unsigned: |
| { |
| m_value = other.m_value.number_unsigned; |
| break; |
| } |
| |
| case value_t::number_float: |
| { |
| m_value = other.m_value.number_float; |
| break; |
| } |
| |
| default: |
| break; |
| } |
| |
| assert_invariant(); |
| } |
| |
| json json::meta() |
| { |
| json result; |
| |
| 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/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"; |
| #elif defined __linux__ |
| result["platform"] = "linux"; |
| #elif defined __APPLE__ |
| result["platform"] = "apple"; |
| #elif defined __unix__ |
| result["platform"] = "unix"; |
| #else |
| result["platform"] = "unknown"; |
| #endif |
| |
| #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) |
| result["compiler"] = "hp" |
| #elif defined(__IBMCPP__) |
| result["compiler"] = {{"family", "ilecpp"}, {"version", __IBMCPP__}}; |
| #elif defined(_MSC_VER) |
| result["compiler"] = {{"family", "msvc"}, {"version", _MSC_VER}}; |
| #elif defined(__PGI) |
| result["compiler"] = {{"family", "pgcpp"}, {"version", __PGI}}; |
| #elif defined(__SUNPRO_CC) |
| result["compiler"] = {{"family", "sunpro"}, {"version", __SUNPRO_CC}}; |
| #else |
| result["compiler"] = {{"family", "unknown"}, {"version", "unknown"}}; |
| #endif |
| |
| #ifdef __cplusplus |
| result["compiler"]["c++"] = std::to_string(__cplusplus); |
| #else |
| result["compiler"]["c++"] = "unknown"; |
| #endif |
| return result; |
| } |
| |
| json::reference json::at(size_type idx) |
| { |
| // at only works for arrays |
| if (JSON_LIKELY(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 " + Twine(idx) + " is out of range")); |
| } |
| } |
| else |
| { |
| JSON_THROW(type_error::create(304, "cannot use at() with " + Twine(type_name()))); |
| } |
| } |
| |
| json::const_reference json::at(size_type idx) const |
| { |
| // at only works for arrays |
| if (JSON_LIKELY(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 " + Twine(idx) + " is out of range")); |
| } |
| } |
| else |
| { |
| JSON_THROW(type_error::create(304, "cannot use at() with " + Twine(type_name()))); |
| } |
| } |
| |
| json::reference json::at(StringRef key) |
| { |
| // 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::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<array_t>(); |
| 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<object_t>(); |
| 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<difference_type>(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 |
| { |
| switch (m_type) |
| { |
| case value_t::null: |
| { |
| // null values are empty |
| return true; |
| } |
| |
| case value_t::array: |
| { |
| // delegate call to array_t::empty() |
| return m_value.array->empty(); |
| } |
| |
| case value_t::object: |
| { |
| // delegate call to object_t::empty() |
| return m_value.object->empty(); |
| } |
| |
| default: |
| { |
| // all other types are nonempty |
| return false; |
| } |
| } |
| } |
| |
| json::size_type json::size() const noexcept |
| { |
| switch (m_type) |
| { |
| case value_t::null: |
| { |
| // null values are empty |
| return 0; |
| } |
| |
| case value_t::array: |
| { |
| // delegate call to array_t::size() |
| return m_value.array->size(); |
| } |
| |
| case value_t::object: |
| { |
| // delegate call to object_t::size() |
| return m_value.object->size(); |
| } |
| |
| default: |
| { |
| // all other types have size 1 |
| return 1; |
| } |
| } |
| } |
| |
| json::size_type json::max_size() const noexcept |
| { |
| switch (m_type) |
| { |
| case value_t::array: |
| { |
| // delegate call to array_t::max_size() |
| return m_value.array->max_size(); |
| } |
| |
| case value_t::object: |
| { |
| // delegate call to std::allocator<object_t>::max_size() |
| return std::allocator_traits<object_t>::max_size(*m_value.object); |
| } |
| |
| default: |
| { |
| // all other types have max_size() == size() |
| return size(); |
| } |
| } |
| } |
| |
| void json::clear() noexcept |
| { |
| switch (m_type) |
| { |
| case value_t::number_integer: |
| { |
| m_value.number_integer = 0; |
| break; |
| } |
| |
| case value_t::number_unsigned: |
| { |
| m_value.number_unsigned = 0; |
| break; |
| } |
| |
| case value_t::number_float: |
| { |
| m_value.number_float = 0.0; |
| break; |
| } |
| |
| case value_t::boolean: |
| { |
| m_value.boolean = false; |
| break; |
| } |
| |
| case value_t::string: |
| { |
| m_value.string->clear(); |
| break; |
| } |
| |
| case value_t::array: |
| { |
| m_value.array->clear(); |
| break; |
| } |
| |
| case value_t::object: |
| { |
| m_value.object->clear(); |
| break; |
| } |
| |
| default: |
| break; |
| } |
| } |
| |
| void json::push_back(json&& val) |
| { |
| // push_back only works for null objects or arrays |
| if (JSON_UNLIKELY(not(is_null() or is_array()))) |
| { |
| JSON_THROW(type_error::create(308, "cannot use push_back() with " + Twine(type_name()))); |
| } |
| |
| // transform null object into an array |
| if (is_null()) |
| { |
| m_type = value_t::array; |
| m_value = value_t::array; |
| assert_invariant(); |
| } |
| |
| // add element to array (move semantics) |
| m_value.array->push_back(std::move(val)); |
| // invalidate object |
| val.m_type = value_t::null; |
| } |
| |
| void json::push_back(const json& val) |
| { |
| // push_back only works for null objects or arrays |
| if (JSON_UNLIKELY(not(is_null() or is_array()))) |
| { |
| JSON_THROW(type_error::create(308, "cannot use push_back() with " + Twine(type_name()))); |
| } |
| |
| // transform null object into an array |
| if (is_null()) |
| { |
| m_type = value_t::array; |
| m_value = value_t::array; |
| assert_invariant(); |
| } |
| |
| // add element to array |
| m_value.array->push_back(val); |
| } |
| |
| void json::push_back(initializer_list_t init) |
| { |
| if (is_object() and init.size() == 2 and (*init.begin())->is_string()) |
| { |
| std::string key = init.begin()->moved_or_copied(); |
| push_back(std::pair<StringRef, json>(key, (init.begin() + 1)->moved_or_copied())); |
| } |
| else |
| { |
| push_back(json(init)); |
| } |
| } |
| |
| json::iterator json::insert(const_iterator pos, const json& val) |
| { |
| // insert only works for arrays |
| if (JSON_LIKELY(is_array())) |
| { |
| // check if iterator pos fits to this JSON value |
| 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); |
| result.m_it.array_iterator = m_value.array->insert(pos.m_it.array_iterator, val); |
| return result; |
| } |
| |
| 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 (JSON_LIKELY(is_array())) |
| { |
| // check if iterator pos fits to this JSON value |
| 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); |
| result.m_it.array_iterator = m_value.array->insert(pos.m_it.array_iterator, cnt, val); |
| return result; |
| } |
| |
| 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 (JSON_UNLIKELY(not is_array())) |
| { |
| JSON_THROW(type_error::create(309, "cannot use insert() with " + Twine(type_name()))); |
| } |
| |
| // check if iterator pos fits to this JSON value |
| 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 (JSON_UNLIKELY(first.m_object != last.m_object)) |
| { |
| JSON_THROW(invalid_iterator::create(210, "iterators do not fit")); |
| } |
| |
| 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); |
| result.m_it.array_iterator = m_value.array->insert( |
| pos.m_it.array_iterator, |
| first.m_it.array_iterator, |
| last.m_it.array_iterator); |
| return result; |
| } |
| |
| json::iterator json::insert(const_iterator pos, initializer_list_t ilist) |
| { |
| // insert only works for arrays |
| if (JSON_UNLIKELY(not is_array())) |
| { |
| JSON_THROW(type_error::create(309, "cannot use insert() with " + Twine(type_name()))); |
| } |
| |
| // check if iterator pos fits to this JSON value |
| 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); |
| 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 (JSON_UNLIKELY(not is_object())) |
| { |
| JSON_THROW(type_error::create(309, "cannot use insert() 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())) |
| { |
| JSON_THROW(invalid_iterator::create(202, "iterators first and last must point to objects")); |
| } |
| |
| for (auto it = first.m_it.object_iterator, end = last.m_it.object_iterator; it != end; ++it) |
| { |
| m_value.object->insert(std::make_pair(it->first(), it->second)); |
| } |
| } |
| |
| void json::update(const_reference j) |
| { |
| // implicitly convert null value to an empty object |
| if (is_null()) |
| { |
| m_type = value_t::object; |
| m_value.object = create<object_t>(); |
| 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(); |
| } |
| } |
| |
| 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<object_t>(); |
| 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<double>(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<double>(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<double>(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<double>(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<int64_t>(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<int64_t>(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<double>(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<double>(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<double>(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<double>(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<int64_t>(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<int64_t>(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<size_type>(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<difference_type>(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<size_type>(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<difference_type>(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 |