Squashed 'third_party/allwpilib_2019/' content from commit bd05dfa1c
Change-Id: I2b1c2250cdb9b055133780c33593292098c375b7
git-subtree-dir: third_party/allwpilib_2019
git-subtree-split: bd05dfa1c7cca74c4fac451e7b9d6a37e7b53447
diff --git a/wpiutil/src/main/native/cpp/json.cpp b/wpiutil/src/main/native/cpp/json.cpp
new file mode 100644
index 0000000..9becfdd
--- /dev/null
+++ b/wpiutil/src/main/native/cpp/json.cpp
@@ -0,0 +1,1493 @@
+/*----------------------------------------------------------------------------*/
+/* 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;
+ alloc.destroy(object);
+ alloc.deallocate(object, 1);
+ break;
+ }
+
+ case value_t::array:
+ {
+ std::allocator<array_t> alloc;
+ alloc.destroy(array);
+ alloc.deallocate(array, 1);
+ break;
+ }
+
+ case value_t::string:
+ {
+ std::allocator<std::string> alloc;
+ alloc.destroy(string);
+ alloc.deallocate(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<json>::max_size()
+ return std::allocator<json>().max_size();
+ }
+
+ 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