diff options
Diffstat (limited to 'include/jsoncons_ext/jsonpath/flatten.hpp')
-rw-r--r-- | include/jsoncons_ext/jsonpath/flatten.hpp | 432 |
1 files changed, 432 insertions, 0 deletions
diff --git a/include/jsoncons_ext/jsonpath/flatten.hpp b/include/jsoncons_ext/jsonpath/flatten.hpp new file mode 100644 index 0000000..938391f --- /dev/null +++ b/include/jsoncons_ext/jsonpath/flatten.hpp @@ -0,0 +1,432 @@ +// Copyright 2021 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_JSONPATH_FLATTEN_HPP +#define JSONCONS_JSONPATH_FLATTEN_HPP + +#include <string> +#include <vector> +#include <memory> +#include <type_traits> // std::is_const +#include <limits> // std::numeric_limits +#include <utility> // std::move +#include <algorithm> // std::copy +#include <iterator> // std::back_inserter +#include <jsoncons_ext/jsonpath/jsonpath.hpp> + +namespace jsoncons { namespace jsonpath { + + template <class CharT, class Sink> + std::size_t escape_string(const CharT* s, std::size_t length, + Sink& sink) + { + std::size_t count = 0; + const CharT* begin = s; + const CharT* end = s + length; + for (const CharT* it = begin; it != end; ++it) + { + CharT c = *it; + switch (c) + { + case '\\': + sink.push_back('\\'); + sink.push_back('\\'); + count += 2; + break; + case '\'': + sink.push_back('\\'); + sink.push_back('\''); + count += 2; + break; + case '\b': + sink.push_back('\\'); + sink.push_back('b'); + count += 2; + break; + case '\f': + sink.push_back('\\'); + sink.push_back('f'); + count += 2; + break; + case '\n': + sink.push_back('\\'); + sink.push_back('n'); + count += 2; + break; + case '\r': + sink.push_back('\\'); + sink.push_back('r'); + count += 2; + break; + case '\t': + sink.push_back('\\'); + sink.push_back('t'); + count += 2; + break; + default: + sink.push_back(c); + ++count; + break; + } + } + return count; + } + + 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()) + { + 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); + key.push_back(']'); + flatten_(key, parent_value.at(i), result); + } + } + break; + } + + case json_type::object_value: + { + if (parent_value.empty()) + { + result.try_emplace(parent_key, Json()); + } + else + { + for (const auto& item : parent_value.object_range()) + { + string_type key(parent_key); + key.push_back('['); + key.push_back('\''); + escape_string(item.key().data(), item.key().length(), key); + key.push_back('\''); + key.push_back(']'); + flatten_(key, item.value(), result); + } + } + break; + } + + default: + { + result[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; + } + + enum class unflatten_state + { + start, + expect_lbracket, + lbracket, + single_quoted_name_state, + double_quoted_name_state, + index_state, + expect_rbracket, + double_quoted_string_escape_char, + single_quoted_string_escape_char + }; + + template<class Json> + Json unflatten(const Json& value) + { + using char_type = typename Json::char_type; + using string_type = std::basic_string<char_type>; + + if (JSONCONS_UNLIKELY(!value.is_object())) + { + JSONCONS_THROW(jsonpath_error(jsonpath_errc::argument_to_unflatten_invalid)); + } + + Json result; + + for (const auto& item : value.object_range()) + { + Json* part = &result; + string_type buffer; + unflatten_state state = unflatten_state::start; + + auto it = item.key().begin(); + auto last = item.key().end(); + + for (; it != last; ++it) + { + switch (state) + { + case unflatten_state::start: + { + switch (*it) + { + case '$': + state = unflatten_state::expect_lbracket; + break; + default: + break; + } + break; + } + case unflatten_state::expect_lbracket: + { + switch (*it) + { + case '[': + state = unflatten_state::lbracket; + break; + default: + JSONCONS_THROW(jsonpath_error(jsonpath_errc::invalid_flattened_key)); + break; + } + break; + } + case unflatten_state::lbracket: + { + switch (*it) + { + case '\'': + state = unflatten_state::single_quoted_name_state; + break; + case '\"': + state = unflatten_state::double_quoted_name_state; + break; + case '0':case '1':case '2':case '3':case '4':case '5':case '6':case '7':case '8':case '9': + buffer.push_back(*it); + state = unflatten_state::index_state; + break; + default: + JSONCONS_THROW(jsonpath_error(jsonpath_errc::invalid_flattened_key)); + break; + } + break; + } + case unflatten_state::single_quoted_name_state: + { + switch (*it) + { + case '\'': + if (it != last-2) + { + auto res = part->try_emplace(buffer,Json()); + part = &(res.first->value()); + } + else + { + auto res = part->try_emplace(buffer,item.value()); + part = &(res.first->value()); + } + buffer.clear(); + state = unflatten_state::expect_rbracket; + break; + case '\\': + state = unflatten_state::single_quoted_string_escape_char; + break; + default: + buffer.push_back(*it); + break; + } + break; + } + case unflatten_state::double_quoted_name_state: + { + switch (*it) + { + case '\"': + if (it != last-2) + { + auto res = part->try_emplace(buffer,Json()); + part = &(res.first->value()); + } + else + { + auto res = part->try_emplace(buffer,item.value()); + part = &(res.first->value()); + } + buffer.clear(); + state = unflatten_state::expect_rbracket; + break; + case '\\': + state = unflatten_state::double_quoted_string_escape_char; + break; + default: + buffer.push_back(*it); + break; + } + break; + } + case unflatten_state::double_quoted_string_escape_char: + switch (*it) + { + case '\"': + buffer.push_back('\"'); + state = unflatten_state::double_quoted_name_state; + break; + case '\\': + buffer.push_back('\\'); + state = unflatten_state::double_quoted_name_state; + break; + case '/': + buffer.push_back('/'); + state = unflatten_state::double_quoted_name_state; + break; + case 'b': + buffer.push_back('\b'); + state = unflatten_state::double_quoted_name_state; + break; + case 'f': + buffer.push_back('\f'); + state = unflatten_state::double_quoted_name_state; + break; + case 'n': + buffer.push_back('\n'); + state = unflatten_state::double_quoted_name_state; + break; + case 'r': + buffer.push_back('\r'); + state = unflatten_state::double_quoted_name_state; + break; + case 't': + buffer.push_back('\t'); + state = unflatten_state::double_quoted_name_state; + break; + default: + break; + } + break; + case unflatten_state::single_quoted_string_escape_char: + switch (*it) + { + case '\'': + buffer.push_back('\''); + state = unflatten_state::single_quoted_name_state; + break; + case '\\': + buffer.push_back('\\'); + state = unflatten_state::double_quoted_name_state; + break; + case '/': + buffer.push_back('/'); + state = unflatten_state::double_quoted_name_state; + break; + case 'b': + buffer.push_back('\b'); + state = unflatten_state::double_quoted_name_state; + break; + case 'f': + buffer.push_back('\f'); + state = unflatten_state::double_quoted_name_state; + break; + case 'n': + buffer.push_back('\n'); + state = unflatten_state::double_quoted_name_state; + break; + case 'r': + buffer.push_back('\r'); + state = unflatten_state::double_quoted_name_state; + break; + case 't': + buffer.push_back('\t'); + state = unflatten_state::double_quoted_name_state; + break; + default: + break; + } + break; + case unflatten_state::index_state: + { + switch (*it) + { + case ']': + { + std::size_t n{0}; + auto r = jsoncons::detail::to_integer(buffer.data(), buffer.size(), n); + if (r) + { + if (!part->is_array()) + { + *part = Json(json_array_arg); + } + if (it != last-1) + { + 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); + } + } + buffer.clear(); + state = unflatten_state::expect_lbracket; + break; + } + case '0':case '1':case '2':case '3':case '4':case '5':case '6':case '7':case '8':case '9': + buffer.push_back(*it); + break; + default: + JSONCONS_THROW(jsonpath_error(jsonpath_errc::invalid_flattened_key)); + break; + } + break; + } + case unflatten_state::expect_rbracket: + { + switch (*it) + { + case ']': + state = unflatten_state::expect_lbracket; + break; + default: + JSONCONS_THROW(jsonpath_error(jsonpath_errc::invalid_flattened_key)); + break; + } + break; + } + default: + JSONCONS_UNREACHABLE(); + break; + } + } + } + + return result; + } +}} + +#endif |