Commit b51414da authored by Dominik Charousset's avatar Dominik Charousset

Add new deserializer for reading JSON input

parent 9a120d9a
......@@ -156,6 +156,7 @@ caf_add_component(
src/ipv6_address.cpp
src/ipv6_endpoint.cpp
src/ipv6_subnet.cpp
src/json_reader.cpp
src/json_writer.cpp
src/load_inspector.cpp
src/local_actor.cpp
......@@ -295,6 +296,7 @@ caf_add_component(
ipv6_address
ipv6_endpoint
ipv6_subnet
json_reader
json_writer
load_inspector
logger
......
......@@ -40,6 +40,20 @@ public:
using data_type
= std::variant<null_t, int64_t, double, bool, string_view, array, object>;
static constexpr size_t null_index = 0;
static constexpr size_t integer_index = 1;
static constexpr size_t double_index = 2;
static constexpr size_t bool_index = 3;
static constexpr size_t string_index = 4;
static constexpr size_t array_index = 5;
static constexpr size_t object_index = 6;
data_type data;
};
......
......@@ -47,6 +47,53 @@ void print_escaped(Buffer& buf, string_view str) {
buf.push_back('"');
}
template <class Buffer>
void print_unescaped(Buffer& buf, string_view str) {
buf.reserve(buf.size() + str.size());
auto i = str.begin();
auto e = str.end();
while (i != e) {
switch (*i) {
default:
buf.push_back(*i);
++i;
break;
case '\\':
if (++i != e) {
switch (*i) {
case '"':
buf.push_back('"');
break;
case '\\':
buf.push_back('\\');
break;
case 'b':
buf.push_back('\b');
break;
case 'f':
buf.push_back('\f');
break;
case 'n':
buf.push_back('\n');
break;
case 'r':
buf.push_back('\r');
break;
case 't':
buf.push_back('\t');
break;
case 'v':
buf.push_back('\v');
break;
default:
buf.push_back('?');
}
++i;
}
}
}
}
template <class Buffer>
void print(Buffer& buf, none_t) {
using namespace caf::literals;
......
......@@ -145,6 +145,13 @@ public:
int compare(uint8_t code, type_id_t category) const noexcept;
// -- modifiers --------------------------------------------------------------
/// Reverts this error to "not an error" as if calling `*this = error{}`.
void reset() noexcept {
data_.reset();
}
// -- static convenience functions -------------------------------------------
/// @cond PRIVATE
......
// This file is part of CAF, the C++ Actor Framework. See the file LICENSE in
// the main distribution directory for license terms and copyright or visit
// https://github.com/actor-framework/actor-framework/blob/master/LICENSE.
#pragma once
#include "caf/deserializer.hpp"
#include "caf/detail/core_export.hpp"
#include "caf/detail/json.hpp"
#include "caf/string_view.hpp"
#include <variant>
namespace caf {
/// Deserializes an inspectable object from a JSON-formatted string.
class CAF_CORE_EXPORT json_reader : public deserializer {
public:
// -- member types -----------------------------------------------------------
using super = deserializer;
struct sequence {
detail::json::array::const_iterator pos;
detail::json::array::const_iterator end;
bool at_end() const noexcept {
return pos == end;
}
auto& current() {
return *pos;
}
void advance() {
++pos;
}
};
struct members {
detail::json::object::const_iterator pos;
detail::json::object::const_iterator end;
bool at_end() const noexcept {
return pos == end;
}
auto& current() {
return *pos;
}
void advance() {
++pos;
}
};
using json_key = string_view;
using value_type
= std::variant<const detail::json::value*, const detail::json::object*,
detail::json::null_t, json_key, sequence, members>;
using stack_allocator
= detail::monotonic_buffer_resource::allocator<value_type>;
using stack_type = std::vector<value_type, stack_allocator>;
/// Denotes the type at the current position.
enum class position {
value,
object,
null,
key,
sequence,
members,
past_the_end,
invalid,
};
// -- constructors, destructors, and assignment operators --------------------
json_reader();
explicit json_reader(actor_system& sys);
explicit json_reader(execution_unit* ctx);
json_reader(const json_reader&) = delete;
json_reader& operator=(const json_reader&) = delete;
~json_reader() override;
// -- modifiers --------------------------------------------------------------
/// Removes any loaded JSON data and reclaims all memory resources.
void reset();
/// Parses @p json_text into an internal representation. After loading the
/// JSON input, the reader is ready for attempting to deserialize inspectable
/// objects.
/// @warning The internal data structure keeps pointers into @p json_text.
/// Hence, the buffer pointed to by the string view must remain valid
/// until either destroying this reader or calling `reset`.
/// @note Implicitly calls `reset`.
bool load(string_view json_text);
/// Reverts the state of the reader back to where it was after calling `load`.
/// @post The reader is ready for attempting to deserialize another
/// inspectable object.
void revert();
// -- overrides --------------------------------------------------------------
bool fetch_next_object_type(type_id_t& type) override;
bool begin_object(type_id_t type, string_view name) override;
bool end_object() override;
bool begin_field(string_view) override;
bool begin_field(string_view name, bool& is_present) override;
bool begin_field(string_view name, span<const type_id_t> types,
size_t& index) override;
bool begin_field(string_view name, bool& is_present,
span<const type_id_t> types, size_t& index) override;
bool end_field() override;
bool begin_tuple(size_t size) override;
bool end_tuple() override;
bool begin_key_value_pair() override;
bool end_key_value_pair() override;
bool begin_sequence(size_t& size) override;
bool end_sequence() override;
bool begin_associative_array(size_t& size) override;
bool end_associative_array() override;
bool value(byte& x) override;
bool value(bool& x) override;
bool value(int8_t& x) override;
bool value(uint8_t& x) override;
bool value(int16_t& x) override;
bool value(uint16_t& x) override;
bool value(int32_t& x) override;
bool value(uint32_t& x) override;
bool value(int64_t& x) override;
bool value(uint64_t& x) override;
bool value(float& x) override;
bool value(double& x) override;
bool value(long double& x) override;
bool value(std::string& x) override;
bool value(std::u16string& x) override;
bool value(std::u32string& x) override;
bool value(span<byte> x) override;
private:
[[nodiscard]] position pos() const noexcept;
template <bool PopOrAdvanceOnSuccess, class F>
bool consume(const char* fun_name, F f);
template <class T>
bool integer(T& x);
template <position P>
auto& top() noexcept {
return std::get<static_cast<size_t>(P)>(st_->back());
}
template <position P>
const auto& top() const noexcept {
return std::get<static_cast<size_t>(P)>(st_->back());
}
void pop() {
st_->pop_back();
}
template <class T>
void push(T&& x) {
st_->emplace_back(std::forward<T>(x));
}
detail::monotonic_buffer_resource buf_;
stack_type* st_ = nullptr;
detail::json::value* root_ = nullptr;
};
} // namespace caf
......@@ -42,7 +42,7 @@ public:
template <class... Ts>
void emplace_error(Ts&&... xs) {
err_ = make_error(xs...);
err_ = make_error(std::forward<Ts>(xs)...);
}
const error& get_error() const noexcept {
......
......@@ -42,7 +42,7 @@ public:
template <class... Ts>
void emplace_error(Ts&&... xs) {
err_ = make_error(xs...);
err_ = make_error(std::forward<Ts>(xs)...);
}
const error& get_error() const noexcept {
......
This diff is collapsed.
// This file is part of CAF, the C++ Actor Framework. See the file LICENSE in
// the main distribution directory for license terms and copyright or visit
// https://github.com/actor-framework/actor-framework/blob/master/LICENSE.
#define CAF_SUITE json_reader
#include "caf/json_reader.hpp"
#include "core-test.hpp"
#include "caf/dictionary.hpp"
using namespace caf;
namespace {
struct fixture {
template <class T>
void add_test_case(string_view input, T val) {
auto f = [this, input, obj{std::move(val)}]() -> bool {
auto tmp = T{};
auto res = CHECK(reader.load(input)) // parse JSON
&& CHECK(reader.apply(tmp)); // deserialize object
if (res) {
if constexpr (std::is_same<T, message>::value)
res = CHECK_EQ(to_string(tmp), to_string(obj));
else
res = CHECK_EQ(tmp, obj);
}
if (!res)
MESSAGE("rejected input: " << input);
return res;
};
test_cases.emplace_back(std::move(f));
}
template <class T, class... Ts>
std::vector<T> ls(Ts... xs) {
return {std::move(xs)...};
}
template <class T>
using dict = dictionary<T>;
fixture();
json_reader reader;
std::vector<std::function<bool()>> test_cases;
};
fixture::fixture() {
add_test_case(R"_(true)_", true);
add_test_case(R"_(false)_", false);
add_test_case(R"_([true, false])_", ls<bool>(true, false));
add_test_case(R"_(42)_", int32_t{42});
add_test_case(R"_([1, 2, 3])_", ls<int32_t>(1, 2, 3));
add_test_case(R"_(2.0)_", 2.0);
add_test_case(R"_([2.0, 4.0, 8.0])_", ls<double>(2.0, 4.0, 8.0));
add_test_case(R"_("hello \"world\"!")_", std::string{R"_(hello "world"!)_"});
add_test_case(R"_(["hello", "world"])_", ls<std::string>("hello", "world"));
add_test_case(R"_({"a": 1, "b": 2})_", my_request(1, 2));
add_test_case(R"_({"a": 1, "b": 2})_", dict<int>({{"a", 1}, {"b", 2}}));
add_test_case(R"_([{"@type": "my_request", "a": 1, "b": 2}])_",
make_message(my_request(1, 2)));
}
} // namespace
CAF_TEST_FIXTURE_SCOPE(json_reader_tests, fixture)
CAF_TEST(json baselines) {
size_t baseline_index = 0;
detail::monotonic_buffer_resource resource;
for (auto& f : test_cases) {
MESSAGE("test case at index " << baseline_index++);
if (!f())
if (auto reason = reader.get_error())
MESSAGE("JSON reader stopped due to: " << reason);
}
}
CAF_TEST_FIXTURE_SCOPE_END()
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment