oxen-core/contrib/epee/include/epee/storages/portable_storage_from_json.h

413 lines
16 KiB
C++

// Copyright (c) 2006-2013, Andrey N. Sabelnikov, www.sabelnikov.net
// All rights reserved.
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are met:
// * Redistributions of source code must retain the above copyright
// notice, this list of conditions and the following disclaimer.
// * Redistributions in binary form must reproduce the above copyright
// notice, this list of conditions and the following disclaimer in the
// documentation and/or other materials provided with the distribution.
// * Neither the name of the Andrey N. Sabelnikov nor the
// names of its contributors may be used to endorse or promote products
// derived from this software without specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
// ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
// WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER BE LIABLE FOR ANY
// DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
// (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
// LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
// ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
//
#pragma once
#include <string_view>
#include <charconv>
#include "parserse_base_utils.h"
#define EPEE_JSON_RECURSION_LIMIT_INTERNAL 100
namespace epee
{
using namespace misc_utils::parse;
namespace serialization
{
namespace json
{
#define CHECK_ISSPACE() if(!epee::misc_utils::parse::isspace(*it)){ ASSERT_MES_AND_THROW("Wrong JSON character at: " << std::string(it, buf_end));}
template <typename T>
T parse_number(const std::string_view str)
{
T num;
// In theory from_chars should work with double, but neither libc++ nor libstdc++ implement
// it yet.
if constexpr (std::is_same_v<T, double>)
{
size_t consumed;
try {
if (num = std::stod(str.data(), &consumed); consumed == str.size())
return num;
}
catch (...) {}
}
else
{
auto* end = str.data() + str.size();
if (auto [p, ec] = std::from_chars(str.data(), end, num); ec == std::errc{} && p == end)
return num;
}
throw std::runtime_error("Invalid number: " + std::string{str});
}
template <typename Storage, typename T>
array_entry* make_array_and_insert(Storage& st, const std::string& value_name, T value, section* parent_section)
{
auto* array = st.template make_array<T>(value_name, parent_section);
CHECK_AND_ASSERT_THROW_MES(array, "failed to insert " + std::string{typeid(T).name()} + " array");
var::get<array_t<T>>(*array).push_back(std::move(value));
return array;
}
/*inline void parse_error()
{
ASSERT_MES_AND_THROW("json parse error");
}*/
template<typename It, class t_storage>
void run_handler(section* current_section, It& sec_buf_begin, It buf_end, t_storage& stg, unsigned int recursion)
{
CHECK_AND_ASSERT_THROW_MES(recursion < EPEE_JSON_RECURSION_LIMIT_INTERNAL, "Wrong JSON data: recursion limitation (" << EPEE_JSON_RECURSION_LIMIT_INTERNAL << ") exceeded");
std::string::const_iterator sub_element_start;
std::string name;
array_entry* array = nullptr;
enum match_state
{
match_state_lookup_for_section_start,
match_state_lookup_for_name,
match_state_waiting_separator,
match_state_wonder_after_separator,
match_state_wonder_after_value,
match_state_wonder_array,
match_state_array_after_value,
match_state_array_waiting_value,
match_state_error
};
enum array_mode
{
array_mode_undefined = 0,
array_mode_sections,
array_mode_string,
array_mode_numbers,
array_mode_booleans
};
match_state state = match_state_lookup_for_section_start;
array_mode array_md = array_mode_undefined;
for(auto it = sec_buf_begin; it != buf_end; ++it)
{
switch (state)
{
case match_state_lookup_for_section_start:
if(*it == '{')
state = match_state_lookup_for_name;
else CHECK_ISSPACE();
break;
case match_state_lookup_for_name:
switch(*it)
{
case '"':
match_string2(it, buf_end, name);
state = match_state_waiting_separator;
break;
case '}':
//this is it! section ends here.
//seems that it is empty section
sec_buf_begin = it;
return;
default:
CHECK_ISSPACE();
}
break;
case match_state_waiting_separator:
if(*it == ':')
state = match_state_wonder_after_separator;
else CHECK_ISSPACE();
break;
case match_state_wonder_after_separator:
if(*it == '"')
{//just a named string value started
std::string val;
match_string2(it, buf_end, val);
//insert text value
stg.set_value(name, std::move(val), current_section);
state = match_state_wonder_after_value;
}else if (epee::misc_utils::parse::isdigit(*it) || *it == '-')
{//just a named number value started
std::string_view val;
bool is_v_float = false;bool is_signed = false;
match_number2(it, buf_end, val, is_v_float, is_signed);
if(is_v_float)
stg.set_value(name, parse_number<double>(val), current_section);
else if(is_signed)
stg.set_value(name, parse_number<int64_t>(val), current_section);
else
stg.set_value(name, parse_number<uint64_t>(val), current_section);
state = match_state_wonder_after_value;
}else if(isalpha(*it) )
{// could be null, true or false
std::string_view word;
match_word2(it, buf_end, word);
if(word == "null")
//just skip this,
state = match_state_wonder_after_value;
else if(word == "true" || word == "false")
stg.set_value(name, word == "true", current_section);
else ASSERT_MES_AND_THROW("Unknown value keyword " << word);
state = match_state_wonder_after_value;
}else if(*it == '{')
{
//sub section here
section* new_sec = stg.open_section(name, current_section, true);
CHECK_AND_ASSERT_THROW_MES(new_sec, "Failed to insert new section in json: " << std::string(it, buf_end));
run_handler(new_sec, it, buf_end, stg, recursion + 1);
state = match_state_wonder_after_value;
}else if(*it == '[')
{//array of something
state = match_state_wonder_array;
}else CHECK_ISSPACE();
break;
case match_state_wonder_after_value:
if(*it == ',')
state = match_state_lookup_for_name;
else if(*it == '}')
{
//this is it! section ends here.
sec_buf_begin = it;
return;
}else CHECK_ISSPACE();
break;
case match_state_wonder_array:
if(*it == '[')
{
ASSERT_MES_AND_THROW("array of array not supported yet :( sorry");
//mean array of array
}
if(*it == '{')
{
//mean array of sections
array = make_array_and_insert(stg, name, section{}, current_section);
run_handler(&var::get<array_t<section>>(*array).back(), it, buf_end, stg, recursion + 1);
state = match_state_array_after_value;
array_md = array_mode_sections;
}else if(*it == '"')
{
//mean array of strings
std::string val;
match_string2(it, buf_end, val);
array = make_array_and_insert(stg, name, std::move(val), current_section);
state = match_state_array_after_value;
array_md = array_mode_string;
}else if (epee::misc_utils::parse::isdigit(*it) || *it == '-')
{//array of numbers value started
std::string_view val;
bool is_v_float = false;bool is_signed_val = false;
match_number2(it, buf_end, val, is_v_float, is_signed_val);
// This numeric array handling here is gross: the first value determines the array
// type, but subsequent values have to be guessed as the same type or else you get a
// parse error because the code requires (and has always required) that the guessed
// type is the same. So these are okay:
// "foo": [1,2]
// "foo": [-1,-2]
// "foo": [1.5, 2.0]
// but these result in parse failures:
// "foo": [1,-2]
// "foo": [-1,2]
// "foo": [1.5, 2]
// even though 2 is both a valid signed integer and a valid double. And that means
// there is *no way* to send a list of signed integer values unless *all* happen to be
// negative, and no way to send a list of doubles unless *all* happen to not be
// integers.
//
// It is not worth fixing this cursed code, though: better to scrap it and move to a
// proper library that isn't such a pile of garbage.
//
if (is_v_float)
array = make_array_and_insert(stg, name, parse_number<double>(val), current_section);
else if (is_signed_val)
array = make_array_and_insert(stg, name, parse_number<int64_t>(val), current_section);
else
array = make_array_and_insert(stg, name, parse_number<uint64_t>(val), current_section);
state = match_state_array_after_value;
array_md = array_mode_numbers;
}else if(*it == ']')//empty array
{
array_md = array_mode_undefined;
state = match_state_wonder_after_value;
}else if(isalpha(*it) )
{// array of booleans
std::string_view word;
match_word2(it, buf_end, word);
if(word == "true" || word == "false")
array = make_array_and_insert(stg, name, word == "true", current_section);
else
ASSERT_MES_AND_THROW("Unknown value keyword " << word)
state = match_state_array_after_value;
array_md = array_mode_booleans;
}else CHECK_ISSPACE();
break;
case match_state_array_after_value:
if(*it == ',')
state = match_state_array_waiting_value;
else if(*it == ']')
{
array = nullptr;
array_md = array_mode_undefined;
state = match_state_wonder_after_value;
}else CHECK_ISSPACE();
break;
case match_state_array_waiting_value:
switch(array_md)
{
case array_mode_sections:
if(*it == '{')
{
auto* a = std::get_if<array_t<section>>(array);
CHECK_AND_ASSERT_THROW_MES(a, "failed to insert next section");
a->emplace_back();
run_handler(&a->back(), it, buf_end, stg, recursion + 1);
state = match_state_array_after_value;
}else CHECK_ISSPACE();
break;
case array_mode_string:
if(*it == '"')
{
std::string val;
match_string2(it, buf_end, val);
auto* a = std::get_if<array_t<std::string>>(array);
CHECK_AND_ASSERT_THROW_MES(a, "failed to insert string value");
a->push_back(std::move(val));
state = match_state_array_after_value;
}else CHECK_ISSPACE();
break;
case array_mode_numbers:
if (epee::misc_utils::parse::isdigit(*it) || *it == '-')
{//array of numbers value started
std::string_view val;
bool is_v_float = false;bool is_signed_val = false;
match_number2(it, buf_end, val, is_v_float, is_signed_val);
bool insert_res = false;
// This is broken AF. See comment above.
if (is_v_float)
{
if (auto* a = std::get_if<array_t<double>>(array)) {
a->push_back(parse_number<double>(val));
insert_res = true;
}
}
else if (is_signed_val)
{
if (auto* a = std::get_if<array_t<int64_t>>(array)) {
a->push_back(parse_number<int64_t>(val));
insert_res = true;
}
}
else
{
if (auto* a = std::get_if<array_t<uint64_t>>(array)) {
a->push_back(parse_number<uint64_t>(val));
insert_res = true;
}
}
CHECK_AND_ASSERT_THROW_MES(insert_res, "Failed to insert next value");
state = match_state_array_after_value;
array_md = array_mode_numbers;
}else CHECK_ISSPACE();
break;
case array_mode_booleans:
if(isalpha(*it) )
{// array of booleans
std::string_view word;
match_word2(it, buf_end, word);
bool val;
if (word == "true" || word == "false")
val = true;
else if (word == "false")
val = false;
else ASSERT_MES_AND_THROW("Unknown value keyword " << word);
if (auto* a = std::get_if<array_t<bool>>(array))
a->push_back(val);
else ASSERT_MES_AND_THROW("can't handle a bool value mixed with other types");
}else CHECK_ISSPACE();
break;
case array_mode_undefined:
default:
ASSERT_MES_AND_THROW("Bad array state");
}
break;
case match_state_error:
default:
ASSERT_MES_AND_THROW("WRONG JSON STATE");
}
}
}
/*
{
"firstName": "John",
"lastName": "Smith",
"age": 25,
"address": {
"streetAddress": "21 2nd Street",
"city": "New York",
"state": "NY",
"postalCode": -10021,
"have_boobs": true,
"have_balls": false
},
"phoneNumber": [
{
"type": "home",
"number": "212 555-1234"
},
{
"type": "fax",
"number": "646 555-4567"
}
],
"phoneNumbers": [
"812 123-1234",
"916 123-4567"
]
}
*/
template<class t_storage>
inline bool load_from_json(std::string_view buff_json, t_storage& stg)
{
try
{
auto it = buff_json.begin();
run_handler(nullptr, it, buff_json.end(), stg, 0);
return true;
}
catch(const std::exception& ex)
{
MERROR("Failed to parse json, what: " << ex.what());
return false;
}
catch(...)
{
MERROR("Failed to parse json");
return false;
}
}
}
}
}