yalantinglibs/include/ylt/standalone/iguana/json_reader.hpp

842 lines
22 KiB
C++

#pragma once
#include "detail/utf.hpp"
#include "error_code.h"
#include "json_util.hpp"
namespace iguana {
template <typename T, typename It, std::enable_if_t<refletable_v<T>, int> = 0>
IGUANA_INLINE void from_json(T &value, It &&it, It &&end);
namespace detail {
template <typename U, typename It,
std::enable_if_t<sequence_container_v<U>, int> = 0>
IGUANA_INLINE void from_json_impl(U &value, It &&it, It &&end);
template <typename U, typename It, std::enable_if_t<smart_ptr_v<U>, int> = 0>
IGUANA_INLINE void from_json_impl(U &value, It &&it, It &&end);
template <typename U, typename It, std::enable_if_t<refletable_v<U>, int> = 0>
IGUANA_INLINE void from_json_impl(U &value, It &&it, It &&end) {
from_json(value, it, end);
}
template <typename U, typename It, std::enable_if_t<string_v<U>, int> = 0>
IGUANA_INLINE void parse_escape(U &value, It &&it, It &&end) {
if (it == end)
IGUANA_UNLIKELY { throw std::runtime_error(R"(Expected ")"); }
if (*it == 'u') {
++it;
if (std::distance(it, end) <= 4)
IGUANA_UNLIKELY {
throw std::runtime_error(R"(Expected 4 hexadecimal digits)");
}
auto code_point = parse_unicode_hex4(it);
encode_utf8(value, code_point);
}
else if (*it == 'n') {
++it;
value.push_back('\n');
}
else if (*it == 't') {
++it;
value.push_back('\t');
}
else if (*it == 'r') {
++it;
value.push_back('\r');
}
else if (*it == 'b') {
++it;
value.push_back('\b');
}
else if (*it == 'f') {
++it;
value.push_back('\f');
}
else {
value.push_back(*it); // add the escaped character
++it;
}
}
template <typename U, typename It, std::enable_if_t<num_v<U>, int> = 0>
IGUANA_INLINE void from_json_impl(U &value, It &&it, It &&end) {
skip_ws(it, end);
if constexpr (contiguous_iterator<std::decay_t<It>>) {
const auto size = std::distance(it, end);
if (size == 0)
IGUANA_UNLIKELY { throw std::runtime_error("Failed to parse number"); }
const auto start = &*it;
auto [p, ec] = detail::from_chars(start, start + size, value);
if (ec != std::errc{})
IGUANA_UNLIKELY { throw std::runtime_error("Failed to parse number"); }
it += (p - &*it);
}
else {
char buffer[256];
size_t i{};
while (it != end && is_numeric(*it)) {
if (i > 254)
IGUANA_UNLIKELY { throw std::runtime_error("Number is too long"); }
buffer[i] = *it++;
++i;
}
auto [p, ec] = detail::from_chars(buffer, buffer + i, value);
if (ec != std::errc{})
IGUANA_UNLIKELY { throw std::runtime_error("Failed to parse number"); }
}
}
template <typename U, typename It, std::enable_if_t<numeric_str_v<U>, int> = 0>
IGUANA_INLINE void from_json_impl(U &value, It &&it, It &&end) {
skip_ws(it, end);
auto start = it;
while (it != end && is_numeric(*it)) {
++it;
}
value.value() =
std::string_view(&*start, static_cast<size_t>(std::distance(start, it)));
}
template <bool skip = false, typename U, typename It,
std::enable_if_t<char_v<U>, int> = 0>
IGUANA_INLINE void from_json_impl(U &value, It &&it, It &&end) {
if constexpr (!skip) {
skip_ws(it, end);
match<'"'>(it, end);
}
if (it == end)
IGUANA_UNLIKELY { throw std::runtime_error("Unxpected end of buffer"); }
if (*it == '\\')
IGUANA_UNLIKELY {
if (++it == end)
IGUANA_UNLIKELY { throw std::runtime_error("Unxpected end of buffer"); }
else if (*it == 'n') {
value = '\n';
}
else if (*it == 't') {
value = '\t';
}
else if (*it == 'r') {
value = '\r';
}
else if (*it == 'b') {
value = '\b';
}
else if (*it == 'f') {
value = '\f';
}
else
IGUANA_UNLIKELY { value = *it; }
}
else {
value = *it;
}
++it;
if constexpr (!skip) {
match<'"'>(it, end);
}
}
template <typename U, typename It, std::enable_if_t<bool_v<U>, int> = 0>
IGUANA_INLINE void from_json_impl(U &&value, It &&it, It &&end) {
skip_ws(it, end);
if (it < end)
IGUANA_LIKELY {
switch (*it) {
case 't':
++it;
match<'r', 'u', 'e'>(it, end);
value = true;
break;
case 'f':
++it;
match<'a', 'l', 's', 'e'>(it, end);
value = false;
break;
IGUANA_UNLIKELY default
: throw std::runtime_error("Expected true or false");
}
}
else
IGUANA_UNLIKELY { throw std::runtime_error("Expected true or false"); }
}
template <bool skip = false, typename U, typename It,
std::enable_if_t<string_v<U>, int> = 0>
IGUANA_INLINE void from_json_impl(U &value, It &&it, It &&end) {
if constexpr (!skip) {
skip_ws(it, end);
match<'"'>(it, end);
}
value.clear();
if constexpr (contiguous_iterator<std::decay_t<It>>) {
auto start = it;
while (it < end) {
skip_till_escape_or_qoute(it, end);
if (*it == '"') {
value.append(&*start, static_cast<size_t>(std::distance(start, it)));
++it;
return;
}
else {
// Must be an escape
value.append(&*start, static_cast<size_t>(std::distance(start, it)));
++it; // skip first escape
parse_escape(value, it, end);
start = it;
}
}
}
else {
while (it != end) {
switch (*it) {
IGUANA_UNLIKELY case '\\' : ++it;
parse_escape(value, it, end);
break;
// IGUANA_UNLIKELY case ']' : return;
IGUANA_UNLIKELY case '"' : ++it;
return;
IGUANA_LIKELY default : value.push_back(*it);
++it;
}
}
}
}
template <bool skip = false, typename U, typename It,
std::enable_if_t<string_view_v<U>, int> = 0>
IGUANA_INLINE void from_json_impl(U &value, It &&it, It &&end) {
static_assert(contiguous_iterator<std::decay_t<It>>, "must be contiguous");
if constexpr (!skip) {
skip_ws(it, end);
match<'"'>(it, end);
}
using T = std::decay_t<U>;
auto start = it;
while (it != end) {
skip_till_qoute(it, end);
if (*(it - 1) != '\\') {
value = T(&*start, static_cast<size_t>(std::distance(start, it)));
++it;
return;
}
++it;
}
throw std::runtime_error("Expected \"");
}
template <typename U, typename It, std::enable_if_t<enum_v<U>, int> = 0>
IGUANA_INLINE void from_json_impl(U &value, It &&it, It &&end) {
static constexpr auto str_to_enum = get_enum_map<true, std::decay_t<U>>();
if constexpr (bool_v<decltype(str_to_enum)>) {
// not defined a specialization template
using T = std::underlying_type_t<std::decay_t<U>>;
from_json_impl(reinterpret_cast<T &>(value), it, end);
}
else {
std::string_view enum_names;
from_json_impl(enum_names, it, end);
auto it = str_to_enum.find(enum_names);
if (it != str_to_enum.end())
IGUANA_LIKELY { value = it->second; }
else {
throw std::runtime_error(std::string(enum_names) +
" missing corresponding value in enum_value");
}
}
}
template <typename U, typename It, std::enable_if_t<fixed_array_v<U>, int> = 0>
IGUANA_INLINE void from_json_impl(U &value, It &&it, It &&end) {
using T = std::remove_reference_t<U>;
constexpr auto n = sizeof(T) / sizeof(decltype(std::declval<T>()[0]));
skip_ws(it, end);
if constexpr (std::is_same_v<char, std::remove_reference_t<
decltype(std::declval<T>()[0])>>) {
if (*it == '"') {
match<'"'>(it, end);
auto value_it = std::begin(value);
for (size_t i = 0; i < n; ++i) {
if (*it != '"')
IGUANA_LIKELY { from_json_impl<true>(*value_it++, it, end); }
}
match<'"'>(it, end);
return;
}
}
match<'['>(it, end);
skip_ws(it, end);
if (it == end)
IGUANA_UNLIKELY { throw std::runtime_error("Unexpected end"); }
if (*it == ']')
IGUANA_UNLIKELY {
++it;
return;
}
auto value_it = std::begin(value);
for (size_t i = 0; i < n; ++i) {
from_json_impl(*value_it++, it, end);
skip_ws(it, end);
if (it == end) {
throw std::runtime_error("Unexpected end");
}
if (*it == ',')
IGUANA_LIKELY {
++it;
skip_ws(it, end);
}
else if (*it == ']') {
++it;
return;
}
else
IGUANA_UNLIKELY { throw std::runtime_error("Expected ]"); }
}
}
template <typename U, typename It,
std::enable_if_t<sequence_container_v<U>, int>>
IGUANA_INLINE void from_json_impl(U &value, It &&it, It &&end) {
value.clear();
skip_ws(it, end);
match<'['>(it, end);
skip_ws(it, end);
for (size_t i = 0; it != end; ++i) {
if (*it == ']')
IGUANA_UNLIKELY {
++it;
return;
}
if (i > 0)
IGUANA_LIKELY { match<','>(it, end); }
from_json_impl(value.emplace_back(), it, end);
skip_ws(it, end);
}
throw std::runtime_error("Expected ]");
}
template <typename It>
IGUANA_INLINE auto get_key(It &&it, It &&end) {
if constexpr (contiguous_iterator<std::decay_t<It>>) {
// skip white space and escape characters and find the string
skip_ws(it, end);
match<'"'>(it, end);
auto start = it;
skip_till_escape_or_qoute(it, end);
if (*it == '\\')
IGUANA_UNLIKELY {
// we dont' optimize this currently because it would increase binary
// size significantly with the complexity of generating escaped
// compile time versions of keys
it = start;
static thread_local std::string static_key{};
detail::from_json_impl<true>(static_key, it, end);
return std::string_view(static_key);
}
else
IGUANA_LIKELY {
auto key = std::string_view{
&*start, static_cast<size_t>(std::distance(start, it))};
++it;
if (key[0] == '@')
IGUANA_UNLIKELY { return key.substr(1); }
return key;
}
}
else {
static thread_local std::string static_key{};
detail::from_json_impl(static_key, it, end);
return std::string_view(static_key);
}
}
template <typename U, typename It,
std::enable_if_t<map_container_v<U>, int> = 0>
IGUANA_INLINE void from_json_impl(U &value, It &&it, It &&end) {
using T = std::remove_reference_t<U>;
using key_type = typename T::key_type;
skip_ws(it, end);
match<'{'>(it, end);
skip_ws(it, end);
bool first = true;
while (it != end) {
if (*it == '}')
IGUANA_UNLIKELY {
++it;
return;
}
else if (first)
IGUANA_UNLIKELY { first = false; }
else
IGUANA_LIKELY { match<','>(it, end); }
std::string_view key = get_key(it, end);
skip_ws(it, end);
match<':'>(it, end);
if constexpr (string_v<key_type> || string_view_v<key_type>) {
from_json_impl(value[key_type(key)], it, end);
}
else {
static thread_local key_type key_value{};
from_json_impl(key_value, key.begin(), key.end());
from_json_impl(value[key_value], it, end);
}
skip_ws(it, end);
}
}
template <typename U, typename It, std::enable_if_t<tuple_v<U>, int> = 0>
IGUANA_INLINE void from_json_impl(U &value, It &&it, It &&end) {
skip_ws(it, end);
match<'['>(it, end);
skip_ws(it, end);
for_each(value, [&](auto &v, auto i) IGUANA__INLINE_LAMBDA {
constexpr auto I = decltype(i)::value;
if (it == end || *it == ']') {
return;
}
if constexpr (I != 0) {
match<','>(it, end);
skip_ws(it, end);
}
from_json_impl(v, it, end);
skip_ws(it, end);
});
match<']'>(it, end);
}
template <typename U, typename It, std::enable_if_t<optional_v<U>, int> = 0>
IGUANA_INLINE void from_json_impl(U &value, It &&it, It &&end) {
skip_ws(it, end);
if (it < end && *it == '"')
IGUANA_LIKELY { ++it; }
using T = std::remove_reference_t<U>;
if (it == end)
IGUANA_UNLIKELY { throw std::runtime_error("Unexexpected eof"); }
if (*it == 'n') {
++it;
match<'u', 'l', 'l'>(it, end);
if constexpr (!std::is_pointer_v<T>) {
value.reset();
if (it < end && *it == '"') {
++it;
}
}
}
else {
using value_type = typename T::value_type;
value_type t;
if constexpr (string_v<value_type> || string_view_v<value_type>) {
from_json_impl<true>(t, it, end);
}
else {
from_json_impl(t, it, end);
}
value = std::move(t);
}
}
template <typename U, typename It, std::enable_if_t<smart_ptr_v<U>, int>>
IGUANA_INLINE void from_json_impl(U &value, It &&it, It &&end) {
skip_ws(it, end);
if (it == end)
IGUANA_UNLIKELY { throw std::runtime_error("Unexexpected eof"); }
if (*it == 'n') {
++it;
match<'u', 'l', 'l'>(it, end);
}
else {
using value_type = typename std::remove_reference_t<U>::element_type;
if constexpr (unique_ptr_v<U>) {
value = std::make_unique<value_type>();
}
else {
value = std::make_shared<value_type>();
}
from_json_impl(*value, it, end);
}
}
template <typename It>
IGUANA_INLINE void skip_object_value(It &&it, It &&end) {
skip_ws(it, end);
while (it != end) {
switch (*it) {
case '{':
skip_until_closed<'{', '}'>(it, end);
break;
case '[':
skip_until_closed<'[', ']'>(it, end);
break;
case '"':
skip_string(it, end);
break;
case '/':
skip_comment(it, end);
continue;
case ',':
case '}':
case ']':
break;
default: {
++it;
continue;
}
}
break;
}
}
} // namespace detail
template <typename T, typename It, std::enable_if_t<refletable_v<T>, int>>
IGUANA_INLINE void from_json(T &value, It &&it, It &&end) {
skip_ws(it, end);
match<'{'>(it, end);
skip_ws(it, end);
if (*it == '}')
IGUANA_UNLIKELY {
++it;
return;
}
std::string_view key = detail::get_key(it, end);
#ifdef SEQUENTIAL_PARSE
bool parse_done = false;
for_each(value, [&](const auto member_ptr, auto i) IGUANA__INLINE_LAMBDA {
constexpr auto mkey = iguana::get_name<T, decltype(i)::value>();
constexpr std::string_view st_key(mkey.data(), mkey.size());
if (parse_done || (key != st_key))
IGUANA_UNLIKELY { return; }
skip_ws(it, end);
match<':'>(it, end);
{
using namespace detail;
from_json_impl(value.*member_ptr, it, end);
}
skip_ws(it, end);
if (*it == '}')
IGUANA_UNLIKELY {
++it;
parse_done = true;
return;
}
else
IGUANA_LIKELY { match<','>(it, end); }
key = detail::get_key(it, end);
});
if (parse_done) [[unlikely]] {
return;
}
#endif
while (it != end) {
static constexpr auto frozen_map = get_iguana_struct_map<T>();
if constexpr (frozen_map.size() > 0) {
const auto &member_it = frozen_map.find(key);
skip_ws(it, end);
match<':'>(it, end);
if (member_it != frozen_map.end())
IGUANA_LIKELY {
std::visit(
[&](auto &&member_ptr) IGUANA__INLINE_LAMBDA {
using V = std::decay_t<decltype(member_ptr)>;
if constexpr (std::is_member_pointer_v<V>) {
using namespace detail;
from_json_impl(value.*member_ptr, it, end);
}
else {
static_assert(!sizeof(V), "type not supported");
}
},
member_it->second);
}
else
IGUANA_UNLIKELY {
#ifdef THROW_UNKNOWN_KEY
throw std::runtime_error("Unknown key: " + std::string(key));
#else
detail::skip_object_value(it, end);
#endif
}
}
skip_ws(it, end);
if (*it == '}')
IGUANA_UNLIKELY {
++it;
return;
}
else
IGUANA_LIKELY { match<','>(it, end); }
key = detail::get_key(it, end);
}
}
template <typename T, typename It,
std::enable_if_t<non_refletable_v<T>, int> = 0>
IGUANA_INLINE void from_json(T &value, It &&it, It &&end) {
using namespace detail;
from_json_impl(value, it, end);
}
template <typename T, typename It>
IGUANA_INLINE void from_json(T &value, It &&it, It &&end,
std::error_code &ec) noexcept {
try {
from_json(value, it, end);
ec = {};
} catch (std::runtime_error &e) {
ec = iguana::make_error_code(e.what());
}
}
template <typename T, typename View,
std::enable_if_t<json_view_v<View>, int> = 0>
IGUANA_INLINE void from_json(T &value, const View &view) {
from_json(value, std::begin(view), std::end(view));
}
template <typename T, typename View,
std::enable_if_t<json_view_v<View>, int> = 0>
IGUANA_INLINE void from_json(T &value, const View &view,
std::error_code &ec) noexcept {
try {
from_json(value, view);
ec = {};
} catch (std::runtime_error &e) {
ec = iguana::make_error_code(e.what());
}
}
template <typename T, typename Byte,
std::enable_if_t<json_byte_v<Byte>, int> = 0>
IGUANA_INLINE void from_json(T &value, const Byte *data, size_t size) {
std::string_view buffer(data, size);
from_json(value, buffer);
}
template <typename T, typename Byte,
std::enable_if_t<json_byte_v<Byte>, int> = 0>
IGUANA_INLINE void from_json(T &value, const Byte *data, size_t size,
std::error_code &ec) noexcept {
try {
from_json(value, data, size);
ec = {};
} catch (std::runtime_error &e) {
ec = iguana::make_error_code(e.what());
}
}
template <bool Is_view = false, typename It>
void parse(jvalue &result, It &&it, It &&end, bool parse_as_double = false);
template <bool Is_view = false, typename It>
inline void parse(jarray &result, It &&it, It &&end,
bool parse_as_double = false) {
skip_ws(it, end);
match<'['>(it, end);
if (*it == ']')
IGUANA_UNLIKELY {
++it;
return;
}
while (true) {
if (it == end) {
break;
}
result.emplace_back();
parse<Is_view>(result.back(), it, end, parse_as_double);
if (*it == ']')
IGUANA_UNLIKELY {
++it;
return;
}
match<','>(it, end);
}
throw std::runtime_error("Expected ]");
}
template <bool Is_view = false, typename It>
inline void parse(jobject &result, It &&it, It &&end,
bool parse_as_double = false) {
skip_ws(it, end);
match<'{'>(it, end);
if (*it == '}')
IGUANA_UNLIKELY {
++it;
return;
}
skip_ws(it, end);
while (true) {
if (it == end)
IGUANA_UNLIKELY { throw std::runtime_error("Expected }"); }
std::string key;
using namespace detail;
from_json_impl(key, it, end);
auto emplaced = result.try_emplace(key);
if (!emplaced.second)
throw std::runtime_error("duplicated key " + key);
match<':'>(it, end);
parse<Is_view>(emplaced.first->second, it, end, parse_as_double);
if (*it == '}')
IGUANA_UNLIKELY {
++it;
return;
}
match<','>(it, end);
}
}
template <bool Is_view, typename It>
inline void parse(jvalue &result, It &&it, It &&end, bool parse_as_double) {
using namespace detail;
skip_ws(it, end);
switch (*it) {
case 'n':
++it;
match<'u', 'l', 'l'>(it, end);
result.template emplace<std::nullptr_t>();
break;
case 'f':
case 't':
from_json_impl(result.template emplace<bool>(), it, end);
break;
case '0':
case '1':
case '2':
case '3':
case '4':
case '5':
case '6':
case '7':
case '8':
case '9':
case '-': {
double d{};
from_json_impl(d, it, end);
if (!parse_as_double && (static_cast<int>(d) == d))
result.emplace<int>(d);
else
result.emplace<double>(d);
break;
}
case '"':
if constexpr (Is_view) {
result.template emplace<std::string_view>();
from_json_impl(std::get<std::string_view>(result), it, end);
}
else {
result.template emplace<std::string>();
from_json_impl(std::get<std::string>(result), it, end);
}
break;
case '[':
result.template emplace<jarray>();
parse<Is_view>(std::get<jarray>(result), it, end, parse_as_double);
break;
case '{': {
result.template emplace<jobject>();
parse<Is_view>(std::get<jobject>(result), it, end, parse_as_double);
break;
}
default:
throw std::runtime_error("parse failed");
}
skip_ws(it, end);
}
// set Is_view == true, parse str as std::string_view
// set parse_as_double == true, parse the number as double in any case
template <bool Is_view = false, typename It>
inline void parse(jvalue &result, It &&it, It &&end, std::error_code &ec,
bool parse_as_double = false) {
try {
parse<Is_view>(result, it, end, parse_as_double);
ec = {};
} catch (const std::runtime_error &e) {
result.template emplace<std::nullptr_t>();
ec = iguana::make_error_code(e.what());
}
}
template <bool Is_view = false, typename T, typename View,
std::enable_if_t<json_view_v<View>, int> = 0>
inline void parse(T &result, const View &view, bool parse_as_double = false) {
parse<Is_view>(result, std::begin(view), std::end(view), parse_as_double);
}
template <bool Is_view = false, typename T, typename View,
std::enable_if_t<json_view_v<View>, int> = 0>
inline void parse(T &result, const View &view, std::error_code &ec,
bool parse_as_double = false) noexcept {
try {
parse<Is_view>(result, view, parse_as_double);
ec = {};
} catch (std::runtime_error &e) {
ec = iguana::make_error_code(e.what());
}
}
IGUANA_INLINE std::string json_file_content(const std::string &filename) {
std::error_code ec;
uint64_t size = std::filesystem::file_size(filename, ec);
if (ec) {
throw std::runtime_error("file size error " + ec.message());
}
if (size == 0) {
throw std::runtime_error("empty file");
}
std::string content;
content.resize(size);
std::ifstream file(filename, std::ios::binary);
file.read(content.data(), content.size());
return content;
}
template <typename T>
IGUANA_INLINE void from_json_file(T &value, const std::string &filename) {
std::string content = json_file_content(filename);
from_json(value, content.begin(), content.end());
}
template <typename T>
IGUANA_INLINE void from_json_file(T &value, const std::string &filename,
std::error_code &ec) noexcept {
try {
from_json_file(value, filename);
ec = {};
} catch (std::runtime_error &e) {
ec = iguana::make_error_code(e.what());
}
}
} // namespace iguana