diff options
author | Richard <q@1bpm.net> | 2022-09-04 00:32:56 +0100 |
---|---|---|
committer | Richard <q@1bpm.net> | 2022-09-04 00:32:56 +0100 |
commit | 1d055261b4144dbf86b2658437015b15d4dd9bff (patch) | |
tree | 6049b19d1bf953a650383de1a5e438b8b82679f6 /include/jsoncons_ext/jsonpointer | |
download | csound-json-1d055261b4144dbf86b2658437015b15d4dd9bff.tar.gz csound-json-1d055261b4144dbf86b2658437015b15d4dd9bff.tar.bz2 csound-json-1d055261b4144dbf86b2658437015b15d4dd9bff.zip |
initial
Diffstat (limited to 'include/jsoncons_ext/jsonpointer')
-rw-r--r-- | include/jsoncons_ext/jsonpointer/jsonpointer.hpp | 1577 | ||||
-rw-r--r-- | include/jsoncons_ext/jsonpointer/jsonpointer_error.hpp | 119 |
2 files changed, 1696 insertions, 0 deletions
diff --git a/include/jsoncons_ext/jsonpointer/jsonpointer.hpp b/include/jsoncons_ext/jsonpointer/jsonpointer.hpp new file mode 100644 index 0000000..41e41e2 --- /dev/null +++ b/include/jsoncons_ext/jsonpointer/jsonpointer.hpp @@ -0,0 +1,1577 @@ +// Copyright 2017 Daniel Parker +// Distributed under the Boost license, Version 1.0. +// (See accompanying file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) + +// See https://github.com/danielaparker/jsoncons for latest version + +#ifndef JSONCONS_JSONPOINTER_JSONPOINTER_HPP +#define JSONCONS_JSONPOINTER_JSONPOINTER_HPP + +#include <string> +#include <vector> +#include <memory> +#include <iostream> +#include <iterator> +#include <utility> // std::move +#include <system_error> // system_error +#include <type_traits> // std::enable_if, std::true_type +#include <jsoncons/json.hpp> +#include <jsoncons_ext/jsonpointer/jsonpointer_error.hpp> +#include <jsoncons/detail/write_number.hpp> + +namespace jsoncons { namespace jsonpointer { + + namespace detail { + + enum class pointer_state + { + start, + escaped, + delim + }; + + } // namespace detail + + template <class CharT> + std::basic_string<CharT> escape_string(const std::basic_string<CharT>& s) + { + std::basic_string<CharT> result; + for (auto c : s) + { + switch (c) + { + case '~': + result.push_back('~'); + result.push_back('0'); + break; + case '/': + result.push_back('~'); + result.push_back('1'); + break; + default: + result.push_back(c); + break; + } + } + return result; + } + + // basic_json_pointer + + template <class CharT> + class basic_json_pointer + { + public: + // Member types + using char_type = CharT; + using string_type = std::basic_string<char_type>; + using string_view_type = jsoncons::basic_string_view<char_type>; + using const_iterator = typename std::vector<string_type>::const_iterator; + using iterator = const_iterator; + using const_reverse_iterator = typename std::vector<string_type>::const_reverse_iterator; + using reverse_iterator = const_reverse_iterator; + private: + std::vector<string_type> tokens_; + public: + // Constructors + basic_json_pointer() + { + } + + basic_json_pointer(const std::vector<string_type>& tokens) + : tokens_(tokens) + { + } + + basic_json_pointer(std::vector<string_type>&& tokens) + : tokens_(std::move(tokens)) + { + } + + explicit basic_json_pointer(const string_view_type& s) + { + std::error_code ec; + auto jp = parse(s, ec); + if (ec) + { + throw jsonpointer_error(ec); + } + tokens_ = std::move(jp.tokens_); + } + + explicit basic_json_pointer(const string_view_type& s, std::error_code& ec) + { + auto jp = parse(s, ec); + if (!ec) + { + tokens_ = std::move(jp.tokens_); + } + } + + basic_json_pointer(const basic_json_pointer&) = default; + + basic_json_pointer(basic_json_pointer&&) = default; + + static basic_json_pointer parse(const string_view_type& input, std::error_code& ec) + { + std::vector<string_type> tokens; + if (input.empty() || (input[0] == '#' && input.size() == 1)) + { + return basic_json_pointer<CharT>(); + } + + const char_type* p; + const char_type* pend; + string_type unescaped; + if (input[0] == '#') + { + unescaped = unescape_uri_string(input, ec); + p = unescaped.data() + 1; + pend = unescaped.data() + unescaped.size(); + } + else + { + p = input.data(); + pend = input.data() + input.size(); + } + + auto state = jsonpointer::detail::pointer_state::start; + string_type buffer; + + while (p < pend) + { + bool done = false; + while (p < pend && !done) + { + switch (state) + { + case jsonpointer::detail::pointer_state::start: + switch (*p) + { + case '/': + state = jsonpointer::detail::pointer_state::delim; + break; + default: + ec = jsonpointer_errc::expected_slash; + return basic_json_pointer(); + }; + break; + case jsonpointer::detail::pointer_state::delim: + switch (*p) + { + case '/': + done = true; + break; + case '~': + state = jsonpointer::detail::pointer_state::escaped; + break; + default: + buffer.push_back(*p); + break; + }; + break; + case jsonpointer::detail::pointer_state::escaped: + switch (*p) + { + case '0': + buffer.push_back('~'); + state = jsonpointer::detail::pointer_state::delim; + break; + case '1': + buffer.push_back('/'); + state = jsonpointer::detail::pointer_state::delim; + break; + default: + ec = jsonpointer_errc::expected_0_or_1; + return basic_json_pointer(); + }; + break; + } + ++p; + } + tokens.push_back(buffer); + buffer.clear(); + } + if (!buffer.empty()) + { + tokens.push_back(buffer); + } + return basic_json_pointer(tokens); + } + + static string_type escape_uri_string(const string_type& s) + { + string_type escaped; + for (auto ch : s) + { + switch (ch) + { + case '%': + escaped.append(string_type{'%','2','5'}); + break; + case '^': + escaped.append(string_type{'%','5','E'}); + break; + case '|': + escaped.append(string_type{'%','7','C'}); + break; + case '\\': + escaped.append(string_type{'%','5','C'}); + break; + case '\"': + escaped.append(string_type{'%','2','2'}); + break; + case ' ': + escaped.append(string_type{'%','2','0'}); + break; + default: + escaped.push_back(ch); + break; + } + } + + return escaped; + } + + static string_type unescape_uri_string(const string_view_type& s, std::error_code& ec) + { + if (s.size() < 3) + { + return string_type(s); + } + string_type unescaped; + std::size_t last = s.size() - 2; + std::size_t pos = 0; + while (pos < last) + { + if (s[pos] == '%') + { + uint8_t ch; + auto result = jsoncons::detail::to_integer_base16(s.data() + (pos+1), 2, ch); + if (!result) + { + ec = jsonpointer_errc::invalid_uri_escaped_data; + return string_type(s); + } + unescaped.push_back(ch); + pos += 3; + } + else + { + unescaped.push_back(s[pos]); + ++pos; + } + } + while (pos < s.size()) + { + unescaped.push_back(s[pos]); + ++pos; + } + return unescaped; + } + + // operator= + basic_json_pointer& operator=(const basic_json_pointer&) = default; + + basic_json_pointer& operator=(basic_json_pointer&&) = default; + + // Modifiers + + void clear() + { + tokens_.clear(); + } + + basic_json_pointer& operator/=(const string_type& s) + { + tokens_.push_back(s); + return *this; + } + + template <class IntegerType> + typename std::enable_if<type_traits::is_integer<IntegerType>::value, basic_json_pointer&>::type + operator/=(IntegerType val) + { + string_type s; + jsoncons::detail::from_integer(val, s); + tokens_.push_back(s); + + return *this; + } + + basic_json_pointer& operator+=(const basic_json_pointer& p) + { + for (const auto& s : p.tokens_) + { + tokens_.push_back(s); + } + return *this; + } + + // Accessors + bool empty() const + { + return tokens_.empty(); + } + +#if !defined(JSONCONS_NO_DEPRECATED) + + JSONCONS_DEPRECATED_MSG("Instead, use to_string()") + string_type string() const + { + return to_string(); + } +#endif + string_type to_string() const + { + string_type buffer; + for (const auto& token : tokens_) + { + buffer.push_back('/'); + for (auto c : token) + { + switch (c) + { + case '~': + buffer.push_back('~'); + buffer.push_back('0'); + break; + case '/': + buffer.push_back('~'); + buffer.push_back('1'); + break; + default: + buffer.push_back(c); + break; + } + } + } + return buffer; + } + + string_type to_uri_fragment() const + { + string_type buffer{'#'}; + for (const auto& token : tokens_) + { + buffer.push_back('/'); + string_type s = escape_uri_string(token); + for (auto c : s) + { + switch (c) + { + case '~': + buffer.push_back('~'); + buffer.push_back('0'); + break; + case '/': + buffer.push_back('~'); + buffer.push_back('1'); + break; + default: + buffer.push_back(c); + break; + } + } + } + return buffer; + } + + // Iterators + iterator begin() const + { + return tokens_.begin(); + } + iterator end() const + { + return tokens_.end(); + } + + reverse_iterator rbegin() const + { + return tokens_.rbegin(); + } + reverse_iterator rend() const + { + return tokens_.rend(); + } + + // Non-member functions + friend basic_json_pointer<CharT> operator/(const basic_json_pointer<CharT>& lhs, const string_type& rhs) + { + basic_json_pointer<CharT> p(lhs); + p /= rhs; + return p; + } + + friend basic_json_pointer<CharT> operator+( const basic_json_pointer<CharT>& lhs, const basic_json_pointer<CharT>& rhs ) + { + basic_json_pointer<CharT> p(lhs); + p += rhs; + return p; + } + + friend bool operator==( const basic_json_pointer& lhs, const basic_json_pointer& rhs ) + { + return lhs.tokens_ == rhs.okens_; + } + + friend bool operator!=( const basic_json_pointer& lhs, const basic_json_pointer& rhs ) + { + return lhs.tokens_ != rhs.tokens_; + } + + friend std::basic_ostream<CharT>& + operator<<( std::basic_ostream<CharT>& os, const basic_json_pointer<CharT>& p ) + { + os << p.to_string(); + return os; + } + }; + + template <class CharT,class IntegerType> + typename std::enable_if<type_traits::is_integer<IntegerType>::value, basic_json_pointer<CharT>>::type + operator/(const basic_json_pointer<CharT>& lhs, IntegerType rhs) + { + basic_json_pointer<CharT> p(lhs); + p /= rhs; + return p; + } + + using json_pointer = basic_json_pointer<char>; + using wjson_pointer = basic_json_pointer<wchar_t>; + + #if !defined(JSONCONS_NO_DEPRECATED) + template<class CharT> + using basic_address = basic_json_pointer<CharT>; + template<class CharT> + using basic_json_ptr = basic_json_pointer<CharT>; + JSONCONS_DEPRECATED_MSG("Instead, use json_pointer") typedef json_pointer address; + JSONCONS_DEPRECATED_MSG("Instead, use json_pointer") typedef json_pointer json_ptr; + JSONCONS_DEPRECATED_MSG("Instead, use wjson_pointer") typedef json_pointer wjson_ptr; + #endif + + namespace detail { + + template <class Json> + const Json* resolve(const Json* current, const typename Json::string_view_type& buffer, std::error_code& ec) + { + if (current->is_array()) + { + if (buffer.size() == 1 && buffer[0] == '-') + { + ec = jsonpointer_errc::index_exceeds_array_size; + return current; + } + std::size_t index{0}; + auto result = jsoncons::detail::to_integer_decimal(buffer.data(), buffer.length(), index); + if (!result) + { + ec = jsonpointer_errc::invalid_index; + return current; + } + if (index >= current->size()) + { + ec = jsonpointer_errc::index_exceeds_array_size; + return current; + } + current = std::addressof(current->at(index)); + } + else if (current->is_object()) + { + if (!current->contains(buffer)) + { + ec = jsonpointer_errc::key_not_found; + return current; + } + current = std::addressof(current->at(buffer)); + } + else + { + ec = jsonpointer_errc::expected_object_or_array; + return current; + } + return current; + } + + template <class Json> + Json* resolve(Json* current, const typename Json::string_view_type& buffer, bool create_if_missing, std::error_code& ec) + { + if (current->is_array()) + { + if (buffer.size() == 1 && buffer[0] == '-') + { + ec = jsonpointer_errc::index_exceeds_array_size; + return current; + } + std::size_t index{0}; + auto result = jsoncons::detail::to_integer_decimal(buffer.data(), buffer.length(), index); + if (!result) + { + ec = jsonpointer_errc::invalid_index; + return current; + } + if (index >= current->size()) + { + ec = jsonpointer_errc::index_exceeds_array_size; + return current; + } + current = std::addressof(current->at(index)); + } + else if (current->is_object()) + { + if (!current->contains(buffer)) + { + if (create_if_missing) + { + auto r = current->try_emplace(buffer, Json()); + current = std::addressof(r.first->value()); + } + else + { + ec = jsonpointer_errc::key_not_found; + return current; + } + } + else + { + current = std::addressof(current->at(buffer)); + } + } + else + { + ec = jsonpointer_errc::expected_object_or_array; + return current; + } + return current; + } + + } // namespace detail + + // get + + template<class Json> + Json& get(Json& root, + const basic_json_pointer<typename Json::char_type>& location, + bool create_if_missing, + std::error_code& ec) + { + if (location.empty()) + { + return root; + } + + Json* current = std::addressof(root); + auto it = location.begin(); + auto end = location.end(); + while (it != end) + { + current = jsoncons::jsonpointer::detail::resolve(current, *it, create_if_missing, ec); + if (ec) + return *current; + ++it; + } + return *current; + } + + template<class Json, class StringSource> + typename std::enable_if<std::is_convertible<StringSource,jsoncons::basic_string_view<typename Json::char_type>>::value,Json&>::type + get(Json& root, + const StringSource& location_str, + bool create_if_missing, + std::error_code& ec) + { + auto jsonptr = basic_json_pointer<typename Json::char_type>::parse(location_str, ec); + if (ec) + { + return root; + } + return get(root, jsonptr, create_if_missing, ec); + } + + template<class Json> + const Json& get(const Json& root, + const basic_json_pointer<typename Json::char_type>& location, + std::error_code& ec) + { + if (location.empty()) + { + return root; + } + + const Json* current = std::addressof(root); + auto it = location.begin(); + auto end = location.end(); + while (it != end) + { + current = jsoncons::jsonpointer::detail::resolve(current, *it, ec); + if (ec) + return *current; + ++it; + } + return *current; + } + + template<class Json, class StringSource> + typename std::enable_if<std::is_convertible<StringSource,jsoncons::basic_string_view<typename Json::char_type>>::value,const Json&>::type + get(const Json& root, + const StringSource& location_str, + std::error_code& ec) + { + auto jsonptr = basic_json_pointer<typename Json::char_type>::parse(location_str, ec); + if (ec) + { + return root; + } + return get(root, jsonptr, ec); + } + + template<class Json> + Json& get(Json& root, + const basic_json_pointer<typename Json::char_type>& location, + std::error_code& ec) + { + return get(root, location, false, ec); + } + + template<class Json, class StringSource> + typename std::enable_if<std::is_convertible<StringSource,jsoncons::basic_string_view<typename Json::char_type>>::value,Json&>::type + get(Json& root, + const StringSource& location_str, + std::error_code& ec) + { + return get(root, location_str, false, ec); + } + + template<class Json> + Json& get(Json& root, + const basic_json_pointer<typename Json::char_type>& location, + bool create_if_missing = false) + { + std::error_code ec; + Json& j = get(root, location, create_if_missing, ec); + if (ec) + { + JSONCONS_THROW(jsonpointer_error(ec)); + } + return j; + } + + template<class Json, class StringSource> + typename std::enable_if<std::is_convertible<StringSource,jsoncons::basic_string_view<typename Json::char_type>>::value,Json&>::type + get(Json& root, + const StringSource& location_str, + bool create_if_missing = false) + { + std::error_code ec; + Json& result = get(root, location_str, create_if_missing, ec); + if (ec) + { + JSONCONS_THROW(jsonpointer_error(ec)); + } + return result; + } + + template<class Json> + const Json& get(const Json& root, const basic_json_pointer<typename Json::char_type>& location) + { + std::error_code ec; + const Json& j = get(root, location, ec); + if (ec) + { + JSONCONS_THROW(jsonpointer_error(ec)); + } + return j; + } + + template<class Json, class StringSource> + typename std::enable_if<std::is_convertible<StringSource,jsoncons::basic_string_view<typename Json::char_type>>::value,const Json&>::type + get(const Json& root, const StringSource& location_str) + { + std::error_code ec; + const Json& j = get(root, location_str, ec); + if (ec) + { + JSONCONS_THROW(jsonpointer_error(ec)); + } + return j; + } + + // contains + + template<class Json> + bool contains(const Json& root, const basic_json_pointer<typename Json::char_type>& location) + { + std::error_code ec; + get(root, location, ec); + return !ec ? true : false; + } + + template<class Json, class StringSource> + typename std::enable_if<std::is_convertible<StringSource,jsoncons::basic_string_view<typename Json::char_type>>::value,bool>::type + contains(const Json& root, const StringSource& location_str) + { + std::error_code ec; + get(root, location_str, ec); + return !ec ? true : false; + } + + template<class Json,class T> + void add(Json& root, + const basic_json_pointer<typename Json::char_type>& location, + T&& value, + bool create_if_missing, + std::error_code& ec) + { + Json* current = std::addressof(root); + + std::basic_string<typename Json::char_type> buffer; + auto it = location.begin(); + auto end = location.end(); + while (it != end) + { + buffer = *it; + ++it; + if (it != end) + { + current = jsoncons::jsonpointer::detail::resolve(current, buffer, create_if_missing, ec); + if (ec) + return; + } + } + if (current->is_array()) + { + if (buffer.size() == 1 && buffer[0] == '-') + { + current->emplace_back(std::forward<T>(value)); + current = std::addressof(current->at(current->size()-1)); + } + else + { + std::size_t index{0}; + auto result = jsoncons::detail::to_integer_decimal(buffer.data(), buffer.length(), index); + if (!result) + { + ec = jsonpointer_errc::invalid_index; + return; + } + if (index > current->size()) + { + ec = jsonpointer_errc::index_exceeds_array_size; + return; + } + if (index == current->size()) + { + current->emplace_back(std::forward<T>(value)); + current = std::addressof(current->at(current->size()-1)); + } + else + { + auto it2 = current->insert(current->array_range().begin()+index,std::forward<T>(value)); + current = std::addressof(*it2); + } + } + } + else if (current->is_object()) + { + auto r = current->insert_or_assign(buffer,std::forward<T>(value)); + current = std::addressof(r.first->value()); + } + else + { + ec = jsonpointer_errc::expected_object_or_array; + return; + } + } + + // add + template<class Json, class StringSource, class T> + typename std::enable_if<std::is_convertible<StringSource,jsoncons::basic_string_view<typename Json::char_type>>::value,void>::type + add(Json& root, + const StringSource& location_str, + T&& value, + bool create_if_missing, + std::error_code& ec) + { + auto jsonptr = basic_json_pointer<typename Json::char_type>::parse(location_str, ec); + if (ec) + { + return; + } + add(root, jsonptr, std::forward<T>(value), create_if_missing, ec); + } + + template<class Json,class T> + void add(Json& root, + const basic_json_pointer<typename Json::char_type>& location, + T&& value, + std::error_code& ec) + { + add(root, location, std::forward<T>(value), false, ec); + } + + template<class Json, class StringSource, class T> + typename std::enable_if<std::is_convertible<StringSource,jsoncons::basic_string_view<typename Json::char_type>>::value,void>::type + add(Json& root, + const StringSource& location_str, + T&& value, + std::error_code& ec) + { + add(root, location_str, std::forward<T>(value), false, ec); + } + + template<class Json,class T> + void add(Json& root, + const basic_json_pointer<typename Json::char_type>& location, + T&& value, + bool create_if_missing = false) + { + std::error_code ec; + add(root, location, std::forward<T>(value), create_if_missing, ec); + if (ec) + { + JSONCONS_THROW(jsonpointer_error(ec)); + } + } + + template<class Json, class StringSource, class T> + typename std::enable_if<std::is_convertible<StringSource,jsoncons::basic_string_view<typename Json::char_type>>::value,void>::type + add(Json& root, + const StringSource& location_str, + T&& value, + bool create_if_missing = false) + { + std::error_code ec; + add(root, location_str, std::forward<T>(value), create_if_missing, ec); + if (ec) + { + JSONCONS_THROW(jsonpointer_error(ec)); + } + } + + // add_if_absent + + template<class Json, class T> + void add_if_absent(Json& root, + const basic_json_pointer<typename Json::char_type>& location, + T&& value, + bool create_if_missing, + std::error_code& ec) + { + Json* current = std::addressof(root); + + std::basic_string<typename Json::char_type> buffer; + auto it = location.begin(); + auto end = location.end(); + + while (it != end) + { + buffer = *it; + ++it; + if (it != end) + { + current = jsoncons::jsonpointer::detail::resolve(current, buffer, create_if_missing, ec); + if (ec) + return; + } + } + if (current->is_array()) + { + if (buffer.size() == 1 && buffer[0] == '-') + { + current->emplace_back(std::forward<T>(value)); + current = std::addressof(current->at(current->size()-1)); + } + else + { + std::size_t index{0}; + auto result = jsoncons::detail::to_integer_decimal(buffer.data(), buffer.length(), index); + if (!result) + { + ec = jsonpointer_errc::invalid_index; + return; + } + if (index > current->size()) + { + ec = jsonpointer_errc::index_exceeds_array_size; + return; + } + if (index == current->size()) + { + current->emplace_back(std::forward<T>(value)); + current = std::addressof(current->at(current->size()-1)); + } + else + { + auto it2 = current->insert(current->array_range().begin()+index,std::forward<T>(value)); + current = std::addressof(*it2); + } + } + } + else if (current->is_object()) + { + if (current->contains(buffer)) + { + ec = jsonpointer_errc::key_already_exists; + return; + } + else + { + auto r = current->try_emplace(buffer,std::forward<T>(value)); + current = std::addressof(r.first->value()); + } + } + else + { + ec = jsonpointer_errc::expected_object_or_array; + return; + } + } + + template<class Json, class StringSource, class T> + typename std::enable_if<std::is_convertible<StringSource,jsoncons::basic_string_view<typename Json::char_type>>::value,void>::type + add_if_absent(Json& root, + const StringSource& location_str, + T&& value, + bool create_if_missing, + std::error_code& ec) + { + auto jsonptr = basic_json_pointer<typename Json::char_type>::parse(location_str, ec); + if (ec) + { + return; + } + add_if_absent(root, jsonptr, std::forward<T>(value), create_if_missing, ec); + } + + template<class Json, class StringSource, class T> + typename std::enable_if<std::is_convertible<StringSource,jsoncons::basic_string_view<typename Json::char_type>>::value,void>::type + add_if_absent(Json& root, + const StringSource& location, + T&& value, + std::error_code& ec) + { + add_if_absent(root, location, std::forward<T>(value), false, ec); + } + + template<class Json, class StringSource, class T> + typename std::enable_if<std::is_convertible<StringSource,jsoncons::basic_string_view<typename Json::char_type>>::value,void>::type + add_if_absent(Json& root, + const StringSource& location_str, + T&& value, + bool create_if_missing = false) + { + std::error_code ec; + add_if_absent(root, location_str, std::forward<T>(value), create_if_missing, ec); + if (ec) + { + JSONCONS_THROW(jsonpointer_error(ec)); + } + } + + template<class Json, class T> + void add_if_absent(Json& root, + const basic_json_pointer<typename Json::char_type>& location, + T&& value, + std::error_code& ec) + { + add_if_absent(root, location, std::forward<T>(value), false, ec); + } + + template<class Json, class T> + void add_if_absent(Json& root, + const basic_json_pointer<typename Json::char_type>& location, + T&& value, + bool create_if_missing = false) + { + std::error_code ec; + add_if_absent(root, location, std::forward<T>(value), create_if_missing, ec); + if (ec) + { + JSONCONS_THROW(jsonpointer_error(ec)); + } + } + + // remove + + template<class Json> + void remove(Json& root, const basic_json_pointer<typename Json::char_type>& location, std::error_code& ec) + { + Json* current = std::addressof(root); + + std::basic_string<typename Json::char_type> buffer; + auto it = location.begin(); + auto end = location.end(); + + while (it != end) + { + buffer = *it; + ++it; + if (it != end) + { + current = jsoncons::jsonpointer::detail::resolve(current, buffer, false, ec); + if (ec) + return; + } + } + if (current->is_array()) + { + if (buffer.size() == 1 && buffer[0] == '-') + { + ec = jsonpointer_errc::index_exceeds_array_size; + return; + } + else + { + std::size_t index{0}; + auto result = jsoncons::detail::to_integer_decimal(buffer.data(), buffer.length(), index); + if (!result) + { + ec = jsonpointer_errc::invalid_index; + return; + } + if (index >= current->size()) + { + ec = jsonpointer_errc::index_exceeds_array_size; + return; + } + current->erase(current->array_range().begin()+index); + } + } + else if (current->is_object()) + { + if (!current->contains(buffer)) + { + ec = jsonpointer_errc::key_not_found; + return; + } + else + { + current->erase(buffer); + } + } + else + { + ec = jsonpointer_errc::expected_object_or_array; + return; + } + } + + template<class Json, class StringSource> + typename std::enable_if<std::is_convertible<StringSource,jsoncons::basic_string_view<typename Json::char_type>>::value,void>::type + remove(Json& root, const StringSource& location_str, std::error_code& ec) + { + auto jsonptr = basic_json_pointer<typename Json::char_type>::parse(location_str, ec); + if (ec) + { + return; + } + remove(root, jsonptr, ec); + } + + template<class Json, class StringSource> + typename std::enable_if<std::is_convertible<StringSource,jsoncons::basic_string_view<typename Json::char_type>>::value,void>::type + remove(Json& root, const StringSource& location_str) + { + std::error_code ec; + remove(root, location_str, ec); + if (ec) + { + JSONCONS_THROW(jsonpointer_error(ec)); + } + } + + template<class Json> + void remove(Json& root, const basic_json_pointer<typename Json::char_type>& location) + { + std::error_code ec; + remove(root, location, ec); + if (ec) + { + JSONCONS_THROW(jsonpointer_error(ec)); + } + } + + // replace + + template<class Json, class T> + void replace(Json& root, + const basic_json_pointer<typename Json::char_type>& location, + T&& value, + bool create_if_missing, + std::error_code& ec) + { + Json* current = std::addressof(root); + + std::basic_string<typename Json::char_type> buffer; + auto it = location.begin(); + auto end = location.end(); + + while (it != end) + { + buffer = *it; + ++it; + if (it != end) + { + current = jsoncons::jsonpointer::detail::resolve(current, buffer, create_if_missing, ec); + if (ec) + return; + } + } + if (current->is_array()) + { + if (buffer.size() == 1 && buffer[0] == '-') + { + ec = jsonpointer_errc::index_exceeds_array_size; + return; + } + else + { + std::size_t index{}; + auto result = jsoncons::detail::to_integer_decimal(buffer.data(), buffer.length(), index); + if (!result) + { + ec = jsonpointer_errc::invalid_index; + return; + } + if (index >= current->size()) + { + ec = jsonpointer_errc::index_exceeds_array_size; + return; + } + current->at(index) = std::forward<T>(value); + } + } + else if (current->is_object()) + { + if (!current->contains(buffer)) + { + if (create_if_missing) + { + current->try_emplace(buffer,std::forward<T>(value)); + } + else + { + ec = jsonpointer_errc::key_not_found; + return; + } + } + else + { + auto r = current->insert_or_assign(buffer,std::forward<T>(value)); + current = std::addressof(r.first->value()); + } + } + else + { + ec = jsonpointer_errc::expected_object_or_array; + return; + } + } + + template<class Json, class StringSource, class T> + typename std::enable_if<std::is_convertible<StringSource,jsoncons::basic_string_view<typename Json::char_type>>::value,void>::type + replace(Json& root, + const StringSource& location_str, + T&& value, + bool create_if_missing, + std::error_code& ec) + { + auto jsonptr = basic_json_pointer<typename Json::char_type>::parse(location_str, ec); + if (ec) + { + return; + } + replace(root, jsonptr, std::forward<T>(value), create_if_missing, ec); + } + + template<class Json, class StringSource, class T> + typename std::enable_if<std::is_convertible<StringSource,jsoncons::basic_string_view<typename Json::char_type>>::value,void>::type + replace(Json& root, + const StringSource& location_str, + T&& value, + std::error_code& ec) + { + replace(root, location_str, std::forward<T>(value), false, ec); + } + + template<class Json, class StringSource, class T> + typename std::enable_if<std::is_convertible<StringSource,jsoncons::basic_string_view<typename Json::char_type>>::value,void>::type + replace(Json& root, + const StringSource& location_str, + T&& value, + bool create_if_missing = false) + { + std::error_code ec; + replace(root, location_str, std::forward<T>(value), create_if_missing, ec); + if (ec) + { + JSONCONS_THROW(jsonpointer_error(ec)); + } + } + + template<class Json, class T> + void replace(Json& root, + const basic_json_pointer<typename Json::char_type>& location, + T&& value, + std::error_code& ec) + { + replace(root, location, std::forward<T>(value), false, ec); + } + + template<class Json, class T> + void replace(Json& root, + const basic_json_pointer<typename Json::char_type>& location, + T&& value, + bool create_if_missing = false) + { + std::error_code ec; + replace(root, location, std::forward<T>(value), create_if_missing, ec); + if (ec) + { + JSONCONS_THROW(jsonpointer_error(ec)); + } + } + + template <class String,class Result> + typename std::enable_if<std::is_convertible<typename String::value_type,typename Result::value_type>::value>::type + escape(const String& s, Result& result) + { + for (auto c : s) + { + if (c == '~') + { + result.push_back('~'); + result.push_back('0'); + } + else if (c == '/') + { + result.push_back('~'); + result.push_back('1'); + } + else + { + result.push_back(c); + } + } + } + + template <class CharT> + std::basic_string<CharT> escape(const jsoncons::basic_string_view<CharT>& s) + { + std::basic_string<CharT> result; + + for (auto c : s) + { + if (c == '~') + { + result.push_back('~'); + result.push_back('0'); + } + else if (c == '/') + { + result.push_back('~'); + result.push_back('1'); + } + else + { + result.push_back(c); + } + } + return result; + } + + // flatten + + template<class Json> + void flatten_(const std::basic_string<typename Json::char_type>& parent_key, + const Json& parent_value, + Json& result) + { + using char_type = typename Json::char_type; + using string_type = std::basic_string<char_type>; + + switch (parent_value.type()) + { + case json_type::array_value: + { + if (parent_value.empty()) + { + // Flatten empty array to null + //result.try_emplace(parent_key, null_type{}); + //result[parent_key] = parent_value; + result.try_emplace(parent_key, parent_value); + } + else + { + for (std::size_t i = 0; i < parent_value.size(); ++i) + { + string_type key(parent_key); + key.push_back('/'); + jsoncons::detail::from_integer(i,key); + flatten_(key, parent_value.at(i), result); + } + } + break; + } + + case json_type::object_value: + { + if (parent_value.empty()) + { + // Flatten empty object to null + //result.try_emplace(parent_key, null_type{}); + //result[parent_key] = parent_value; + result.try_emplace(parent_key, parent_value); + } + else + { + for (const auto& item : parent_value.object_range()) + { + string_type key(parent_key); + key.push_back('/'); + escape(jsoncons::basic_string_view<char_type>(item.key().data(),item.key().size()), key); + flatten_(key, item.value(), result); + } + } + break; + } + + default: + { + // add primitive parent_value with its reference string + //result[parent_key] = parent_value; + result.try_emplace(parent_key, parent_value); + break; + } + } + } + + template<class Json> + Json flatten(const Json& value) + { + Json result; + std::basic_string<typename Json::char_type> parent_key; + flatten_(parent_key, value, result); + return result; + } + + + // unflatten + + enum class unflatten_options {none,assume_object = 1 + #if !defined(JSONCONS_NO_DEPRECATED) +,object = assume_object +#endif +}; + + template<class Json> + Json safe_unflatten (Json& value) + { + if (!value.is_object() || value.empty()) + { + return value; + } + bool safe = true; + std::size_t index = 0; + for (const auto& item : value.object_range()) + { + std::size_t n; + auto r = jsoncons::detail::to_integer_decimal(item.key().data(),item.key().size(), n); + if (!r || (index++ != n)) + { + safe = false; + break; + } + } + + if (safe) + { + Json j(json_array_arg); + j.reserve(value.size()); + for (auto& item : value.object_range()) + { + j.emplace_back(std::move(item.value())); + } + Json a(json_array_arg); + for (auto& item : j.array_range()) + { + a.emplace_back(safe_unflatten (item)); + } + return a; + } + else + { + Json o(json_object_arg); + for (auto& item : value.object_range()) + { + o.try_emplace(item.key(), safe_unflatten (item.value())); + } + return o; + } + } + + template<class Json> + jsoncons::optional<Json> try_unflatten_array(const Json& value) + { + using char_type = typename Json::char_type; + + if (JSONCONS_UNLIKELY(!value.is_object())) + { + JSONCONS_THROW(jsonpointer_error(jsonpointer_errc::argument_to_unflatten_invalid)); + } + Json result; + + for (const auto& item: value.object_range()) + { + Json* part = &result; + basic_json_pointer<char_type> ptr(item.key()); + std::size_t index = 0; + for (auto it = ptr.begin(); it != ptr.end(); ) + { + auto s = *it; + size_t n{0}; + auto r = jsoncons::detail::to_integer_decimal(s.data(), s.size(), n); + if (r.ec == jsoncons::detail::to_integer_errc() && (index++ == n)) + { + if (!part->is_array()) + { + *part = Json(json_array_arg); + } + if (++it != ptr.end()) + { + if (n+1 > part->size()) + { + Json& ref = part->emplace_back(); + part = std::addressof(ref); + } + else + { + part = &part->at(n); + } + } + else + { + Json& ref = part->emplace_back(item.value()); + part = std::addressof(ref); + } + } + else if (part->is_object()) + { + if (++it != ptr.end()) + { + auto res = part->try_emplace(s,Json()); + part = &(res.first->value()); + } + else + { + auto res = part->try_emplace(s, item.value()); + part = &(res.first->value()); + } + } + else + { + return jsoncons::optional<Json>(); + } + } + } + + return result; + } + + template<class Json> + Json unflatten_to_object(const Json& value, unflatten_options options = unflatten_options::none) + { + using char_type = typename Json::char_type; + + if (JSONCONS_UNLIKELY(!value.is_object())) + { + JSONCONS_THROW(jsonpointer_error(jsonpointer_errc::argument_to_unflatten_invalid)); + } + Json result; + + for (const auto& item: value.object_range()) + { + Json* part = &result; + basic_json_pointer<char_type> ptr(item.key()); + for (auto it = ptr.begin(); it != ptr.end(); ) + { + auto s = *it; + if (++it != ptr.end()) + { + auto res = part->try_emplace(s,Json()); + part = &(res.first->value()); + } + else + { + auto res = part->try_emplace(s, item.value()); + part = &(res.first->value()); + } + } + } + + return options == unflatten_options::none ? safe_unflatten (result) : result; + } + + template<class Json> + Json unflatten(const Json& value, unflatten_options options = unflatten_options::none) + { + if (options == unflatten_options::none) + { + jsoncons::optional<Json> j = try_unflatten_array(value); + return j ? *j : unflatten_to_object(value,options); + } + else + { + return unflatten_to_object(value,options); + } + } + +#if !defined(JSONCONS_NO_DEPRECATED) + + template<class Json> + JSONCONS_DEPRECATED_MSG("Instead, use add(Json&, const typename Json::string_view_type&, const Json&)") + void insert_or_assign(Json& root, const std::basic_string<typename Json::char_type>& location, const Json& value) + { + add(root, location, value); + } + + template<class Json> + JSONCONS_DEPRECATED_MSG("Instead, use add(Json&, const typename Json::string_view_type&, const Json&, std::error_code&)") + void insert_or_assign(Json& root, const std::basic_string<typename Json::char_type>& location, const Json& value, std::error_code& ec) + { + add(root, location, value, ec); + } + template<class Json, class T> + void insert(Json& root, + const std::basic_string<typename Json::char_type>& location, + T&& value, + bool create_if_missing, + std::error_code& ec) + { + add_if_absent(root,location,std::forward<T>(value),create_if_missing,ec); + } + + template<class Json, class T> + void insert(Json& root, + const std::basic_string<typename Json::char_type>& location, + T&& value, + std::error_code& ec) + { + add_if_absent(root, location, std::forward<T>(value), ec); + } + + template<class Json, class T> + void insert(Json& root, + const std::basic_string<typename Json::char_type>& location, + T&& value, + bool create_if_missing = false) + { + add_if_absent(root, location, std::forward<T>(value), create_if_missing); + } +#endif + +} // namespace jsonpointer +} // namespace jsoncons + +#endif diff --git a/include/jsoncons_ext/jsonpointer/jsonpointer_error.hpp b/include/jsoncons_ext/jsonpointer/jsonpointer_error.hpp new file mode 100644 index 0000000..a0cfeff --- /dev/null +++ b/include/jsoncons_ext/jsonpointer/jsonpointer_error.hpp @@ -0,0 +1,119 @@ +// Copyright 2013 Daniel Parker +// Distributed under the Boost license, Version 1.0. +// (See accompanying file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) + +// See https://github.com/danielaparker/jsoncons for latest version + +#ifndef JSONCONS_JSONPOINTER_JSONPOINTER_ERROR_HPP +#define JSONCONS_JSONPOINTER_JSONPOINTER_ERROR_HPP + +#include <jsoncons/json_exception.hpp> +#include <system_error> + +namespace jsoncons { namespace jsonpointer { + +class jsonpointer_error : public std::system_error, public virtual json_exception +{ +public: + jsonpointer_error(const std::error_code& ec) + : std::system_error(ec) + { + } + jsonpointer_error(const std::error_code& ec, const std::string& what_arg) + : std::system_error(ec, what_arg) + { + } + jsonpointer_error(const std::error_code& ec, const char* what_arg) + : std::system_error(ec, what_arg) + { + } + jsonpointer_error(const jsonpointer_error& other) = default; + + jsonpointer_error(jsonpointer_error&& other) = default; + + const char* what() const noexcept override + { + return std::system_error::what(); + } +}; + +enum class jsonpointer_errc +{ + success = 0, + expected_slash = 1, + index_exceeds_array_size, + expected_0_or_1, + invalid_index, + key_not_found, + key_already_exists, + expected_object_or_array, + end_of_input, + unexpected_end_of_input, + argument_to_unflatten_invalid, + invalid_flattened_key, + invalid_uri_escaped_data +}; + +class jsonpointer_error_category_impl + : public std::error_category +{ +public: + const char* name() const noexcept override + { + return "jsoncons/jsonpointer"; + } + std::string message(int ev) const override + { + switch (static_cast<jsonpointer_errc>(ev)) + { + case jsonpointer_errc::expected_slash: + return "Expected /"; + case jsonpointer_errc::index_exceeds_array_size: + return "Index exceeds array size"; + case jsonpointer_errc::expected_0_or_1: + return "Expected '0' or '1' after escape character '~'"; + case jsonpointer_errc::key_not_found: + return "Key not found"; + case jsonpointer_errc::invalid_index: + return "Invalid array index"; + case jsonpointer_errc::key_already_exists: + return "Key already exists"; + case jsonpointer_errc::expected_object_or_array: + return "Expected object or array"; + case jsonpointer_errc::end_of_input: + return "Unexpected end of input"; + case jsonpointer_errc::unexpected_end_of_input: + return "Unexpected end of jsonpointer input"; + case jsonpointer_errc::argument_to_unflatten_invalid: + return "Argument to unflatten must be an object"; + case jsonpointer_errc::invalid_flattened_key: + return "Flattened key is invalid"; + default: + return "Unknown jsonpointer error"; + } + } +}; + +inline +const std::error_category& jsonpointer_error_category() +{ + static jsonpointer_error_category_impl instance; + return instance; +} + +inline +std::error_code make_error_code(jsonpointer_errc result) +{ + return std::error_code(static_cast<int>(result),jsonpointer_error_category()); +} + +}} + +namespace std { + template<> + struct is_error_code_enum<jsoncons::jsonpointer::jsonpointer_errc> : public true_type + { + }; +} + +#endif |