Commit a3b6ddc5 authored by Dominik Charousset's avatar Dominik Charousset

Merge branch 'topic/neverlord/json-api'

parents 7168be7a c5bb6a65
......@@ -3,6 +3,13 @@
All notable changes to this project will be documented in this file. The format
is based on [Keep a Changelog](https://keepachangelog.com).
## [Unreleased]
### Added
- The new classes `json_value`, `json_array` and `json_object` allow working
with JSON inputs directly. Actors can also pass around JSON values safely.
## [0.19.0-rc.1] - 2020-10-31
### Added
......
......@@ -161,7 +161,11 @@ caf_add_component(
src/ipv6_address.cpp
src/ipv6_endpoint.cpp
src/ipv6_subnet.cpp
src/json_array.cpp
src/json_builder.cpp
src/json_object.cpp
src/json_reader.cpp
src/json_value.cpp
src/json_writer.cpp
src/load_inspector.cpp
src/local_actor.cpp
......@@ -313,7 +317,11 @@ caf_add_component(
ipv6_address
ipv6_endpoint
ipv6_subnet
json_array
json_builder
json_object
json_reader
json_value
json_writer
load_inspector
logger
......
......@@ -4,13 +4,20 @@
#pragma once
#include "caf/detail/monotonic_buffer_resource.hpp"
#include "caf/detail/print.hpp"
#include "caf/intrusive_ptr.hpp"
#include "caf/parser_state.hpp"
#include "caf/ref_counted.hpp"
#include "caf/span.hpp"
#include "caf/type_id.hpp"
#include <cstdint>
#include <cstring>
#include <iterator>
#include <new>
#include <string_view>
#include <variant>
#include <vector>
#include "caf/detail/monotonic_buffer_resource.hpp"
#include "caf/parser_state.hpp"
// This JSON abstraction is designed to allocate its entire state in a monotonic
// buffer resource. This minimizes memory allocations and also enables us to
......@@ -20,25 +27,313 @@
namespace caf::detail::json {
// -- utility classes ----------------------------------------------------------
// Wraps a buffer resource with a reference count.
struct CAF_CORE_EXPORT storage : public ref_counted {
/// Provides the memory for all of our parsed JSON entities.
detail::monotonic_buffer_resource buf;
};
// -- helper for modeling the JSON type system ---------------------------------
using storage_ptr = intrusive_ptr<storage>;
struct null_t {};
class value {
constexpr bool operator==(null_t, null_t) {
return true;
}
constexpr bool operator!=(null_t, null_t) {
return false;
}
struct undefined_t {};
constexpr bool operator==(undefined_t, undefined_t) {
return true;
}
constexpr bool operator!=(undefined_t, undefined_t) {
return false;
}
template <class T>
struct linked_list_node {
T value;
linked_list_node* next;
};
template <class T>
class linked_list_iterator {
public:
using difference_type = ptrdiff_t;
using value_type = T;
using pointer = value_type*;
using reference = value_type&;
using iterator_category = std::forward_iterator_tag;
using node_pointer
= std::conditional_t<std::is_const_v<T>,
const linked_list_node<std::remove_const_t<T>>*,
linked_list_node<value_type>*>;
constexpr linked_list_iterator() noexcept = default;
constexpr explicit linked_list_iterator(node_pointer ptr) noexcept
: ptr_(ptr) {
// nop
}
constexpr linked_list_iterator(
const linked_list_iterator&) noexcept = default;
constexpr linked_list_iterator&
operator=(const linked_list_iterator&) noexcept = default;
constexpr node_pointer get() const noexcept {
return ptr_;
}
linked_list_iterator& operator++() noexcept {
ptr_ = ptr_->next;
return *this;
}
linked_list_iterator operator++(int) noexcept {
return linked_list_iterator{ptr_->next};
}
T& operator*() const noexcept {
return ptr_->value;
}
T* operator->() const noexcept {
return std::addressof(ptr_->value);
}
private:
node_pointer ptr_ = nullptr;
};
template <class T>
constexpr bool
operator==(linked_list_iterator<T> lhs, linked_list_iterator<T> rhs) {
return lhs.get() == rhs.get();
}
template <class T>
constexpr bool
operator!=(linked_list_iterator<T> lhs, linked_list_iterator<T> rhs) {
return !(lhs == rhs);
}
// A minimal version of a linked list that has constexpr constructor and an
// iterator type where the default-constructed iterator is the past-the-end
// iterator. Properties that std::list unfortunately lacks.
//
// The default-constructed list object is an empty list that does not allow
// push_back.
template <class T>
class linked_list {
public:
using value_type = T;
using node_type = linked_list_node<value_type>;
using allocator_type = monotonic_buffer_resource::allocator<node_type>;
using reference = value_type&;
using const_reference = const value_type&;
using pointer = value_type*;
using const_pointer = const value_type*;
using node_pointer = node_type*;
using iterator = linked_list_iterator<value_type>;
using const_iterator = linked_list_iterator<const value_type>;
linked_list() noexcept {
// nop
}
~linked_list() {
auto* ptr = head_;
while (ptr != nullptr) {
auto* next = ptr->next;
ptr->~node_type();
allocator_.deallocate(ptr, 1);
ptr = next;
}
}
explicit linked_list(allocator_type allocator) noexcept
: allocator_(allocator) {
// nop
}
linked_list(const linked_list&) = delete;
linked_list(linked_list&& other)
: size_(other.size_),
head_(other.head_),
tail_(other.tail_),
allocator_(other.allocator_) {
other.size_ = 0;
other.head_ = nullptr;
other.tail_ = nullptr;
}
linked_list& operator=(const linked_list&) = delete;
linked_list& operator=(linked_list&& other) {
using std::swap;
swap(size_, other.size_);
swap(head_, other.head_);
swap(tail_, other.tail_);
swap(allocator_, other.allocator_);
return *this;
}
[[nodiscard]] bool empty() const noexcept {
return size_ == 0;
}
[[nodiscard]] size_t size() const noexcept {
return size_;
}
[[nodiscard]] iterator begin() noexcept {
return iterator{head_};
}
[[nodiscard]] const_iterator begin() const noexcept {
return const_iterator{head_};
}
[[nodiscard]] const_iterator cbegin() const noexcept {
return begin();
}
[[nodiscard]] iterator end() noexcept {
return {};
}
[[nodiscard]] const_iterator end() const noexcept {
return {};
}
[[nodiscard]] const_iterator cend() const noexcept {
return {};
}
[[nodiscard]] reference front() noexcept {
return head_->value;
}
[[nodiscard]] const_reference front() const noexcept {
return head_->value;
}
[[nodiscard]] reference back() noexcept {
return tail_->value;
}
[[nodiscard]] const_reference back() const noexcept {
return tail_->value;
}
[[nodiscard]] allocator_type get_allocator() const noexcept {
return allocator_;
}
void push_back(T value) {
++size_;
auto new_node = allocator_->allocate(1);
new (new_node) node_type{std::move(value), nullptr};
if (head_ == nullptr) {
head_ = tail_ = new_node;
} else {
tail_->next = new_node;
tail_ = new_node;
}
}
template <class... Ts>
reference emplace_back(Ts&&... args) {
++size_;
auto new_node = allocator_.allocate(1);
new (new_node) node_type{T{std::forward<Ts>(args)...}, nullptr};
if (head_ == nullptr) {
head_ = tail_ = new_node;
} else {
tail_->next = new_node;
tail_ = new_node;
}
return new_node->value;
}
private:
size_t size_ = 0;
node_pointer head_ = nullptr;
node_pointer tail_ = nullptr;
allocator_type allocator_;
};
template <class T>
bool operator==(const linked_list<T>& lhs, const linked_list<T>& rhs) {
return std::equal(lhs.begin(), lhs.end(), rhs.begin(), rhs.end());
}
template <class T>
bool operator!=(const linked_list<T>& lhs, const linked_list<T>& rhs) {
return !(lhs == rhs);
}
/// Re-allocates the given string at the buffer resource.
CAF_CORE_EXPORT std::string_view realloc(std::string_view str,
monotonic_buffer_resource* res);
inline std::string_view realloc(std::string_view str, const storage_ptr& ptr) {
return realloc(str, &ptr->buf);
}
/// Concatenates all strings and allocates a single new string for the result.
CAF_CORE_EXPORT std::string_view
concat(std::initializer_list<std::string_view> xs,
monotonic_buffer_resource* res);
inline std::string_view concat(std::initializer_list<std::string_view> xs,
const storage_ptr& ptr) {
return concat(xs, &ptr->buf);
}
class CAF_CORE_EXPORT value {
public:
using array_allocator = monotonic_buffer_resource::allocator<value>;
using array = linked_list<value>;
using array = std::vector<value, array_allocator>;
using array_allocator = array::allocator_type;
struct member {
std::string_view key;
value* val = nullptr;
};
using member_allocator = monotonic_buffer_resource::allocator<member>;
using object = linked_list<member>;
using object = std::vector<member, member_allocator>;
using object_allocator = object::allocator_type;
using data_type = std::variant<null_t, int64_t, double, bool,
std::string_view, array, object>;
std::string_view, array, object, undefined_t>;
static constexpr size_t null_index = 0;
......@@ -54,21 +349,482 @@ public:
static constexpr size_t object_index = 6;
static constexpr size_t undefined_index = 7;
data_type data;
bool is_null() const noexcept {
return data.index() == null_index;
}
bool is_integer() const noexcept {
return data.index() == integer_index;
}
bool is_double() const noexcept {
return data.index() == double_index;
}
bool is_bool() const noexcept {
return data.index() == bool_index;
}
bool is_string() const noexcept {
return data.index() == string_index;
}
bool is_array() const noexcept {
return data.index() == array_index;
}
bool is_object() const noexcept {
return data.index() == object_index;
}
bool is_undefined() const noexcept {
return data.index() == undefined_index;
}
void assign_string(std::string_view str, monotonic_buffer_resource* res) {
data = realloc(str, res);
}
void assign_string(std::string_view str, const storage_ptr& ptr) {
data = realloc(str, &ptr->buf);
}
void assign_object(monotonic_buffer_resource* res) {
data = object{object_allocator{res}};
}
void assign_object(const storage_ptr& ptr) {
assign_object(&ptr->buf);
}
void assign_array(monotonic_buffer_resource* res) {
data = array{array_allocator{res}};
}
void assign_array(const storage_ptr& ptr) {
assign_array(&ptr->buf);
}
};
inline bool operator==(const value& lhs, const value& rhs) {
return lhs.data == rhs.data;
}
inline bool operator!=(const value& lhs, const value& rhs) {
return !(lhs == rhs);
}
inline bool operator==(const value::member& lhs, const value::member& rhs) {
if (lhs.key == rhs.key && lhs.val != nullptr && rhs.val != nullptr) {
return *lhs.val == *rhs.val;
}
return false;
}
inline bool operator!=(const value::member& lhs, const value::member& rhs) {
return !(lhs == rhs);
}
using array = value::array;
using member = value::member;
using object = value::object;
// -- factory functions --------------------------------------------------------
value* make_value(monotonic_buffer_resource* storage);
inline value* make_value(const storage_ptr& ptr) {
return make_value(&ptr->buf);
}
array* make_array(monotonic_buffer_resource* storage);
inline array* make_array(const storage_ptr& ptr) {
return make_array(&ptr->buf);
}
object* make_object(monotonic_buffer_resource* storage);
inline object* make_object(const storage_ptr& ptr) {
return make_object(&ptr->buf);
}
// -- saving -------------------------------------------------------------------
template <class Serializer>
bool save(Serializer& sink, const object& obj);
template <class Serializer>
bool save(Serializer& sink, const array& arr);
template <class Serializer>
bool save(Serializer& sink, const value& val) {
// On the "wire", we only use the public types.
if (!sink.begin_object(type_id_v<json_value>, type_name_v<json_value>))
return false;
// Maps our type indexes to their public API counterpart.
type_id_t mapping[] = {type_id_v<unit_t>, type_id_v<int64_t>,
type_id_v<double>, type_id_v<bool>,
type_id_v<std::string>, type_id_v<json_array>,
type_id_v<json_object>, type_id_v<none_t>};
// Act as-if this type is a variant of the mapped types.
auto type_index = val.data.index();
if (!sink.begin_field("value", make_span(mapping), type_index))
return false;
// Dispatch on the run-time type of this value.
switch (type_index) {
case value::integer_index:
if (!sink.apply(std::get<int64_t>(val.data)))
return false;
break;
case value::double_index:
if (!sink.apply(std::get<double>(val.data)))
return false;
break;
case value::bool_index:
if (!sink.apply(std::get<bool>(val.data)))
return false;
break;
case value::string_index:
if (!sink.apply(std::get<std::string_view>(val.data)))
return false;
break;
case value::array_index:
if (!save(sink, std::get<array>(val.data)))
return false;
break;
case value::object_index:
if (!save(sink, std::get<object>(val.data)))
return false;
break;
default:
// null and undefined both have no data
break;
}
// Wrap up.
return sink.end_field() && sink.end_object();
}
template <class Serializer>
bool save(Serializer& sink, const object& obj) {
if (!sink.begin_associative_array(obj.size()))
return false;
for (const auto& kvp : obj) {
if (kvp.val != nullptr) {
if (!sink.begin_key_value_pair() // <key-value-pair>
|| !sink.value(kvp.key) // <key ...>
|| !save(sink, *kvp.val) // <value ...>
|| !sink.end_key_value_pair()) // </key-value-pair>
return false;
}
}
return sink.end_associative_array();
}
template <class Serializer>
bool save(Serializer& sink, const array& arr) {
if (!sink.begin_sequence(arr.size()))
return false;
for (const auto& val : arr)
if (!save(sink, val))
return false;
return sink.end_sequence();
}
// -- loading ------------------------------------------------------------------
template <class Deserializer>
bool load(Deserializer& source, object& obj, monotonic_buffer_resource* res);
template <class Deserializer>
bool load(Deserializer& source, array& arr, monotonic_buffer_resource* res);
template <class Deserializer>
bool load(Deserializer& source, value& val, monotonic_buffer_resource* res) {
// On the "wire", we only use the public types.
if (!source.begin_object(type_id_v<json_value>, type_name_v<json_value>))
return false;
// Maps our type indexes to their public API counterpart.
type_id_t mapping[] = {type_id_v<unit_t>, type_id_v<int64_t>,
type_id_v<double>, type_id_v<bool>,
type_id_v<std::string>, type_id_v<json_array>,
type_id_v<json_object>, type_id_v<none_t>};
// Act as-if this type is a variant of the mapped types.
auto type_index = size_t{0};
if (!source.begin_field("value", make_span(mapping), type_index))
return false;
// Dispatch on the run-time type of this value.
switch (type_index) {
case value::null_index:
val.data = null_t{};
break;
case value::integer_index: {
auto tmp = int64_t{0};
if (!source.apply(tmp))
return false;
val.data = tmp;
break;
}
case value::double_index: {
auto tmp = double{0};
if (!source.apply(tmp))
return false;
val.data = tmp;
break;
}
case value::bool_index: {
auto tmp = false;
if (!source.apply(tmp))
return false;
val.data = tmp;
break;
}
case value::string_index: {
auto tmp = std::string{};
if (!source.apply(tmp))
return false;
if (tmp.empty()) {
val.data = std::string_view{};
} else {
val.assign_string(tmp, res);
}
break;
}
case value::array_index:
val.data = array{array::allocator_type{res}};
if (!load(source, std::get<array>(val.data), res))
return false;
break;
case value::object_index:
val.data = object{object::allocator_type{res}};
if (!load(source, std::get<object>(val.data), res))
return false;
break;
default: // undefined
val.data = undefined_t{};
break;
}
// Wrap up.
return source.end_field() && source.end_object();
}
template <class Deserializer>
bool load(Deserializer& source, object& obj, monotonic_buffer_resource* res) {
auto size = size_t{0};
if (!source.begin_associative_array(size))
return false;
for (size_t i = 0; i < size; ++i) {
if (!source.begin_key_value_pair())
return false;
auto& kvp = obj.emplace_back();
// Deserialize the key.
auto key = std::string{};
if (!source.apply(key))
return false;
if (key.empty()) {
kvp.key = std::string_view{};
} else {
kvp.key = realloc(key, res);
}
// Deserialize the value.
kvp.val = make_value(res);
if (!load(source, *kvp.val, res))
return false;
if (!source.end_key_value_pair())
return false;
}
return source.end_associative_array();
}
template <class Deserializer>
bool load(Deserializer& source, array& arr, monotonic_buffer_resource* res) {
auto size = size_t{0};
if (!source.begin_sequence(size))
return false;
for (size_t i = 0; i < size; ++i) {
auto& val = arr.emplace_back();
if (!load(source, val, res))
return false;
}
return source.end_sequence();
}
template <class Deserializer>
bool load(Deserializer& source, object& obj, const storage_ptr& ptr) {
return load(source, obj, std::addressof(ptr->buf));
}
template <class Deserializer>
bool load(Deserializer& source, array& arr, const storage_ptr& ptr) {
return load(source, arr, std::addressof(ptr->buf));
}
template <class Deserializer>
bool load(Deserializer& source, value& val, const storage_ptr& ptr) {
return load(source, val, std::addressof(ptr->buf));
}
// -- singletons ---------------------------------------------------------------
const value* null_value() noexcept;
const value* undefined_value() noexcept;
const object* empty_object() noexcept;
const array* empty_array() noexcept;
// -- parsing ------------------------------------------------------------------
// Specialization for parsers operating on mutable character sequences.
using mutable_string_parser_state = parser_state<char*>;
// Specialization for parsers operating on files.
using file_parser_state = parser_state<std::istreambuf_iterator<char>>;
// Parses the input string and makes a deep copy of all strings.
value* parse(string_parser_state& ps, monotonic_buffer_resource* storage);
// Parses the input string and makes a deep copy of all strings.
value* parse(file_parser_state& ps, monotonic_buffer_resource* storage);
// Parses the input file and makes a deep copy of all strings.
value* parse(string_parser_state& ps, monotonic_buffer_resource* storage);
// Parses the input and makes a shallow copy of strings whenever possible.
// Strings that do not have escaped characters are not copied, other strings
// will be copied.
value* parse_shallow(string_parser_state& ps,
monotonic_buffer_resource* storage);
// Parses the input and makes a shallow copy of all strings. Strings with
// escaped characters are decoded in place.
value* parse_in_situ(mutable_string_parser_state& ps,
monotonic_buffer_resource* storage);
// -- printing -----------------------------------------------------------------
template <class Buffer>
static void print_to(Buffer& buf, std::string_view str) {
buf.insert(buf.end(), str.begin(), str.end());
}
template <class Buffer>
static void print_nl_to(Buffer& buf, size_t indentation) {
buf.push_back('\n');
buf.insert(buf.end(), indentation, ' ');
}
template <class Buffer>
void print_to(Buffer& buf, const array& arr, size_t indentation_factor,
size_t offset = 0);
template <class Buffer>
void print_to(Buffer& buf, const object& obj, size_t indentation_factor,
size_t offset = 0);
template <class Buffer>
void print_to(Buffer& buf, const value& val, size_t indentation_factor,
size_t offset = 0) {
using namespace std::literals;
switch (val.data.index()) {
case value::integer_index:
print(buf, std::get<int64_t>(val.data));
break;
case value::double_index:
print(buf, std::get<double>(val.data));
break;
case value::bool_index:
print(buf, std::get<bool>(val.data));
break;
case value::string_index:
print_escaped(buf, std::get<std::string_view>(val.data));
break;
case value::array_index:
print_to(buf, std::get<array>(val.data), indentation_factor, offset);
break;
case value::object_index:
print_to(buf, std::get<object>(val.data), indentation_factor, offset);
break;
default:
print_to(buf, "null"sv);
}
}
template <class Buffer>
void print_to(Buffer& buf, const array& arr, size_t indentation_factor,
size_t offset) {
using namespace std::literals;
if (arr.empty()) {
print_to(buf, "[]"sv);
} else if (indentation_factor == 0) {
buf.push_back('[');
auto i = arr.begin();
print_to(buf, *i, 0);
auto e = arr.end();
while (++i != e) {
print_to(buf, ", "sv);
print_to(buf, *i, 0);
}
buf.push_back(']');
} else {
buf.push_back('[');
auto new_offset = indentation_factor + offset;
print_nl_to(buf, new_offset);
auto i = arr.begin();
print_to(buf, *i, indentation_factor, new_offset);
auto e = arr.end();
while (++i != e) {
buf.push_back(',');
print_nl_to(buf, new_offset);
print_to(buf, *i, indentation_factor, new_offset);
}
print_nl_to(buf, offset);
buf.push_back(']');
}
}
template <class Buffer>
void print_to(Buffer& buf, const object& obj, size_t indentation_factor,
size_t offset) {
using namespace std::literals;
auto print_member = [&](const value::member& kvp, size_t new_offset) {
print_escaped(buf, kvp.key);
print_to(buf, ": "sv);
print_to(buf, *kvp.val, indentation_factor, new_offset);
};
if (obj.empty()) {
print_to(buf, "{}"sv);
} else if (indentation_factor == 0) {
buf.push_back('{');
auto i = obj.begin();
print_member(*i, offset);
auto e = obj.end();
while (++i != e) {
print_to(buf, ", "sv);
print_member(*i, offset);
}
buf.push_back('}');
} else {
buf.push_back('{');
auto new_offset = indentation_factor + offset;
print_nl_to(buf, new_offset);
auto i = obj.begin();
print_member(*i, new_offset);
auto e = obj.end();
while (++i != e) {
buf.push_back(',');
print_nl_to(buf, new_offset);
print_member(*i, new_offset);
}
print_nl_to(buf, offset);
buf.push_back('}');
}
}
} // namespace caf::detail::json
......@@ -55,20 +55,20 @@ public:
using other = allocator<U>;
};
explicit allocator(monotonic_buffer_resource* mbr) : mbr_(mbr) {
constexpr explicit allocator(monotonic_buffer_resource* mbr) : mbr_(mbr) {
// nop
}
allocator() : mbr_(nullptr) {
constexpr allocator() : mbr_(nullptr) {
// nop
}
allocator(const allocator&) = default;
constexpr allocator(const allocator&) = default;
allocator& operator=(const allocator&) = default;
constexpr allocator& operator=(const allocator&) = default;
template <class U>
allocator(const allocator<U>& other) : mbr_(other.resource()) {
constexpr allocator(const allocator<U>& other) : mbr_(other.resource()) {
// nop
}
......
......@@ -110,6 +110,11 @@ class ipv4_subnet;
class ipv6_address;
class ipv6_endpoint;
class ipv6_subnet;
class json_array;
class json_object;
class json_reader;
class json_value;
class json_writer;
class local_actor;
class mailbox_element;
class message;
......
// 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/json_array.hpp"
#include "caf/json_builder.hpp"
#include "caf/json_object.hpp"
#include "caf/json_reader.hpp"
#include "caf/json_value.hpp"
#include "caf/json_writer.hpp"
// 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/detail/core_export.hpp"
#include "caf/detail/json.hpp"
#include "caf/fwd.hpp"
#include "caf/json_value.hpp"
#include <iterator>
#include <memory>
namespace caf {
/// Represents a JSON array.
class CAF_CORE_EXPORT json_array {
public:
// -- friends ----------------------------------------------------------------
friend class json_value;
// -- member types -----------------------------------------------------------
class const_iterator {
public:
using difference_type = ptrdiff_t;
using value_type = json_value;
using pointer = value_type*;
using reference = value_type&;
using iterator_category = std::forward_iterator_tag;
const_iterator(detail::json::value::array::const_iterator iter,
detail::json::storage* storage)
: iter_(iter), storage_(storage) {
// nop
}
const_iterator() noexcept : storage_(nullptr) {
// nop
}
const_iterator(const const_iterator&) = default;
const_iterator& operator=(const const_iterator&) = default;
json_value value() const noexcept {
return json_value{std::addressof(*iter_), storage_};
}
json_value operator*() const noexcept {
return value();
}
const_iterator& operator++() noexcept {
++iter_;
return *this;
}
const_iterator operator++(int) noexcept {
return {iter_++, storage_};
}
bool equal_to(const const_iterator& other) const noexcept {
return iter_ == other.iter_;
}
private:
detail::json::value::array::const_iterator iter_;
detail::json::storage* storage_;
};
// -- constructors, destructors, and assignment operators --------------------
json_array() noexcept : arr_(detail::json::empty_array()) {
// nop
}
json_array(json_array&&) noexcept = default;
json_array(const json_array&) noexcept = default;
json_array& operator=(json_array&&) noexcept = default;
json_array& operator=(const json_array&) noexcept = default;
// -- properties -------------------------------------------------------------
/// Checks whether the array has no members.
bool empty() const noexcept {
return arr_->empty();
}
/// Alias for @c empty.
bool is_empty() const noexcept {
return empty();
}
/// Returns the number of key-value pairs in this array.
size_t size() const noexcept {
return arr_->size();
}
const_iterator begin() const noexcept {
return {arr_->begin(), storage_.get()};
}
const_iterator end() const noexcept {
return {arr_->end(), storage_.get()};
}
// -- printing ---------------------------------------------------------------
template <class Buffer>
void print_to(Buffer& buf, size_t indentation_factor = 0) const {
detail::json::print_to(buf, *arr_, indentation_factor);
}
// -- serialization ----------------------------------------------------------
template <class Inspector>
friend bool inspect(Inspector& inspector, json_array& arr) {
if constexpr (Inspector::is_loading) {
auto storage = make_counted<detail::json::storage>();
auto* internal_arr = detail::json::make_array(storage);
if (!detail::json::load(inspector, *internal_arr, storage))
return false;
arr = json_array{internal_arr, std::move(storage)};
return true;
} else {
return detail::json::save(inspector, *arr.arr_);
}
}
private:
json_array(const detail::json::value::array* obj,
detail::json::storage_ptr sptr) noexcept
: arr_(obj), storage_(sptr) {
// nop
}
const detail::json::value::array* arr_ = nullptr;
detail::json::storage_ptr storage_;
};
// -- free functions -----------------------------------------------------------
inline bool operator==(const json_array::const_iterator& lhs,
const json_array::const_iterator& rhs) noexcept {
return lhs.equal_to(rhs);
}
inline bool operator!=(const json_array::const_iterator& lhs,
const json_array::const_iterator& rhs) noexcept {
return !lhs.equal_to(rhs);
}
inline bool operator==(const json_array& lhs, const json_array& rhs) noexcept {
return std::equal(lhs.begin(), lhs.end(), rhs.begin(), rhs.end());
}
inline bool operator!=(const json_array& lhs, const json_array& rhs) noexcept {
return !(lhs == rhs);
}
/// @relates json_array
CAF_CORE_EXPORT std::string to_string(const json_array& arr);
} // namespace caf
// 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/detail/core_export.hpp"
#include "caf/detail/json.hpp"
#include "caf/json_value.hpp"
#include "caf/json_writer.hpp"
#include "caf/serializer.hpp"
namespace caf {
/// Serializes an inspectable object to a @ref json_value.
class json_builder : public serializer {
public:
// -- member types -----------------------------------------------------------
using super = serializer;
using type = json_writer::type;
// -- constructors, destructors, and assignment operators --------------------
json_builder();
explicit json_builder(actor_system& sys);
explicit json_builder(execution_unit* ctx);
json_builder(const json_builder&) = delete;
json_builder& operator=(const json_builder&) = delete;
~json_builder() override;
// -- properties -------------------------------------------------------------
/// Returns whether the writer omits empty fields entirely (true) or renders
/// empty fields as `$field: null` (false).
[[nodiscard]] bool skip_empty_fields() const noexcept {
return skip_empty_fields_;
}
/// Configures whether the writer omits empty fields.
void skip_empty_fields(bool value) noexcept {
skip_empty_fields_ = value;
}
/// Returns whether the writer omits '@type' annotations for JSON objects.
[[nodiscard]] bool skip_object_type_annotation() const noexcept {
return skip_object_type_annotation_;
}
/// Configures whether the writer omits '@type' annotations for JSON objects.
void skip_object_type_annotation(bool value) noexcept {
skip_object_type_annotation_ = value;
}
/// Returns the suffix for generating type annotation fields for variant
/// fields. For example, CAF inserts field called "@foo${field_type_suffix}"
/// for a variant field called "foo".
[[nodiscard]] std::string_view field_type_suffix() const noexcept {
return field_type_suffix_;
}
/// Configures whether the writer omits empty fields.
void field_type_suffix(std::string_view suffix) noexcept {
field_type_suffix_ = suffix;
}
// -- modifiers --------------------------------------------------------------
/// Restores the writer to its initial state.
void reset();
/// Seals the JSON value, i.e., rendering it immutable, and returns it. After
/// calling this member function, the @ref json_builder is in a moved-from
/// state and users may only call @c reset to start a new building process or
/// destroy this instance.
json_value seal();
// -- overrides --------------------------------------------------------------
bool begin_object(type_id_t type, std::string_view name) override;
bool end_object() override;
bool begin_field(std::string_view) override;
bool begin_field(std::string_view name, bool is_present) override;
bool begin_field(std::string_view name, span<const type_id_t> types,
size_t index) override;
bool begin_field(std::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(std::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_view x) override;
bool value(const std::u16string& x) override;
bool value(const std::u32string& x) override;
bool value(span<const std::byte> x) override;
private:
// -- implementation details -------------------------------------------------
template <class T>
bool number(T);
using key_type = std::string_view;
// -- state management -------------------------------------------------------
void init();
// Returns the current top of the stack or `null` if empty.
type top();
// Returns the current top of the stack or `null` if empty.
template <class T = detail::json::value>
T* top_ptr();
// Returns the current top-level object.
detail::json::object* top_obj();
// Enters a new level of nesting.
void push(detail::json::value*, type);
// Enters a new level of nesting with type member.
void push(detail::json::value::member*);
// Enters a new level of nesting with type key.
void push(key_type*);
// Backs up one level of nesting.
bool pop();
// Backs up one level of nesting but checks that current top is `t` before.
bool pop_if(type t);
// Sets an error reason that the inspector failed to write a t.
void fail(type t);
// Checks whether any element in the stack has the type `object`.
bool inside_object() const noexcept;
// -- member variables -------------------------------------------------------
// Our output.
detail::json::value* val_;
// Storage for the assembled output.
detail::json::storage_ptr storage_;
struct entry {
union {
detail::json::value* val_ptr;
detail::json::member* mem_ptr;
key_type* key_ptr;
};
type t;
entry(detail::json::value* ptr, type ptr_type) noexcept {
val_ptr = ptr;
t = ptr_type;
}
explicit entry(detail::json::member* ptr) noexcept {
mem_ptr = ptr;
t = type::member;
}
explicit entry(key_type* ptr) noexcept {
key_ptr = ptr;
t = type::key;
}
entry(const entry&) noexcept = default;
entry& operator=(const entry&) noexcept = default;
};
// Bookkeeping for where we are in the current object.
std::vector<entry> stack_;
// Configures whether we omit empty fields entirely (true) or render empty
// fields as `$field: null` (false).
bool skip_empty_fields_ = json_writer::skip_empty_fields_default;
// Configures whether we omit the top-level '@type' annotation.
bool skip_object_type_annotation_ = false;
std::string_view field_type_suffix_ = json_writer::field_type_suffix_default;
};
} // namespace caf
// 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/detail/core_export.hpp"
#include "caf/detail/json.hpp"
#include "caf/fwd.hpp"
#include "caf/json_value.hpp"
#include <iterator>
namespace caf {
/// Represents a JSON object.
class CAF_CORE_EXPORT json_object {
public:
// -- friends ----------------------------------------------------------------
friend class json_value;
// -- member types -----------------------------------------------------------
class const_iterator {
public:
using difference_type = ptrdiff_t;
using value_type = std::pair<std::string_view, json_value>;
using pointer = value_type*;
using reference = value_type&;
using iterator_category = std::forward_iterator_tag;
const_iterator(detail::json::value::object::const_iterator iter,
detail::json::storage* storage)
: iter_(iter), storage_(storage) {
// nop
}
const_iterator() noexcept = default;
const_iterator(const const_iterator&) = default;
const_iterator& operator=(const const_iterator&) = default;
std::string_view key() const noexcept {
return iter_->key;
}
json_value value() const noexcept {
return json_value{iter_->val, storage_};
}
value_type operator*() const noexcept {
return {key(), value()};
}
const_iterator& operator++() noexcept {
++iter_;
return *this;
}
const_iterator operator++(int) noexcept {
return {iter_++, storage_};
}
bool equal_to(const const_iterator& other) const noexcept {
return iter_ == other.iter_;
}
private:
detail::json::value::object::const_iterator iter_;
detail::json::storage* storage_ = nullptr;
};
// -- constructors, destructors, and assignment operators --------------------
json_object() noexcept : obj_(detail::json::empty_object()) {
// nop
}
json_object(json_object&&) noexcept = default;
json_object(const json_object&) noexcept = default;
json_object& operator=(json_object&&) noexcept = default;
json_object& operator=(const json_object&) noexcept = default;
// -- properties -------------------------------------------------------------
/// Checks whether the object has no members.
bool empty() const noexcept {
return !obj_ || obj_->empty();
}
/// Alias for @c empty.
bool is_empty() const noexcept {
return empty();
}
/// Returns the number of key-value pairs in this object.
size_t size() const noexcept {
return obj_ ? obj_->size() : 0u;
}
/// Returns the value for @p key or an @c undefined value if the object does
/// not contain a value for @p key.
json_value value(std::string_view key) const;
const_iterator begin() const noexcept {
return {obj_->begin(), storage_.get()};
}
const_iterator end() const noexcept {
return {obj_->end(), storage_.get()};
}
// -- printing ---------------------------------------------------------------
template <class Buffer>
void print_to(Buffer& buf, size_t indentation_factor = 0) const {
detail::json::print_to(buf, *obj_, indentation_factor);
}
// -- serialization ----------------------------------------------------------
template <class Inspector>
friend bool inspect(Inspector& inspector, json_object& obj) {
if constexpr (Inspector::is_loading) {
auto storage = make_counted<detail::json::storage>();
auto* internal_obj = detail::json::make_object(storage);
if (!detail::json::load(inspector, *internal_obj, storage))
return false;
obj = json_object{internal_obj, std::move(storage)};
return true;
} else {
return detail::json::save(inspector, *obj.obj_);
}
}
private:
json_object(const detail::json::value::object* obj,
detail::json::storage_ptr sptr) noexcept
: obj_(obj), storage_(sptr) {
// nop
}
const detail::json::value::object* obj_ = nullptr;
detail::json::storage_ptr storage_;
};
inline bool operator==(const json_object::const_iterator& lhs,
const json_object::const_iterator& rhs) noexcept {
return lhs.equal_to(rhs);
}
inline bool operator!=(const json_object::const_iterator& lhs,
const json_object::const_iterator& rhs) noexcept {
return !lhs.equal_to(rhs);
}
inline bool operator==(const json_object& lhs,
const json_object& rhs) noexcept {
return std::equal(lhs.begin(), lhs.end(), rhs.begin(), rhs.end());
}
inline bool operator!=(const json_object& lhs,
const json_object& rhs) noexcept {
return !(lhs == rhs);
}
/// @relates json_object
CAF_CORE_EXPORT std::string to_string(const json_object& val);
} // namespace caf
......@@ -133,6 +133,17 @@ public:
/// @note Implicitly calls `reset`.
bool load(std::string_view json_text);
/// Parses the content of the file under the given @p path. After loading the
/// content of the JSON file, the reader is ready for attempting to
/// deserialize inspectable objects.
/// @note Implicitly calls `reset`.
bool load_file(const char* path);
/// @copydoc load_file
bool load_file(const std::string& path) {
return load_file(path.c_str());
}
/// 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.
......
// 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/detail/core_export.hpp"
#include "caf/detail/json.hpp"
#include "caf/fwd.hpp"
#include "caf/make_counted.hpp"
#include <string>
#include <string_view>
namespace caf {
/// Represents an immutable JSON value.
class CAF_CORE_EXPORT json_value {
public:
// -- constructors, destructors, and assignment operators --------------------
json_value() noexcept : val_(detail::json::null_value()) {
// nop
}
json_value(const detail::json::value* val,
detail::json::storage_ptr sptr) noexcept
: val_(val), storage_(sptr) {
// nop
}
json_value(json_value&&) noexcept = default;
json_value(const json_value&) noexcept = default;
json_value& operator=(json_value&&) noexcept = default;
json_value& operator=(const json_value&) noexcept = default;
// -- factories --------------------------------------------------------------
static json_value undefined() noexcept {
return json_value{detail::json::undefined_value(), nullptr};
}
// -- properties -------------------------------------------------------------
/// Checks whether the value is @c null.
bool is_null() const noexcept {
return val_->is_null();
}
/// Checks whether the value is undefined. This special state indicates that a
/// previous key lookup failed.
bool is_undefined() const noexcept {
return val_->is_undefined();
}
/// Checks whether the value is an @c int64_t.
bool is_integer() const noexcept {
return val_->is_integer();
}
/// Checks whether the value is a @c double.
bool is_double() const noexcept {
return val_->is_double();
}
/// Checks whether the value is a number, i.e., an @c int64_t or a @c double.
bool is_number() const noexcept {
return is_integer() || is_double();
}
/// Checks whether the value is a @c bool.
bool is_bool() const noexcept {
return val_->is_bool();
}
/// Checks whether the value is a JSON string (@c std::string_view).
bool is_string() const noexcept {
return val_->is_string();
}
/// Checks whether the value is an JSON array.
bool is_array() const noexcept {
return val_->is_array();
}
/// Checks whether the value is a JSON object.
bool is_object() const noexcept {
return val_->is_object();
}
// -- conversion -------------------------------------------------------------
int64_t to_integer(int64_t fallback = 0) const;
double to_double(double fallback = 0.0) const;
bool to_bool(bool fallback = false) const;
std::string_view to_string() const;
std::string_view to_string(std::string_view fallback) const;
json_array to_array() const;
json_array to_array(json_array fallback) const;
json_object to_object() const;
json_object to_object(json_object fallback) const;
// -- comparison -------------------------------------------------------------
bool equal_to(const json_value& other) const noexcept;
// -- parsing ----------------------------------------------------------------
/// Attempts to parse @p str as JSON input into a self-contained value.
static expected<json_value> parse(std::string_view str);
/// Attempts to parse @p str as JSON input into a value that avoids copies
/// where possible by pointing into @p str.
/// @warning The returned @ref json_value may hold pointers into @p str. Thus,
/// the input *must* outlive the @ref json_value and any other JSON
/// objects created from that value.
static expected<json_value> parse_shallow(std::string_view str);
/// Attempts to parse @p str as JSON input. Decodes JSON in place and points
/// back into the @p str for all strings in the JSON input.
/// @warning The returned @ref json_value may hold pointers into @p str. Thus,
/// the input *must* outlive the @ref json_value and any other JSON
/// objects created from that value.
static expected<json_value> parse_in_situ(std::string& str);
/// Attempts to parse the content of the file at @p path as JSON input into a
/// self-contained value.
static expected<json_value> parse_file(const char* path);
/// @copydoc parse_file
static expected<json_value> parse_file(const std::string& path);
// -- printing ---------------------------------------------------------------
template <class Buffer>
void print_to(Buffer& buf, size_t indentation_factor = 0) const {
detail::json::print_to(buf, *val_, indentation_factor);
}
// -- serialization ----------------------------------------------------------
template <class Inspector>
friend bool inspect(Inspector& inspector, json_value& val) {
if constexpr (Inspector::is_loading) {
auto storage = make_counted<detail::json::storage>();
auto* internal_val = detail::json::make_value(storage);
if (!detail::json::load(inspector, *internal_val, storage))
return false;
val = json_value{internal_val, std::move(storage)};
return true;
} else {
return detail::json::save(inspector, *val.val_);
}
}
private:
const detail::json::value* val_;
detail::json::storage_ptr storage_;
};
// -- free functions -----------------------------------------------------------
inline bool operator==(const json_value& lhs, const json_value& rhs) {
return lhs.equal_to(rhs);
}
inline bool operator!=(const json_value& lhs, const json_value& rhs) {
return !(lhs == rhs);
}
/// @relates json_value
CAF_CORE_EXPORT std::string to_string(const json_value& val);
} // namespace caf
......@@ -210,7 +210,7 @@ private:
void init();
// Returns the current top of the stack or `null_literal` if empty.
// Returns the current top of the stack or `null` if empty.
type top();
// Enters a new level of nesting.
......@@ -300,4 +300,12 @@ private:
const type_id_mapper* mapper_ = &default_mapper_;
};
/// @relates json_writer::type
CAF_CORE_EXPORT std::string_view as_json_type_name(json_writer::type t);
/// @relates json_writer::type
constexpr bool can_morph(json_writer::type from, json_writer::type to) {
return from == json_writer::type::element && to != json_writer::type::member;
}
} // namespace caf
......@@ -16,6 +16,8 @@ namespace caf {
/// Stores all information necessary for implementing an FSM-based parser.
template <class Iterator, class Sentinel>
struct parser_state {
using iterator_type = Iterator;
/// Current position of the parser.
Iterator i;
......
......@@ -405,7 +405,6 @@ CAF_BEGIN_TYPE_ID_BLOCK(core_module, 0)
CAF_ADD_TYPE_ID(core_module, (caf::cow_string))
CAF_ADD_TYPE_ID(core_module, (caf::cow_u16string))
CAF_ADD_TYPE_ID(core_module, (caf::cow_u32string))
CAF_ADD_TYPE_ID(core_module, (caf::dictionary<caf::config_value>) )
CAF_ADD_TYPE_ID(core_module, (caf::down_msg))
CAF_ADD_TYPE_ID(core_module, (caf::error))
CAF_ADD_TYPE_ID(core_module, (caf::exit_msg))
......@@ -419,6 +418,9 @@ CAF_BEGIN_TYPE_ID_BLOCK(core_module, 0)
CAF_ADD_TYPE_ID(core_module, (caf::ipv6_address))
CAF_ADD_TYPE_ID(core_module, (caf::ipv6_endpoint))
CAF_ADD_TYPE_ID(core_module, (caf::ipv6_subnet))
CAF_ADD_TYPE_ID(core_module, (caf::json_array))
CAF_ADD_TYPE_ID(core_module, (caf::json_object))
CAF_ADD_TYPE_ID(core_module, (caf::json_value))
CAF_ADD_TYPE_ID(core_module, (caf::message))
CAF_ADD_TYPE_ID(core_module, (caf::message_id))
CAF_ADD_TYPE_ID(core_module, (caf::node_down_msg))
......@@ -426,6 +428,7 @@ CAF_BEGIN_TYPE_ID_BLOCK(core_module, 0)
CAF_ADD_TYPE_ID(core_module, (caf::none_t))
CAF_ADD_TYPE_ID(core_module, (caf::pec))
CAF_ADD_TYPE_ID(core_module, (caf::sec))
CAF_ADD_TYPE_ID(core_module, (caf::settings))
CAF_ADD_TYPE_ID(core_module, (caf::shared_action_ptr))
CAF_ADD_TYPE_ID(core_module, (caf::stream))
CAF_ADD_TYPE_ID(core_module, (caf::stream_abort_msg))
......
......@@ -4,8 +4,11 @@
#include "caf/detail/json.hpp"
#include <cstring>
#include <iterator>
#include <memory>
#include <numeric>
#include <streambuf>
#include "caf/config.hpp"
#include "caf/detail/parser/chars.hpp"
......@@ -14,6 +17,7 @@
#include "caf/detail/parser/read_number.hpp"
#include "caf/detail/scope_guard.hpp"
#include "caf/pec.hpp"
#include "caf/span.hpp"
CAF_PUSH_UNUSED_LABEL_WARNING
......@@ -23,6 +27,115 @@ namespace {
constexpr size_t max_nesting_level = 128;
size_t do_unescape(const char* i, const char* e, char* out) {
size_t new_size = 0;
while (i != e) {
switch (*i) {
default:
if (i != out) {
*out++ = *i++;
} else {
++out;
++i;
}
++new_size;
break;
case '\\':
if (++i != e) {
switch (*i) {
case '"':
*out++ = '"';
break;
case '\\':
*out++ = '\\';
break;
case 'b':
*out++ = '\b';
break;
case 'f':
*out++ = '\f';
break;
case 'n':
*out++ = '\n';
break;
case 'r':
*out++ = '\r';
break;
case 't':
*out++ = '\t';
break;
case 'v':
*out++ = '\v';
break;
default:
// TODO: support control characters in \uXXXX notation.
*out++ = '?';
}
++i;
++new_size;
}
}
}
return new_size;
}
std::string_view as_str_view(const char* first, const char* last) {
return {first, static_cast<size_t>(last - first)};
}
struct regular_unescaper {
std::string_view operator()(caf::detail::monotonic_buffer_resource* storage,
const char* first, const char* last,
bool is_escaped) const {
auto len = static_cast<size_t>(last - first);
caf::detail::monotonic_buffer_resource::allocator<char> alloc{storage};
auto* str_buf = alloc.allocate(len);
if (!is_escaped) {
strncpy(str_buf, first, len);
return std::string_view{str_buf, len};
}
auto unescaped_size = do_unescape(first, last, str_buf);
return std::string_view{str_buf, unescaped_size};
}
};
struct shallow_unescaper {
std::string_view operator()(caf::detail::monotonic_buffer_resource* storage,
const char* first, const char* last,
bool is_escaped) const {
if (!is_escaped)
return as_str_view(first, last);
caf::detail::monotonic_buffer_resource::allocator<char> alloc{storage};
auto* str_buf = alloc.allocate(static_cast<size_t>(last - first));
auto unescaped_size = do_unescape(first, last, str_buf);
return std::string_view{str_buf, unescaped_size};
}
};
struct in_situ_unescaper {
std::string_view operator()(caf::detail::monotonic_buffer_resource*,
char* first, char* last, bool is_escaped) const {
if (!is_escaped)
return as_str_view(first, last);
auto unescaped_size = do_unescape(first, last, first);
return std::string_view{first, unescaped_size};
}
};
template <class Escaper, class Consumer, class Iterator>
void assign_value(Escaper escaper, Consumer& consumer, Iterator first,
Iterator last, bool is_escaped) {
auto iter2ptr = [](Iterator iter) {
if constexpr (std::is_pointer_v<Iterator>)
return iter;
else
return std::addressof(*iter);
};
auto str = escaper(consumer.storage, iter2ptr(first), iter2ptr(last),
is_escaped);
consumer.value(str);
}
} // namespace
namespace caf::detail::parser {
......@@ -46,6 +159,7 @@ struct val_consumer {
};
struct key_consumer {
monotonic_buffer_resource* storage;
std::string_view* ptr;
void value(std::string_view str) {
......@@ -58,7 +172,7 @@ struct member_consumer {
json::member* ptr;
key_consumer begin_key() {
return {std::addressof(ptr->key)};
return {storage, std::addressof(ptr->key)};
}
val_consumer begin_val() {
......@@ -71,8 +185,8 @@ struct obj_consumer {
json::object* ptr;
member_consumer begin_member() {
ptr->emplace_back();
return {ptr->get_allocator().resource(), std::addressof(ptr->back())};
auto& new_member = ptr->emplace_back();
return {ptr->get_allocator().resource(), &new_member};
}
};
......@@ -80,30 +194,25 @@ struct arr_consumer {
json::array* ptr;
val_consumer begin_value() {
ptr->emplace_back();
return {ptr->get_allocator().resource(), std::addressof(ptr->back())};
auto& new_element = ptr->emplace_back();
return {ptr->get_allocator().resource(), &new_element};
}
};
arr_consumer val_consumer::begin_array() {
ptr->data = json::array(json::value::array_allocator{storage});
auto& arr = std::get<json::array>(ptr->data);
arr.reserve(16);
return {&arr};
}
obj_consumer val_consumer::begin_object() {
ptr->data = json::object(json::value::member_allocator{storage});
ptr->data = json::object(json::value::object_allocator{storage});
auto& obj = std::get<json::object>(ptr->data);
obj.reserve(16);
return {&obj};
}
void read_value(string_parser_state& ps, size_t nesting_level,
val_consumer consumer);
template <class Consumer>
void read_json_null_or_nan(string_parser_state& ps, Consumer consumer) {
template <class ParserState, class Consumer>
void read_json_null_or_nan(ParserState& ps, Consumer consumer) {
enum { nil, is_null, is_nan };
auto res_type = nil;
auto g = make_scope_guard([&] {
......@@ -141,9 +250,16 @@ void read_json_null_or_nan(string_parser_state& ps, Consumer consumer) {
// clang-format on
}
template <class Consumer>
void read_json_string(string_parser_state& ps, Consumer consumer) {
std::string_view::iterator first;
// If we have an iterator into a contiguous memory block, we simply store the
// iterator position and use the escaper to decide whether we make regular,
// shallow or in-situ copies. Otherwise, we use the scratch-space and decode the
// string while parsing.
template <class ParserState, class Unescaper, class Consumer>
void read_json_string(ParserState& ps, unit_t, Unescaper escaper,
Consumer consumer) {
using iterator_t = typename ParserState::iterator_type;
iterator_t first;
// clang-format off
start();
state(init) {
......@@ -152,14 +268,17 @@ void read_json_string(string_parser_state& ps, Consumer consumer) {
}
state(read_chars) {
transition(escape, '\\')
transition(done, '"',
consumer.value(std::string_view{
std::addressof(*first), static_cast<size_t>(ps.i - first)}))
transition(done, '"', assign_value(escaper, consumer, first, ps.i, false))
transition(read_chars, any_char)
}
state(read_chars_after_escape) {
transition(escape, '\\')
transition(done, '"', assign_value(escaper, consumer, first, ps.i, true))
transition(read_chars_after_escape, any_char)
}
state(escape) {
// TODO: Add support for JSON's \uXXXX escaping.
transition(read_chars, "\"\\/bfnrt")
transition(read_chars_after_escape, "\"\\/bfnrtv")
}
term_state(done) {
transition(done, " \t\n")
......@@ -168,17 +287,57 @@ void read_json_string(string_parser_state& ps, Consumer consumer) {
// clang-format on
}
void read_member(string_parser_state& ps, size_t nesting_level,
template <class ParserState, class Unescaper, class Consumer>
void read_json_string(ParserState& ps, std::vector<char>& scratch_space,
Unescaper escaper, Consumer consumer) {
scratch_space.clear();
// clang-format off
start();
state(init) {
transition(init, " \t\n")
transition(read_chars, '"')
}
state(read_chars) {
transition(escape, '\\')
transition(done, '"',
assign_value(escaper, consumer, scratch_space.begin(),
scratch_space.end(), false))
transition(read_chars, any_char, scratch_space.push_back(ch))
}
state(escape) {
// TODO: Add support for JSON's \uXXXX escaping.
transition(read_chars, '"', scratch_space.push_back('"'))
transition(read_chars, '\\', scratch_space.push_back('\\'))
transition(read_chars, 'b', scratch_space.push_back('\b'))
transition(read_chars, 'f', scratch_space.push_back('\f'))
transition(read_chars, 'n', scratch_space.push_back('\n'))
transition(read_chars, 'r', scratch_space.push_back('\r'))
transition(read_chars, 't', scratch_space.push_back('\t'))
transition(read_chars, 'v', scratch_space.push_back('\v'))
}
term_state(done) {
transition(done, " \t\n")
}
fin();
// clang-format on
}
template <class ParserState, class ScratchSpace, class Unescaper>
void read_member(ParserState& ps, ScratchSpace& scratch_space,
Unescaper unescaper, size_t nesting_level,
member_consumer consumer) {
// clang-format off
start();
state(init) {
transition(init, " \t\n")
fsm_epsilon(read_json_string(ps, consumer.begin_key()), after_key, '"')
fsm_epsilon(read_json_string(ps, scratch_space, unescaper,
consumer.begin_key()),
after_key, '"')
}
state(after_key) {
transition(after_key, " \t\n")
fsm_transition(read_value(ps, nesting_level, consumer.begin_val()),
fsm_transition(read_value(ps, scratch_space, unescaper, nesting_level,
consumer.begin_val()),
done, ':')
}
term_state(done) {
......@@ -188,7 +347,9 @@ void read_member(string_parser_state& ps, size_t nesting_level,
// clang-format on
}
void read_json_object(string_parser_state& ps, size_t nesting_level,
template <class ParserState, class ScratchSpace, class Unescaper>
void read_json_object(ParserState& ps, ScratchSpace& scratch_space,
Unescaper unescaper, size_t nesting_level,
obj_consumer consumer) {
if (nesting_level >= max_nesting_level) {
ps.code = pec::nested_too_deeply;
......@@ -202,7 +363,8 @@ void read_json_object(string_parser_state& ps, size_t nesting_level,
}
state(has_open_brace) {
transition(has_open_brace, " \t\n")
fsm_epsilon(read_member(ps, nesting_level + 1, consumer.begin_member()),
fsm_epsilon(read_member(ps, scratch_space, unescaper, nesting_level + 1,
consumer.begin_member()),
after_member, '"')
transition(done, '}')
}
......@@ -213,7 +375,8 @@ void read_json_object(string_parser_state& ps, size_t nesting_level,
}
state(after_comma) {
transition(after_comma, " \t\n")
fsm_epsilon(read_member(ps, nesting_level + 1, consumer.begin_member()),
fsm_epsilon(read_member(ps, scratch_space, unescaper, nesting_level + 1,
consumer.begin_member()),
after_member, '"')
}
term_state(done) {
......@@ -223,7 +386,9 @@ void read_json_object(string_parser_state& ps, size_t nesting_level,
// clang-format on
}
void read_json_array(string_parser_state& ps, size_t nesting_level,
template <class ParserState, class ScratchSpace, class Unescaper>
void read_json_array(ParserState& ps, ScratchSpace& scratch_space,
Unescaper unescaper, size_t nesting_level,
arr_consumer consumer) {
if (nesting_level >= max_nesting_level) {
ps.code = pec::nested_too_deeply;
......@@ -238,7 +403,8 @@ void read_json_array(string_parser_state& ps, size_t nesting_level,
state(has_open_brace) {
transition(has_open_brace, " \t\n")
transition(done, ']')
fsm_epsilon(read_value(ps, nesting_level + 1, consumer.begin_value()),
fsm_epsilon(read_value(ps, scratch_space, unescaper, nesting_level + 1,
consumer.begin_value()),
after_value)
}
state(after_value) {
......@@ -248,7 +414,8 @@ void read_json_array(string_parser_state& ps, size_t nesting_level,
}
state(after_comma) {
transition(after_comma, " \t\n")
fsm_epsilon(read_value(ps, nesting_level + 1, consumer.begin_value()),
fsm_epsilon(read_value(ps, scratch_space, unescaper, nesting_level + 1,
consumer.begin_value()),
after_value)
}
term_state(done) {
......@@ -258,19 +425,24 @@ void read_json_array(string_parser_state& ps, size_t nesting_level,
// clang-format on
}
void read_value(string_parser_state& ps, size_t nesting_level,
template <class ParserState, class ScratchSpace, class Unescaper>
void read_value(ParserState& ps, ScratchSpace& scratch_space,
Unescaper unescaper, size_t nesting_level,
val_consumer consumer) {
// clang-format off
start();
state(init) {
transition(init, " \t\n")
fsm_epsilon(read_json_string(ps, consumer), done, '"')
fsm_epsilon(read_json_string(ps, scratch_space, unescaper, consumer),
done, '"')
fsm_epsilon(read_bool(ps, consumer), done, "ft")
fsm_epsilon(read_json_null_or_nan(ps, consumer), done, "n")
fsm_epsilon(read_number(ps, consumer), done, "+-.0123456789")
fsm_epsilon(read_json_object(ps, nesting_level, consumer.begin_object()),
fsm_epsilon(read_json_object(ps, scratch_space, unescaper, nesting_level,
consumer.begin_object()),
done, '{')
fsm_epsilon(read_json_array(ps, nesting_level, consumer.begin_array()),
fsm_epsilon(read_json_array(ps, scratch_space, unescaper, nesting_level,
consumer.begin_array()),
done, '[')
}
term_state(done) {
......@@ -288,9 +460,10 @@ namespace caf::detail::json {
namespace {
template <class T, class Allocator>
void init(std::vector<T, Allocator>* ptr, monotonic_buffer_resource* storage) {
new (ptr) std::vector<T, Allocator>(Allocator{storage});
template <class T>
void init(linked_list<T>* ptr, monotonic_buffer_resource* storage) {
using allocator_type = typename linked_list<T>::allocator_type;
new (ptr) linked_list<T>(allocator_type{storage});
}
void init(value* ptr, monotonic_buffer_resource*) {
......@@ -305,28 +478,103 @@ T* make_impl(monotonic_buffer_resource* storage) {
return result;
}
const value null_value_instance;
const value undefined_value_instance = value{undefined_t{}};
const object empty_object_instance;
const array empty_array_instance;
} // namespace
std::string_view realloc(std::string_view str, monotonic_buffer_resource* res) {
using alloc_t = detail::monotonic_buffer_resource::allocator<char>;
auto buf = alloc_t{res}.allocate(str.size());
strncpy(buf, str.data(), str.size());
return std::string_view{buf, str.size()};
}
std::string_view concat(std::initializer_list<std::string_view> xs,
monotonic_buffer_resource* res) {
auto get_size = [](size_t x, std::string_view str) { return x + str.size(); };
auto total_size = std::accumulate(xs.begin(), xs.end(), size_t{0}, get_size);
using alloc_t = detail::monotonic_buffer_resource::allocator<char>;
auto* buf = alloc_t{res}.allocate(total_size);
auto* pos = buf;
for (auto str : xs) {
strncpy(pos, str.data(), str.size());
pos += str.size();
}
return std::string_view{buf, total_size};
}
value* make_value(monotonic_buffer_resource* storage) {
return make_impl<value>(storage);
}
array* make_array(monotonic_buffer_resource* storage) {
auto result = make_impl<array>(storage);
result->reserve(16);
return result;
}
object* make_object(monotonic_buffer_resource* storage) {
auto result = make_impl<object>(storage);
result->reserve(16);
return result;
}
const value* null_value() noexcept {
return &null_value_instance;
}
const value* undefined_value() noexcept {
return &undefined_value_instance;
}
const object* empty_object() noexcept {
return &empty_object_instance;
}
const array* empty_array() noexcept {
return &empty_array_instance;
}
value* parse(string_parser_state& ps, monotonic_buffer_resource* storage) {
unit_t scratch_space;
regular_unescaper unescaper;
monotonic_buffer_resource::allocator<value> alloc{storage};
auto result = new (alloc.allocate(1)) value();
parser::read_value(ps, scratch_space, unescaper, 0, {storage, result});
return result;
}
value* parse(file_parser_state& ps, monotonic_buffer_resource* storage) {
std::vector<char> scratch_space;
scratch_space.reserve(64);
regular_unescaper unescaper;
monotonic_buffer_resource::allocator<value> alloc{storage};
auto result = new (alloc.allocate(1)) value();
parser::read_value(ps, scratch_space, unescaper, 0, {storage, result});
return result;
}
value* parse_shallow(string_parser_state& ps,
monotonic_buffer_resource* storage) {
unit_t scratch_space;
shallow_unescaper unescaper;
monotonic_buffer_resource::allocator<value> alloc{storage};
auto result = new (alloc.allocate(1)) value();
parser::read_value(ps, scratch_space, unescaper, 0, {storage, result});
return result;
}
value* parse_in_situ(mutable_string_parser_state& ps,
monotonic_buffer_resource* storage) {
unit_t scratch_space;
in_situ_unescaper unescaper;
monotonic_buffer_resource::allocator<value> alloc{storage};
auto result = new (alloc.allocate(1)) value();
parser::read_value(ps, 0, {storage, result});
parser::read_value(ps, scratch_space, unescaper, 0, {storage, result});
return result;
}
......
......@@ -21,6 +21,9 @@
#include "caf/ipv6_address.hpp"
#include "caf/ipv6_endpoint.hpp"
#include "caf/ipv6_subnet.hpp"
#include "caf/json_array.hpp"
#include "caf/json_object.hpp"
#include "caf/json_value.hpp"
#include "caf/message.hpp"
#include "caf/message_id.hpp"
#include "caf/node_id.hpp"
......
// 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.
#include "caf/json_array.hpp"
namespace caf {
std::string to_string(const json_array& arr) {
std::string result;
arr.print_to(result);
return result;
}
} // namespace caf
// 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.
#include "caf/json_builder.hpp"
#include "caf/detail/append_hex.hpp"
#include "caf/detail/print.hpp"
#include "caf/json_value.hpp"
#include <type_traits>
using namespace std::literals;
namespace caf {
namespace {
static constexpr const char class_name[] = "caf::json_builder";
} // namespace
// -- implementation details ---------------------------------------------------
template <class T>
bool json_builder::number(T x) {
switch (top()) {
case type::element: {
if constexpr (std::is_floating_point_v<T>) {
top_ptr()->data = static_cast<double>(x);
} else {
top_ptr()->data = static_cast<int64_t>(x);
}
pop();
return true;
}
case type::key: {
std::string str;
detail::print(str, x);
*top_ptr<key_type>() = detail::json::realloc(str, &storage_->buf);
return true;
}
case type::array: {
auto& new_entry = top_ptr<detail::json::array>()->emplace_back();
if constexpr (std::is_floating_point_v<T>) {
new_entry.data = static_cast<double>(x);
} else {
new_entry.data = static_cast<int64_t>(x);
}
return true;
}
default:
fail(type::number);
return false;
}
}
// -- constructors, destructors, and assignment operators ----------------------
json_builder::json_builder() : json_builder(nullptr) {
init();
}
json_builder::json_builder(actor_system& sys) : super(sys) {
init();
}
json_builder::json_builder(execution_unit* ctx) : super(ctx) {
init();
}
json_builder::~json_builder() {
// nop
}
// -- modifiers ----------------------------------------------------------------
void json_builder::reset() {
stack_.clear();
if (!storage_) {
storage_ = make_counted<detail::json::storage>();
} else {
storage_->buf.reclaim();
}
val_ = detail::json::make_value(storage_);
push(val_, type::element);
}
json_value json_builder::seal() {
return json_value{val_, std::move(storage_)};
}
// -- overrides ----------------------------------------------------------------
bool json_builder::begin_object(type_id_t id, std::string_view name) {
auto add_type_annotation = [this, id, name] {
CAF_ASSERT(top() == type::key);
*top_ptr<key_type>() = "@type"sv;
pop();
CAF_ASSERT(top() == type::element);
if (auto tname = query_type_name(id); !tname.empty()) {
top_ptr()->data = tname;
} else {
top_ptr()->data = name;
}
pop();
return true;
};
if (skip_object_type_annotation_ || inside_object())
return begin_associative_array(0);
else
return begin_associative_array(0) // Put opening paren, ...
&& begin_key_value_pair() // ... add implicit @type member, ..
&& add_type_annotation() // ... write content ...
&& end_key_value_pair(); // ... and wait for next field.
}
bool json_builder::end_object() {
return end_associative_array();
}
bool json_builder::begin_field(std::string_view name) {
if (begin_key_value_pair()) {
CAF_ASSERT(top() == type::key);
*top_ptr<key_type>() = name;
pop();
CAF_ASSERT(top() == type::element);
return true;
} else {
return false;
}
}
bool json_builder::begin_field(std::string_view name, bool is_present) {
if (skip_empty_fields_ && !is_present) {
auto t = top();
switch (t) {
case type::object:
push(static_cast<detail::json::member*>(nullptr));
return true;
default: {
std::string str = "expected object, found ";
str += as_json_type_name(t);
emplace_error(sec::runtime_error, class_name, __func__, std::move(str));
return false;
}
}
} else if (begin_key_value_pair()) {
CAF_ASSERT(top() == type::key);
*top_ptr<key_type>() = name;
pop();
CAF_ASSERT(top() == type::element);
if (!is_present) {
// We don't need to assign nullptr explicitly since it's the default.
pop();
}
return true;
} else {
return false;
}
}
bool json_builder::begin_field(std::string_view name,
span<const type_id_t> types, size_t index) {
if (index >= types.size()) {
emplace_error(sec::runtime_error, "index >= types.size()");
return false;
}
if (begin_key_value_pair()) {
if (auto tname = query_type_name(types[index]); !tname.empty()) {
auto& annotation = top_obj()->emplace_back();
annotation.key = detail::json::concat({"@"sv, name, field_type_suffix_},
&storage_->buf);
annotation.val = detail::json::make_value(storage_);
annotation.val->data = tname;
} else {
emplace_error(sec::runtime_error, "query_type_name failed");
return false;
}
CAF_ASSERT(top() == type::key);
*top_ptr<key_type>() = name;
pop();
CAF_ASSERT(top() == type::element);
return true;
} else {
return false;
}
}
bool json_builder::begin_field(std::string_view name, bool is_present,
span<const type_id_t> types, size_t index) {
if (is_present)
return begin_field(name, types, index);
else
return begin_field(name, is_present);
}
bool json_builder::end_field() {
return end_key_value_pair();
}
bool json_builder::begin_tuple(size_t size) {
return begin_sequence(size);
}
bool json_builder::end_tuple() {
return end_sequence();
}
bool json_builder::begin_key_value_pair() {
auto t = top();
switch (t) {
case type::object: {
auto* obj = top_ptr<detail::json::object>();
auto& new_member = obj->emplace_back();
new_member.val = detail::json::make_value(storage_);
push(&new_member);
push(new_member.val, type::element);
push(&new_member.key);
return true;
}
default: {
std::string str = "expected object, found ";
str += as_json_type_name(t);
emplace_error(sec::runtime_error, class_name, __func__, std::move(str));
return false;
}
}
}
bool json_builder::end_key_value_pair() {
return pop_if(type::member);
}
bool json_builder::begin_sequence(size_t) {
switch (top()) {
default:
emplace_error(sec::runtime_error, "unexpected begin_sequence");
return false;
case type::element: {
auto* val = top_ptr();
val->assign_array(storage_);
push(val, type::array);
return true;
}
case type::array: {
auto* arr = top_ptr<detail::json::array>();
auto& new_val = arr->emplace_back();
new_val.assign_array(storage_);
push(&new_val, type::array);
return true;
}
}
}
bool json_builder::end_sequence() {
return pop_if(type::array);
}
bool json_builder::begin_associative_array(size_t) {
switch (top()) {
default:
emplace_error(sec::runtime_error, class_name, __func__,
"unexpected begin_object or begin_associative_array");
return false;
case type::element:
top_ptr()->assign_object(storage_);
stack_.back().t = type::object;
return true;
case type::array: {
auto& new_val = top_ptr<detail::json::array>()->emplace_back();
new_val.assign_object(storage_);
push(&new_val, type::object);
return true;
}
}
}
bool json_builder::end_associative_array() {
return pop_if(type::object);
}
bool json_builder::value(std::byte x) {
return number(std::to_integer<uint8_t>(x));
}
bool json_builder::value(bool x) {
switch (top()) {
case type::element:
top_ptr()->data = x;
pop();
return true;
case type::key:
*top_ptr<key_type>() = x ? "true"sv : "false"sv;
return true;
case type::array: {
auto* arr = top_ptr<detail::json::array>();
arr->emplace_back().data = x;
return true;
}
default:
fail(type::boolean);
return false;
}
}
bool json_builder::value(int8_t x) {
return number(x);
}
bool json_builder::value(uint8_t x) {
return number(x);
}
bool json_builder::value(int16_t x) {
return number(x);
}
bool json_builder::value(uint16_t x) {
return number(x);
}
bool json_builder::value(int32_t x) {
return number(x);
}
bool json_builder::value(uint32_t x) {
return number(x);
}
bool json_builder::value(int64_t x) {
return number(x);
}
bool json_builder::value(uint64_t x) {
return number(x);
}
bool json_builder::value(float x) {
return number(x);
}
bool json_builder::value(double x) {
return number(x);
}
bool json_builder::value(long double x) {
return number(x);
}
bool json_builder::value(std::string_view x) {
switch (top()) {
case type::element:
top_ptr()->assign_string(x, storage_);
pop();
return true;
case type::key:
*top_ptr<key_type>() = detail::json::realloc(x, storage_);
pop();
return true;
case type::array: {
auto& new_val = top_ptr<detail::json::array>()->emplace_back();
new_val.assign_string(x, storage_);
return true;
}
default:
fail(type::string);
return false;
}
}
bool json_builder::value(const std::u16string&) {
emplace_error(sec::unsupported_operation,
"u16string not supported yet by caf::json_builder");
return false;
}
bool json_builder::value(const std::u32string&) {
emplace_error(sec::unsupported_operation,
"u32string not supported yet by caf::json_builder");
return false;
}
bool json_builder::value(span<const std::byte> x) {
std::vector<char> buf;
buf.reserve(x.size() * 2);
detail::append_hex(buf, reinterpret_cast<const void*>(x.data()), x.size());
auto hex_str = std::string_view{buf.data(), buf.size()};
switch (top()) {
case type::element:
top_ptr()->assign_string(hex_str, storage_);
pop();
return true;
case type::key:
*top_ptr<key_type>() = detail::json::realloc(hex_str, storage_);
pop();
return true;
case type::array: {
auto& new_val = top_ptr<detail::json::array>()->emplace_back();
new_val.assign_string(hex_str, storage_);
return true;
}
default:
fail(type::string);
return false;
}
}
// -- state management ---------------------------------------------------------
void json_builder::init() {
has_human_readable_format_ = true;
storage_ = make_counted<detail::json::storage>();
val_ = detail::json::make_value(storage_);
stack_.reserve(32);
push(val_, type::element);
}
json_builder::type json_builder::top() {
if (!stack_.empty())
return stack_.back().t;
else
return type::null;
}
template <class T>
T* json_builder::top_ptr() {
if constexpr (std::is_same_v<T, key_type>) {
return stack_.back().key_ptr;
} else if constexpr (std::is_same_v<T, detail::json::member>) {
return stack_.back().mem_ptr;
} else if constexpr (std::is_same_v<T, detail::json::object>) {
return &std::get<detail::json::object>(stack_.back().val_ptr->data);
} else if constexpr (std::is_same_v<T, detail::json::array>) {
return &std::get<detail::json::array>(stack_.back().val_ptr->data);
} else {
static_assert(std::is_same_v<T, detail::json::value>);
return stack_.back().val_ptr;
}
}
detail::json::object* json_builder::top_obj() {
auto is_obj = [](const entry& x) { return x.t == type::object; };
auto i = std::find_if(stack_.rbegin(), stack_.rend(), is_obj);
if (i != stack_.rend())
return &std::get<detail::json::object>(i->val_ptr->data);
CAF_RAISE_ERROR("json_builder::top_obj was unable to find an object");
}
void json_builder::push(detail::json::value* ptr, type t) {
stack_.emplace_back(ptr, t);
}
void json_builder::push(detail::json::value::member* ptr) {
stack_.emplace_back(ptr);
}
void json_builder::push(key_type* ptr) {
stack_.emplace_back(ptr);
}
// Backs up one level of nesting.
bool json_builder::pop() {
if (!stack_.empty()) {
stack_.pop_back();
return true;
} else {
std::string str = "pop() called with an empty stack: begin/end mismatch";
emplace_error(sec::runtime_error, std::move(str));
return false;
}
}
bool json_builder::pop_if(type t) {
if (!stack_.empty() && stack_.back().t == t) {
stack_.pop_back();
return true;
} else {
std::string str = "pop_if failed: expected ";
str += as_json_type_name(t);
if (stack_.empty()) {
str += ", found an empty stack";
} else {
str += ", found ";
str += as_json_type_name(stack_.back().t);
}
emplace_error(sec::runtime_error, std::move(str));
return false;
}
}
void json_builder::fail(type t) {
std::string str = "failed to write a ";
str += as_json_type_name(t);
str += ": invalid position (begin/end mismatch?)";
emplace_error(sec::runtime_error, std::move(str));
}
bool json_builder::inside_object() const noexcept {
auto is_object = [](const entry& x) { return x.t == type::object; };
return std::any_of(stack_.begin(), stack_.end(), is_object);
}
} // namespace caf
// 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.
#include "caf/json_object.hpp"
namespace caf {
// -- properties ---------------------------------------------------------------
json_value json_object::value(std::string_view key) const {
auto pred = [key](const auto& member) { return member.key == key; };
auto i = std::find_if(obj_->begin(), obj_->end(), pred);
if (i != obj_->end()) {
return {i->val, storage_};
}
return json_value::undefined();
}
std::string to_string(const json_object& obj) {
std::string result;
obj.print_to(result);
return result;
}
} // namespace caf
......@@ -8,6 +8,8 @@
#include "caf/detail/print.hpp"
#include "caf/string_algorithms.hpp"
#include <fstream>
namespace {
static constexpr const char class_name[] = "caf::json_reader";
......@@ -145,19 +147,41 @@ json_reader::~json_reader() {
bool json_reader::load(std::string_view json_text) {
reset();
string_parser_state ps{json_text.begin(), json_text.end()};
root_ = detail::json::parse_shallow(ps, &buf_);
if (ps.code != pec::success) {
set_error(make_error(ps));
st_ = nullptr;
return false;
}
err_.reset();
detail::monotonic_buffer_resource::allocator<stack_type> alloc{&buf_};
st_ = new (alloc.allocate(1)) stack_type(stack_allocator{&buf_});
st_->reserve(16);
st_->emplace_back(root_);
return true;
}
bool json_reader::load_file(const char* path) {
using iterator_t = std::istreambuf_iterator<char>;
reset();
std::ifstream input{path};
if (!input.is_open()) {
emplace_error(sec::cannot_open_file);
return false;
}
detail::json::file_parser_state ps{iterator_t{input}, iterator_t{}};
root_ = detail::json::parse(ps, &buf_);
if (ps.code != pec::success) {
set_error(make_error(ps));
st_ = nullptr;
return false;
} else {
err_.reset();
detail::monotonic_buffer_resource::allocator<stack_type> alloc{&buf_};
st_ = new (alloc.allocate(1)) stack_type(stack_allocator{&buf_});
st_->reserve(16);
st_->emplace_back(root_);
return true;
}
err_.reset();
detail::monotonic_buffer_resource::allocator<stack_type> alloc{&buf_};
st_ = new (alloc.allocate(1)) stack_type(stack_allocator{&buf_});
st_->reserve(16);
st_->emplace_back(root_);
return true;
}
void json_reader::revert() {
......
// 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.
#include "caf/json_value.hpp"
#include "caf/expected.hpp"
#include "caf/json_array.hpp"
#include "caf/json_object.hpp"
#include "caf/make_counted.hpp"
#include "caf/parser_state.hpp"
#include <fstream>
namespace caf {
// -- conversion ---------------------------------------------------------------
int64_t json_value::to_integer(int64_t fallback) const {
if (is_integer()) {
return std::get<int64_t>(val_->data);
}
if (is_double()) {
return static_cast<int64_t>(std::get<double>(val_->data));
}
return fallback;
}
double json_value::to_double(double fallback) const {
if (is_double()) {
return std::get<double>(val_->data);
}
if (is_integer()) {
return static_cast<double>(std::get<int64_t>(val_->data));
}
return fallback;
}
bool json_value::to_bool(bool fallback) const {
if (is_bool()) {
return std::get<bool>(val_->data);
}
return fallback;
}
std::string_view json_value::to_string() const {
return to_string(std::string_view{});
}
std::string_view json_value::to_string(std::string_view fallback) const {
if (is_string()) {
return std::get<std::string_view>(val_->data);
}
return fallback;
}
json_object json_value::to_object() const {
return to_object(json_object{});
}
json_object json_value::to_object(json_object fallback) const {
if (is_object()) {
return json_object{&std::get<detail::json::object>(val_->data), storage_};
}
return fallback;
}
json_array json_value::to_array() const {
return to_array(json_array{});
}
json_array json_value::to_array(json_array fallback) const {
if (is_array()) {
return json_array{&std::get<detail::json::array>(val_->data), storage_};
}
return fallback;
}
// -- comparison ---------------------------------------------------------------
bool json_value::equal_to(const json_value& other) const noexcept {
if (val_ == other.val_) {
return true;
}
if (val_ != nullptr && other.val_ != nullptr) {
return *val_ == *other.val_;
}
return false;
}
// -- parsing ------------------------------------------------------------------
expected<json_value> json_value::parse(std::string_view str) {
auto storage = make_counted<detail::json::storage>();
string_parser_state ps{str.begin(), str.end()};
auto root = detail::json::parse(ps, &storage->buf);
if (ps.code == pec::success)
return {json_value{root, std::move(storage)}};
return {make_error(ps)};
}
expected<json_value> json_value::parse_shallow(std::string_view str) {
auto storage = make_counted<detail::json::storage>();
string_parser_state ps{str.begin(), str.end()};
auto root = detail::json::parse_shallow(ps, &storage->buf);
if (ps.code == pec::success)
return {json_value{root, std::move(storage)}};
return {make_error(ps)};
}
expected<json_value> json_value::parse_in_situ(std::string& str) {
auto storage = make_counted<detail::json::storage>();
detail::json::mutable_string_parser_state ps{str.data(),
str.data() + str.size()};
auto root = detail::json::parse_in_situ(ps, &storage->buf);
if (ps.code == pec::success)
return {json_value{root, std::move(storage)}};
return {make_error(ps)};
}
expected<json_value> json_value::parse_file(const char* path) {
using iterator_t = std::istreambuf_iterator<char>;
std::ifstream input{path};
if (!input.is_open())
return make_error(sec::cannot_open_file);
auto storage = make_counted<detail::json::storage>();
detail::json::file_parser_state ps{iterator_t{input}, iterator_t{}};
auto root = detail::json::parse(ps, &storage->buf);
if (ps.code == pec::success)
return {json_value{root, std::move(storage)}};
return {make_error(ps)};
}
expected<json_value> json_value::parse_file(const std::string& path) {
return parse_file(path.c_str());
}
// -- free functions -----------------------------------------------------------
std::string to_string(const json_value& val) {
std::string result;
val.print_to(result);
return result;
}
} // namespace caf
......@@ -13,17 +13,9 @@ namespace {
static constexpr const char class_name[] = "caf::json_writer";
constexpr bool can_morph(json_writer::type from, json_writer::type to) {
return from == json_writer::type::element && to != json_writer::type::member;
}
constexpr const char* json_type_names[] = {"element", "object", "member",
"array", "string", "number",
"bool", "null"};
constexpr const char* json_type_name(json_writer::type t) {
return json_type_names[static_cast<uint8_t>(t)];
}
constexpr std::string_view json_type_names[] = {"element", "object", "member",
"array", "string", "number",
"bool", "null"};
char last_non_ws_char(const std::vector<char>& buf) {
auto not_ws = [](char c) { return !std::isspace(c); };
......@@ -104,7 +96,7 @@ bool json_writer::begin_object(type_id_t id, std::string_view name) {
pop();
return true;
};
if (inside_object() || skip_object_type_annotation_)
if (skip_object_type_annotation_ || inside_object())
return begin_associative_array(0);
else
return begin_associative_array(0) // Put opening paren, ...
......@@ -140,7 +132,7 @@ bool json_writer::begin_field(std::string_view name, bool is_present) {
return true;
default: {
std::string str = "expected object, found ";
str += json_type_name(t);
str += as_json_type_name(t);
emplace_error(sec::runtime_error, class_name, __func__, std::move(str));
return false;
}
......@@ -222,7 +214,7 @@ bool json_writer::begin_key_value_pair() {
return true;
default: {
std::string str = "expected object, found ";
str += json_type_name(t);
str += as_json_type_name(t);
emplace_error(sec::runtime_error, class_name, __func__, std::move(str));
return false;
}
......@@ -489,12 +481,12 @@ bool json_writer::pop_if(type t) {
return true;
} else {
std::string str = "pop_if failed: expected ";
str += json_type_name(t);
str += as_json_type_name(t);
if (stack_.empty()) {
str += ", found an empty stack";
} else {
str += ", found ";
str += json_type_name(stack_.back().t);
str += as_json_type_name(stack_.back().t);
}
emplace_error(sec::runtime_error, std::move(str));
return false;
......@@ -509,13 +501,13 @@ bool json_writer::pop_if_next(type t) {
return true;
} else {
std::string str = "pop_if_next failed: expected ";
str += json_type_name(t);
str += as_json_type_name(t);
if (stack_.size() < 2) {
str += ", found a stack of size ";
detail::print(str, stack_.size());
} else {
str += ", found ";
str += json_type_name(stack_[stack_.size() - 2].t);
str += as_json_type_name(stack_[stack_.size() - 2].t);
}
emplace_error(sec::runtime_error, std::move(str));
return false;
......@@ -536,9 +528,9 @@ bool json_writer::morph(type t, type& prev) {
return true;
} else {
std::string str = "cannot convert ";
str += json_type_name(stack_.back().t);
str += as_json_type_name(stack_.back().t);
str += " to ";
str += json_type_name(t);
str += as_json_type_name(t);
emplace_error(sec::runtime_error, std::move(str));
return false;
}
......@@ -555,7 +547,7 @@ void json_writer::unsafe_morph(type t) {
void json_writer::fail(type t) {
std::string str = "failed to write a ";
str += json_type_name(t);
str += as_json_type_name(t);
str += ": invalid position (begin/end mismatch?)";
emplace_error(sec::runtime_error, std::move(str));
}
......@@ -589,4 +581,10 @@ void json_writer::sep() {
}
}
// -- free functions -----------------------------------------------------------
std::string_view as_json_type_name(json_writer::type t) {
return json_type_names[static_cast<uint8_t>(t)];
}
} // namespace caf
#pragma once
#include "caf/binary_deserializer.hpp"
#include "caf/binary_serializer.hpp"
#include "caf/byte_buffer.hpp"
#include "caf/cow_vector.hpp"
#include "caf/fwd.hpp"
#include "caf/result.hpp"
......@@ -13,6 +16,32 @@
#include <string>
#include <utility>
// -- utility for testing serialization round-trips ----------------------------
template <class T>
T deep_copy(const T& val) {
using namespace std::literals;
caf::byte_buffer buf;
{
caf::binary_serializer sink{nullptr, buf};
if (!sink.apply(val)) {
auto msg = "serialization failed in deep_copy: "s;
msg += to_string(sink.get_error());
CAF_RAISE_ERROR(msg.c_str());
}
}
auto result = T{};
{
caf::binary_deserializer sink{nullptr, buf};
if (!sink.apply(result)) {
auto msg = "deserialization failed in deep_copy: "s;
msg += to_string(sink.get_error());
CAF_RAISE_ERROR(msg.c_str());
}
}
return result;
}
// -- forward declarations for all unit test suites ----------------------------
using float_actor = caf::typed_actor<caf::result<void>(float)>;
......
......@@ -106,6 +106,12 @@ void stringify(std::string& str, size_t, detail::json::null_t) {
str += "null";
}
void stringify(std::string& str, size_t, detail::json::undefined_t) {
// The parser never emits undefined objects, but we still need to provide an
// overload for this type.
str += "null";
}
void stringify(std::string& str, size_t indent, const detail::json::array& xs) {
if (xs.empty()) {
str += "[]";
......
// 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_array
#include "caf/json_array.hpp"
#include "core-test.hpp"
#include "caf/json_array.hpp"
#include "caf/json_value.hpp"
using namespace caf;
using namespace std::literals;
namespace {
std::string printed(const json_array& arr) {
std::string result;
arr.print_to(result, 2);
return result;
}
} // namespace
TEST_CASE("default-constructed") {
auto arr = json_array{};
CHECK(arr.empty());
CHECK(arr.is_empty());
CHECK(arr.begin() == arr.end());
CHECK_EQ(arr.size(), 0u);
CHECK_EQ(to_string(arr), "[]");
CHECK_EQ(printed(arr), "[]");
CHECK_EQ(deep_copy(arr), arr);
}
TEST_CASE("from empty array") {
auto arr = json_value::parse("[]")->to_array();
CHECK(arr.empty());
CHECK(arr.is_empty());
CHECK(arr.begin() == arr.end());
CHECK_EQ(arr.size(), 0u);
CHECK_EQ(to_string(arr), "[]");
CHECK_EQ(printed(arr), "[]");
CHECK_EQ(deep_copy(arr), arr);
}
TEST_CASE("from non-empty array") {
auto arr = json_value::parse(R"_([1, "two", 3.0])_")->to_array();
CHECK(!arr.empty());
CHECK(!arr.is_empty());
CHECK(arr.begin() != arr.end());
REQUIRE_EQ(arr.size(), 3u);
CHECK_EQ((*arr.begin()).to_integer(), 1);
std::vector<json_value> vals;
for (const auto& val : arr) {
vals.emplace_back(val);
}
REQUIRE_EQ(vals.size(), 3u);
CHECK_EQ(vals[0].to_integer(), 1);
CHECK_EQ(vals[1].to_string(), "two");
CHECK_EQ(vals[2].to_double(), 3.0);
CHECK_EQ(to_string(arr), R"_([1, "two", 3])_");
CHECK_EQ(printed(arr), "[\n 1,\n \"two\",\n 3\n]");
CHECK_EQ(deep_copy(arr), arr);
}
// 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_builder
#include "caf/json_builder.hpp"
#include "core-test.hpp"
#include "caf/json_value.hpp"
#include <string_view>
using namespace caf;
using namespace std::literals;
namespace {
struct fixture {
fixture() {
builder.skip_object_type_annotation(true);
}
std::string printed(const json_value& val, size_t indentation_factor = 0) {
std::string result;
val.print_to(result, indentation_factor);
return result;
}
json_builder builder;
};
} // namespace
BEGIN_FIXTURE_SCOPE(fixture)
TEST_CASE("empty JSON value") {
auto val = builder.seal();
CHECK(val.is_null());
}
TEST_CASE("integer") {
CHECK(builder.value(int32_t{42}));
auto val = builder.seal();
CHECK(val.is_integer());
CHECK_EQ(val.to_integer(), 42);
}
TEST_CASE("floating point") {
CHECK(builder.value(4.2));
auto val = builder.seal();
CHECK(val.is_double());
CHECK_EQ(val.to_double(), 4.2);
}
TEST_CASE("boolean") {
CHECK(builder.value(true));
auto val = builder.seal();
CHECK(val.is_bool());
CHECK_EQ(val.to_bool(), true);
}
TEST_CASE("string") {
CHECK(builder.value("Hello, world!"sv));
auto val = builder.seal();
CHECK(val.is_string());
CHECK_EQ(val.to_string(), "Hello, world!"sv);
}
TEST_CASE("array") {
auto xs = std::vector{1, 2, 3};
CHECK(builder.apply(xs));
auto val = builder.seal();
CHECK(val.is_array());
CHECK_EQ(printed(val), "[1, 2, 3]"sv);
}
TEST_CASE("flat object") {
auto req = my_request{10, 20};
if (!CHECK(builder.apply(req))) {
MESSAGE("builder: " << builder.get_error());
}
auto val = builder.seal();
CHECK(val.is_object());
CHECK_EQ(printed(val), R"_({"a": 10, "b": 20})_");
}
TEST_CASE("flat object with type annotation") {
builder.skip_object_type_annotation(false);
auto req = my_request{10, 20};
if (!CHECK(builder.apply(req))) {
MESSAGE("builder: " << builder.get_error());
}
auto val = builder.seal();
CHECK(val.is_object());
CHECK_EQ(printed(val), R"_({"@type": "my_request", "a": 10, "b": 20})_");
}
namespace {
constexpr std::string_view rect_str = R"_({
"top-left": {
"x": 10,
"y": 10
},
"bottom-right": {
"x": 20,
"y": 20
}
})_";
} // namespace
TEST_CASE("nested object") {
auto rect = rectangle{{10, 10}, {20, 20}};
if (!CHECK(builder.apply(rect))) {
MESSAGE("builder: " << builder.get_error());
}
auto val = builder.seal();
CHECK(val.is_object());
CHECK_EQ(printed(val, 2), rect_str);
}
namespace {
constexpr std::string_view annotated_rect_str = R"_({
"@type": "rectangle",
"top-left": {
"x": 10,
"y": 10
},
"bottom-right": {
"x": 20,
"y": 20
}
})_";
} // namespace
TEST_CASE("nested object with type annotation") {
builder.skip_object_type_annotation(false);
auto rect = rectangle{{10, 10}, {20, 20}};
if (!CHECK(builder.apply(rect))) {
MESSAGE("builder: " << builder.get_error());
}
auto val = builder.seal();
CHECK(val.is_object());
CHECK_EQ(printed(val, 2), annotated_rect_str);
}
END_FIXTURE_SCOPE()
// 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_object
#include "caf/json_object.hpp"
#include "core-test.hpp"
#include "caf/json_array.hpp"
#include "caf/json_value.hpp"
using namespace caf;
using namespace std::literals;
namespace {
std::string printed(const json_object& obj) {
std::string result;
obj.print_to(result, 2);
return result;
}
} // namespace
TEST_CASE("default-constructed") {
auto obj = json_object{};
CHECK(obj.empty());
CHECK(obj.is_empty());
CHECK(obj.begin() == obj.end());
CHECK_EQ(obj.size(), 0u);
CHECK(obj.value("foo").is_undefined());
CHECK_EQ(to_string(obj), "{}");
CHECK_EQ(printed(obj), "{}");
CHECK_EQ(deep_copy(obj), obj);
}
TEST_CASE("from empty object") {
auto obj = json_value::parse("{}")->to_object();
CHECK(obj.empty());
CHECK(obj.is_empty());
CHECK(obj.begin() == obj.end());
CHECK_EQ(obj.size(), 0u);
CHECK(obj.value("foo").is_undefined());
CHECK_EQ(to_string(obj), "{}");
CHECK_EQ(printed(obj), "{}");
CHECK_EQ(deep_copy(obj), obj);
}
TEST_CASE("from non-empty object") {
auto obj = json_value::parse(R"_({"a": "one", "b": 2})_")->to_object();
CHECK(!obj.empty());
CHECK(!obj.is_empty());
CHECK(obj.begin() != obj.end());
REQUIRE_EQ(obj.size(), 2u);
CHECK_EQ(obj.begin().key(), "a");
CHECK_EQ(obj.begin().value().to_string(), "one");
CHECK_EQ(obj.value("a").to_string(), "one");
CHECK_EQ(obj.value("b").to_integer(), 2);
CHECK(obj.value("c").is_undefined());
std::vector<std::pair<std::string_view, json_value>> vals;
for (const auto& val : obj) {
vals.emplace_back(val);
}
REQUIRE_EQ(vals.size(), 2u);
CHECK_EQ(vals[0].first, "a");
CHECK_EQ(vals[0].second.to_string(), "one");
CHECK_EQ(vals[1].first, "b");
CHECK_EQ(vals[1].second.to_integer(), 2);
CHECK_EQ(to_string(obj), R"_({"a": "one", "b": 2})_");
CHECK_EQ(printed(obj), "{\n \"a\": \"one\",\n \"b\": 2\n}");
CHECK_EQ(deep_copy(obj), obj);
}
// 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_value
#include "caf/json_value.hpp"
#include "core-test.hpp"
#include "caf/binary_deserializer.hpp"
#include "caf/binary_serializer.hpp"
#include "caf/json_array.hpp"
#include "caf/json_object.hpp"
using namespace caf;
using namespace std::literals;
namespace {
std::string printed(const json_value& val) {
std::string result;
val.print_to(result, 2);
return result;
}
} // namespace
TEST_CASE("default-constructed") {
auto val = json_value{};
CHECK(val.is_null());
CHECK(!val.is_undefined());
CHECK(!val.is_integer());
CHECK(!val.is_integer());
CHECK(!val.is_double());
CHECK(!val.is_number());
CHECK(!val.is_bool());
CHECK(!val.is_string());
CHECK(!val.is_array());
CHECK(!val.is_object());
CHECK_EQ(val.to_integer(), 0);
CHECK_EQ(val.to_double(), 0.0);
CHECK_EQ(val.to_bool(), false);
CHECK_EQ(val.to_string(), ""sv);
CHECK_EQ(val.to_object().size(), 0u);
CHECK_EQ(to_string(val), "null");
CHECK_EQ(printed(val), "null");
CHECK_EQ(deep_copy(val), val);
}
TEST_CASE("from undefined") {
auto val = json_value::undefined();
CHECK(!val.is_null());
CHECK(val.is_undefined());
CHECK(!val.is_integer());
CHECK(!val.is_integer());
CHECK(!val.is_double());
CHECK(!val.is_number());
CHECK(!val.is_bool());
CHECK(!val.is_string());
CHECK(!val.is_array());
CHECK(!val.is_object());
CHECK_EQ(val.to_integer(), 0);
CHECK_EQ(val.to_double(), 0.0);
CHECK_EQ(val.to_bool(), false);
CHECK_EQ(val.to_string(), ""sv);
CHECK_EQ(val.to_object().size(), 0u);
CHECK_EQ(to_string(val), "null");
CHECK_EQ(printed(val), "null");
CHECK_EQ(deep_copy(val), val);
}
TEST_CASE("from integer") {
auto val = unbox(json_value::parse("42"));
CHECK(!val.is_null());
CHECK(!val.is_undefined());
CHECK(val.is_integer());
CHECK(!val.is_double());
CHECK(val.is_number());
CHECK(!val.is_bool());
CHECK(!val.is_string());
CHECK(!val.is_array());
CHECK(!val.is_object());
CHECK_EQ(val.to_integer(), 42);
CHECK_EQ(val.to_double(), 42.0);
CHECK_EQ(val.to_bool(), false);
CHECK_EQ(val.to_string(), ""sv);
CHECK_EQ(val.to_object().size(), 0u);
CHECK_EQ(to_string(val), "42");
CHECK_EQ(printed(val), "42");
CHECK_EQ(deep_copy(val), val);
}
TEST_CASE("from double") {
auto val = unbox(json_value::parse("42.0"));
CHECK(!val.is_null());
CHECK(!val.is_undefined());
CHECK(!val.is_integer());
CHECK(val.is_double());
CHECK(val.is_number());
CHECK(!val.is_bool());
CHECK(!val.is_string());
CHECK(!val.is_array());
CHECK(!val.is_object());
CHECK_EQ(val.to_integer(), 42);
CHECK_EQ(val.to_double(), 42.0);
CHECK_EQ(val.to_bool(), false);
CHECK_EQ(val.to_string(), ""sv);
CHECK_EQ(val.to_object().size(), 0u);
CHECK_EQ(to_string(val), "42");
CHECK_EQ(printed(val), "42");
CHECK_EQ(deep_copy(val), val);
}
TEST_CASE("from bool") {
auto val = unbox(json_value::parse("true"));
CHECK(!val.is_null());
CHECK(!val.is_undefined());
CHECK(!val.is_integer());
CHECK(!val.is_double());
CHECK(!val.is_number());
CHECK(val.is_bool());
CHECK(!val.is_string());
CHECK(!val.is_array());
CHECK(!val.is_object());
CHECK_EQ(val.to_integer(), 0);
CHECK_EQ(val.to_double(), 0.0);
CHECK_EQ(val.to_bool(), true);
CHECK_EQ(val.to_string(), ""sv);
CHECK_EQ(val.to_object().size(), 0u);
CHECK_EQ(to_string(val), "true");
CHECK_EQ(printed(val), "true");
CHECK_EQ(deep_copy(val), val);
}
TEST_CASE("from string") {
auto val = unbox(json_value::parse(R"_("Hello, world!")_"));
CHECK(!val.is_null());
CHECK(!val.is_undefined());
CHECK(!val.is_integer());
CHECK(!val.is_double());
CHECK(!val.is_number());
CHECK(!val.is_bool());
CHECK(val.is_string());
CHECK(!val.is_array());
CHECK(!val.is_object());
CHECK_EQ(val.to_integer(), 0);
CHECK_EQ(val.to_double(), 0.0);
CHECK_EQ(val.to_bool(), false);
CHECK_EQ(val.to_string(), "Hello, world!"sv);
CHECK_EQ(val.to_object().size(), 0u);
CHECK_EQ(to_string(val), R"_("Hello, world!")_");
CHECK_EQ(printed(val), R"_("Hello, world!")_");
CHECK_EQ(deep_copy(val), val);
}
TEST_CASE("from empty array") {
auto val = unbox(json_value::parse("[]"));
CHECK(!val.is_null());
CHECK(!val.is_undefined());
CHECK(!val.is_integer());
CHECK(!val.is_double());
CHECK(!val.is_number());
CHECK(!val.is_bool());
CHECK(!val.is_string());
CHECK(val.is_array());
CHECK(!val.is_object());
CHECK_EQ(val.to_integer(), 0);
CHECK_EQ(val.to_double(), 0.0);
CHECK_EQ(val.to_bool(), false);
CHECK_EQ(val.to_string(), ""sv);
CHECK_EQ(val.to_array().size(), 0u);
CHECK_EQ(val.to_object().size(), 0u);
CHECK_EQ(to_string(val), "[]");
CHECK_EQ(printed(val), "[]");
CHECK_EQ(deep_copy(val), val);
}
TEST_CASE("from array of size 1") {
auto val = unbox(json_value::parse("[1]"));
CHECK(!val.is_null());
CHECK(!val.is_undefined());
CHECK(!val.is_integer());
CHECK(!val.is_double());
CHECK(!val.is_number());
CHECK(!val.is_bool());
CHECK(!val.is_string());
CHECK(val.is_array());
CHECK(!val.is_object());
CHECK_EQ(val.to_integer(), 0);
CHECK_EQ(val.to_double(), 0.0);
CHECK_EQ(val.to_bool(), false);
CHECK_EQ(val.to_string(), ""sv);
CHECK_EQ(val.to_array().size(), 1u);
CHECK_EQ(val.to_object().size(), 0u);
CHECK_EQ(to_string(val), "[1]");
CHECK_EQ(printed(val), "[\n 1\n]");
CHECK_EQ(deep_copy(val), val);
}
TEST_CASE("from array of size 3") {
auto val = unbox(json_value::parse("[1, 2, 3]"));
CHECK(!val.is_null());
CHECK(!val.is_undefined());
CHECK(!val.is_integer());
CHECK(!val.is_double());
CHECK(!val.is_number());
CHECK(!val.is_bool());
CHECK(!val.is_string());
CHECK(val.is_array());
CHECK(!val.is_object());
CHECK_EQ(val.to_integer(), 0);
CHECK_EQ(val.to_double(), 0.0);
CHECK_EQ(val.to_bool(), false);
CHECK_EQ(val.to_string(), ""sv);
CHECK_EQ(val.to_array().size(), 3u);
CHECK_EQ(val.to_object().size(), 0u);
CHECK_EQ(to_string(val), "[1, 2, 3]");
CHECK_EQ(printed(val), "[\n 1,\n 2,\n 3\n]");
CHECK_EQ(deep_copy(val), val);
}
TEST_CASE("from empty object") {
auto val = unbox(json_value::parse("{}"));
CHECK(!val.is_null());
CHECK(!val.is_undefined());
CHECK(!val.is_integer());
CHECK(!val.is_double());
CHECK(!val.is_number());
CHECK(!val.is_bool());
CHECK(!val.is_string());
CHECK(!val.is_array());
CHECK(val.is_object());
CHECK_EQ(val.to_integer(), 0);
CHECK_EQ(val.to_double(), 0.0);
CHECK_EQ(val.to_bool(), false);
CHECK_EQ(val.to_string(), ""sv);
CHECK_EQ(val.to_object().size(), 0u);
CHECK_EQ(to_string(val), "{}");
CHECK_EQ(printed(val), "{}");
CHECK_EQ(deep_copy(val), val);
}
TEST_CASE("from non-empty object") {
auto val = unbox(json_value::parse(R"_({"foo": "bar"})_"));
CHECK(!val.is_null());
CHECK(!val.is_undefined());
CHECK(!val.is_integer());
CHECK(!val.is_double());
CHECK(!val.is_number());
CHECK(!val.is_bool());
CHECK(!val.is_string());
CHECK(!val.is_array());
CHECK(val.is_object());
CHECK_EQ(val.to_integer(), 0);
CHECK_EQ(val.to_double(), 0.0);
CHECK_EQ(val.to_bool(), false);
CHECK_EQ(val.to_string(), ""sv);
CHECK_EQ(val.to_object().size(), 1u);
CHECK_EQ(to_string(val), R"_({"foo": "bar"})_");
CHECK_EQ(printed(val), "{\n \"foo\": \"bar\"\n}");
CHECK_EQ(deep_copy(val), val);
}
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