Commit e0b41065 authored by Dominik Charousset's avatar Dominik Charousset

Backport support for uint64_t in JSON parsing

parent e4b1bb30
......@@ -162,7 +162,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
......@@ -304,7 +308,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
......
......@@ -10,26 +10,28 @@
namespace caf::detail {
template <class To, bool LargeUnsigned = sizeof(To) >= sizeof(int64_t)
&& std::is_unsigned<To>::value>
template <class To>
struct bounds_checker {
static constexpr bool check(int64_t x) noexcept {
return x >= std::numeric_limits<To>::min()
&& x <= std::numeric_limits<To>::max();
}
};
template <>
struct bounds_checker<int64_t, false> {
static constexpr bool check(int64_t) noexcept {
return true;
template <class From>
static constexpr bool check(From x) noexcept {
auto converted = static_cast<To>(x);
if constexpr (std::is_signed_v<From> == std::is_signed_v<To>) {
// If the source and target types have the same signedness, we can simply
// check whether the value is in the range of the target type.
return static_cast<From>(converted) == x;
} else if constexpr (std::is_signed_v<From>) {
// If the source type is signed and the target type is unsigned, we need
// to check that the value is not negative. Otherwise, the conversion
// could yield a positive value, which is out of range for the target
// type.
return x >= 0 && static_cast<From>(converted) == x;
} else {
static_assert(std::is_signed_v<To>);
// If the source type is unsigned and the target type is signed, we need
// to check whether the conversion produced a non-negative value. If the
// value is negative, the conversion is out of range.
return converted >= 0 && static_cast<From>(converted) == x;
}
};
template <class To>
struct bounds_checker<To, true> {
static constexpr bool check(int64_t x) noexcept {
return x >= 0;
}
};
......
......@@ -59,7 +59,7 @@ private:
// -- member variables -------------------------------------------------------
const config_option_set* options_ = nullptr;
variant<none_t, config_consumer*, config_list_consumer*,
std::variant<none_t, config_consumer*, config_list_consumer*,
config_value_consumer*>
parent_;
};
......@@ -102,8 +102,16 @@ public:
template <class T>
pec value(T&& x) {
using val_t = std::decay_t<T>;
if constexpr (std::is_same_v<val_t, uint64_t>) {
if (x <= INT64_MAX)
return value_impl(config_value{static_cast<int64_t>(x)});
else
return pec::integer_overflow;
} else {
return value_impl(config_value{std::forward<T>(x)});
}
}
const std::string& current_key() {
return current_key_;
......@@ -119,7 +127,7 @@ private:
// -- member variables -------------------------------------------------------
const config_option_set* options_ = nullptr;
variant<none_t, config_consumer*, config_list_consumer*> parent_;
std::variant<none_t, config_consumer*, config_list_consumer*> parent_;
settings* cfg_ = nullptr;
std::string current_key_;
std::string category_;
......
......@@ -4,10 +4,15 @@
#pragma once
#include "caf/pec.hpp"
#include <optional>
#include <type_traits>
#include <utility>
namespace caf::detail {
/// Consumes a single value.
template <class T>
class consumer {
public:
......@@ -17,17 +22,117 @@ public:
// nop
}
void value(T&& y) {
x_ = std::move(y);
template <class U>
void value(U&& y) {
x_ = std::forward<U>(y);
}
private:
T& x_;
};
/// Specializes `consumer` for `int64_t` with a safe conversion from `uint64_t`.
template <>
class consumer<int64_t> {
public:
using value_type = int64_t;
explicit consumer(int64_t& x) : x_(x) {
// nop
}
void value(int64_t y) noexcept {
x_ = y;
}
pec value(uint64_t y) noexcept {
if (y < INT64_MAX) {
value(static_cast<int64_t>(y));
return pec::success;
}
return pec::integer_overflow;
}
private:
int64_t& x_;
};
/// Specializes `consumer` for `uint64_t` with a safe conversion from `int64_t`.
template <>
class consumer<uint64_t> {
public:
using value_type = uint64_t;
explicit consumer(uint64_t& x) : x_(x) {
// nop
}
void value(uint64_t y) noexcept {
x_ = y;
}
pec value(int64_t y) noexcept {
if (y >= 0) {
value(static_cast<uint64_t>(y));
return pec::success;
}
return pec::integer_underflow;
}
private:
uint64_t& x_;
};
template <class... Ts>
class consumer<std::variant<Ts...>> {
public:
explicit consumer(std::variant<Ts...>& x) : x_(x) {
// nop
}
template <class T>
void value(T&& y) {
x_ = std::forward<T>(y);
}
private:
std::variant<Ts...>& x_;
};
template <class T>
class consumer<std::optional<T>> {
public:
using value_type = T;
explicit consumer(std::optional<T>& x) : x_(x) {
// nop
}
template <class U>
void value(U&& y) {
x_ = std::forward<U>(y);
}
private:
std::optional<T>& x_;
};
template <class T>
consumer<T> make_consumer(T& x) {
return consumer<T>{x};
}
/// Applies a consumer to a value and updates the error code if necessary.
template <class Consumer, class T>
static void apply_consumer(Consumer&& consumer, T&& value, pec& code) {
using res_t = decltype(consumer.value(std::forward<T>(value)));
if constexpr (std::is_same_v<res_t, void>) {
consumer.value(std::forward<T>(value));
} else {
auto res = consumer.value(std::forward<T>(value));
if (res != pec::success)
code = res;
}
}
} // namespace caf::detail
This diff is collapsed.
......@@ -5,6 +5,7 @@
#pragma once
#include <cstdint>
#include <optional>
#include <type_traits>
#include "caf/config.hpp"
......@@ -14,7 +15,6 @@
#include "caf/detail/parser/is_digit.hpp"
#include "caf/detail/parser/sub_ascii.hpp"
#include "caf/detail/scope_guard.hpp"
#include "caf/optional.hpp"
#include "caf/pec.hpp"
CAF_PUSH_UNUSED_LABEL_WARNING
......@@ -30,7 +30,7 @@ namespace caf::detail::parser {
/// the pre-decimal value.
template <class State, class Consumer, class ValueType>
void read_floating_point(State& ps, Consumer&& consumer,
optional<ValueType> start_value,
std::optional<ValueType> start_value,
bool negative = false) {
// Any exponent larger than 511 always overflows.
static constexpr int max_double_exponent = 511;
......@@ -38,7 +38,7 @@ void read_floating_point(State& ps, Consumer&& consumer,
enum sign_t { plus, minus };
sign_t sign;
ValueType result;
if (start_value == none) {
if (!start_value) {
sign = plus;
result = 0;
} else if (*start_value < 0) {
......@@ -72,8 +72,8 @@ void read_floating_point(State& ps, Consumer&& consumer,
}
// 3) Scale result.
// Pre-computed powers of 10 for the scaling loop.
static double powerTable[]
= {1e1, 1e2, 1e4, 1e8, 1e16, 1e32, 1e64, 1e128, 1e256};
static double powerTable[] = {1e1, 1e2, 1e4, 1e8, 1e16,
1e32, 1e64, 1e128, 1e256};
auto i = 0;
if (exp < 0) {
for (auto n = -exp; n != 0; n >>= 1, ++i)
......@@ -97,7 +97,7 @@ void read_floating_point(State& ps, Consumer&& consumer,
// Definition of our parser FSM.
start();
unstable_state(init) {
epsilon_if(start_value == none, regular_init)
epsilon_if(!start_value, regular_init)
epsilon(after_dec, "eE.")
epsilon(after_dot, any_char)
}
......@@ -173,7 +173,7 @@ template <class State, class Consumer>
void read_floating_point(State& ps, Consumer&& consumer) {
using consumer_type = typename std::decay<Consumer>::type;
using value_type = typename consumer_type::value_type;
return read_floating_point(ps, consumer, optional<value_type>{});
return read_floating_point(ps, consumer, std::optional<value_type>{});
}
} // namespace caf::detail::parser
......
......@@ -15,10 +15,8 @@
#include "caf/detail/parser/read_timespan.hpp"
#include "caf/detail/scope_guard.hpp"
#include "caf/none.hpp"
#include "caf/optional.hpp"
#include "caf/pec.hpp"
#include "caf/timestamp.hpp"
#include "caf/variant.hpp"
CAF_PUSH_UNUSED_LABEL_WARNING
......@@ -35,34 +33,43 @@ void read_number_or_timespan(State& ps, Consumer& consumer,
struct interim_consumer {
size_t invocations = 0;
Consumer* outer = nullptr;
variant<none_t, int64_t, double> interim;
std::variant<none_t, int64_t, double> interim;
void value(int64_t x) {
// If we see a second integer, we have a range of integers and forward all
// calls to the outer consumer.
switch (++invocations) {
case 1:
interim = x;
break;
case 2:
CAF_ASSERT(holds_alternative<int64_t>(interim));
outer->value(get<int64_t>(interim));
CAF_ASSERT(std::holds_alternative<int64_t>(interim));
outer->value(std::get<int64_t>(interim));
interim = none;
[[fallthrough]];
default:
outer->value(x);
}
}
pec value(uint64_t x) {
if (x <= INT64_MAX) {
value(static_cast<int64_t>(x));
return pec::success;
}
return pec::integer_overflow;
}
void value(double x) {
interim = x;
}
};
interim_consumer ic;
ic.outer = &consumer;
auto has_int = [&] { return holds_alternative<int64_t>(ic.interim); };
auto has_dbl = [&] { return holds_alternative<double>(ic.interim); };
auto get_int = [&] { return get<int64_t>(ic.interim); };
auto has_int = [&] { return std::holds_alternative<int64_t>(ic.interim); };
auto has_dbl = [&] { return std::holds_alternative<double>(ic.interim); };
auto get_int = [&] { return std::get<int64_t>(ic.interim); };
auto g = make_scope_guard([&] {
if (ps.code <= pec::trailing_character) {
if (has_dbl())
consumer.value(get<double>(ic.interim));
consumer.value(std::get<double>(ic.interim));
else if (has_int())
consumer.value(get_int());
}
......
......@@ -6,12 +6,12 @@
#include <chrono>
#include <cstdint>
#include <optional>
#include <string>
#include "caf/config.hpp"
#include "caf/detail/parser/read_signed_integer.hpp"
#include "caf/detail/scope_guard.hpp"
#include "caf/optional.hpp"
#include "caf/pec.hpp"
#include "caf/timestamp.hpp"
......@@ -24,7 +24,7 @@ namespace caf::detail::parser {
/// Reads a timespan.
template <class State, class Consumer>
void read_timespan(State& ps, Consumer&& consumer,
optional<int64_t> num = none) {
std::optional<int64_t> num = std::nullopt) {
using namespace std::chrono;
struct interim_consumer {
using value_type = int64_t;
......
......@@ -1050,6 +1050,10 @@ struct unboxed_oracle<std::optional<T>> {
template <class T>
using unboxed_t = typename unboxed_oracle<T>::type;
template <class T>
constexpr bool is_64bit_integer_v = std::is_same_v<T, int64_t>
|| std::is_same_v<T, uint64_t>;
} // namespace caf::detail
#undef CAF_HAS_MEMBER_TRAIT
......
......@@ -115,6 +115,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]] string_view field_type_suffix() const noexcept {
return field_type_suffix_;
}
/// Configures whether the writer omits empty fields.
void field_type_suffix(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, string_view name) override;
bool end_object() override;
bool begin_field(string_view) override;
bool begin_field(string_view name, bool is_present) override;
bool begin_field(string_view name, span<const type_id_t> types,
size_t index) override;
bool begin_field(string_view name, bool is_present,
span<const type_id_t> types, size_t index) override;
bool end_field() override;
bool begin_tuple(size_t size) override;
bool end_tuple() override;
bool begin_key_value_pair() override;
bool end_key_value_pair() override;
bool begin_sequence(size_t size) override;
bool end_sequence() override;
bool begin_associative_array(size_t size) override;
bool end_associative_array() override;
bool value(byte x) override;
bool value(bool x) override;
bool value(int8_t x) override;
bool value(uint8_t x) override;
bool value(int16_t x) override;
bool value(uint16_t x) override;
bool value(int32_t x) override;
bool value(uint32_t x) override;
bool value(int64_t x) override;
bool value(uint64_t x) override;
bool value(float x) override;
bool value(double x) override;
bool value(long double x) override;
bool value(string_view x) override;
bool value(const std::u16string& x) override;
bool value(const std::u32string& x) override;
bool value(span<const byte> x) override;
private:
// -- implementation details -------------------------------------------------
template <class T>
bool number(T);
using key_type = 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;
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<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;
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(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(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 "caf/string_view.hpp"
#include <string>
namespace caf {
/// Represents an immutable JSON value.
class CAF_CORE_EXPORT json_value {
public:
// -- constructors, destructors, and assignment operators --------------------
/// Creates a @c null JSON value.
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 --------------------------------------------------------------
/// Creates an undefined JSON value. This special state usually indicates that
/// a previous key lookup failed.
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;
/// Checks whether the value is an @c uint64_t.
bool is_unsigned() const noexcept;
/// 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, a @c uint64_t
/// or a @c double.
bool is_number() const noexcept;
/// 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 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 -------------------------------------------------------------
/// Converts the value to an @c int64_t or returns @p fallback if the value
/// is not a valid number.
int64_t to_integer(int64_t fallback = 0) const;
/// Converts the value to an @c uint64_t or returns @p fallback if the value
/// is not a valid number.
uint64_t to_unsigned(uint64_t fallback = 0) const;
/// Converts the value to a @c double or returns @p fallback if the value is
/// not a valid number.
double to_double(double fallback = 0.0) const;
/// Converts the value to a @c bool or returns @p fallback if the value is
/// not a valid boolean.
bool to_bool(bool fallback = false) const;
/// Returns the value as a JSON string or an empty string if the value is not
/// a string.
string_view to_string() const;
/// Returns the value as a JSON string or @p fallback if the value is not a
/// string.
string_view to_string(string_view fallback) const;
/// Returns the value as a JSON array or an empty array if the value is not
/// an array.
json_array to_array() const;
/// Returns the value as a JSON array or @p fallback if the value is not an
/// array.
json_array to_array(json_array fallback) const;
/// Returns the value as a JSON object or an empty object if the value is not
/// an object.
json_object to_object() const;
/// Returns the value as a JSON object or @p fallback if the value is not an
/// object.
json_object to_object(json_object fallback) const;
// -- comparison -------------------------------------------------------------
/// Compares two JSON values for equality.
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(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(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 ---------------------------------------------------------------
/// Prints the JSON value to @p buf.
template <class Buffer>
void print_to(Buffer& buf, size_t indentation_factor = 0) const {
detail::json::print_to(buf, *val_, indentation_factor);
}
// -- serialization ----------------------------------------------------------
/// Applies the `Inspector` to the JSON value `val`.
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.
......@@ -261,11 +261,6 @@ private:
// followed by a newline.
void sep();
// Closes a nested structure like lists or objects. Traces back to see if only
// whitespaces were added between the open character and the current position.
// If so, compress the output to produce "[]" instead of "[\n \n]".
void close_nested(char open, char close);
// -- member variables -------------------------------------------------------
// The current level of indentation.
......@@ -305,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
......@@ -6,16 +6,18 @@
#include <cctype>
#include <cstdint>
#include <string_view>
#include "caf/fwd.hpp"
#include "caf/pec.hpp"
#include "caf/string_view.hpp"
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;
......
......@@ -66,6 +66,8 @@ enum class pec : uint8_t {
/// Stopped after running into an invalid parser state. Should never happen
/// and most likely indicates a bug in the implementation.
invalid_state,
/// Parser stopped after exceeding its maximum supported level of nesting.
nested_too_deeply,
};
/// @relates pec
......
......@@ -417,6 +417,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))
......
This diff is collapsed.
......@@ -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 diff is collapsed.
// This file is part of CAF, the C++ Actor Framework. See the file LICENSE in
// the main distribution directory for license terms and copyright or visit
// https://github.com/actor-framework/actor-framework/blob/master/LICENSE.
#include "caf/json_object.hpp"
namespace caf {
// -- properties ---------------------------------------------------------------
json_value json_object::value(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";
......@@ -50,23 +52,24 @@ std::string type_clash(caf::json_reader::position want,
std::string type_clash(caf::string_view want,
const caf::detail::json::value& got) {
using namespace caf::literals;
using namespace std::literals;
using caf::detail::json::value;
switch (got.data.index()) {
case value::integer_index:
return type_clash(want, "json::integer"_sv);
case value::unsigned_index:
return type_clash(want, "json::integer"sv);
case value::double_index:
return type_clash(want, "json::real"_sv);
return type_clash(want, "json::real"sv);
case value::bool_index:
return type_clash(want, "json::boolean"_sv);
return type_clash(want, "json::boolean"sv);
case value::string_index:
return type_clash(want, "json::string"_sv);
return type_clash(want, "json::string"sv);
case value::array_index:
return type_clash(want, "json::array"_sv);
return type_clash(want, "json::array"sv);
case value::object_index:
return type_clash(want, "json::object"_sv);
return type_clash(want, "json::object"sv);
default:
return type_clash(want, "json::null"_sv);
return type_clash(want, "json::null"sv);
}
}
......@@ -145,19 +148,41 @@ json_reader::~json_reader() {
bool json_reader::load(string_view json_text) {
reset();
string_parser_state ps{json_text.begin(), json_text.end()};
root_ = detail::json::parse(ps, &buf_);
root_ = detail::json::parse_shallow(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;
}
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;
}
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() {
......@@ -166,6 +191,7 @@ void json_reader::revert() {
err_.reset();
st_->clear();
st_->emplace_back(root_);
field_.clear();
}
}
......@@ -173,6 +199,7 @@ void json_reader::reset() {
buf_.reclaim();
st_ = nullptr;
err_.reset();
field_.clear();
}
// -- interface functions ------------------------------------------------------
......@@ -199,7 +226,7 @@ bool json_reader::fetch_next_object_name(string_view& type_name) {
FN_DECL;
return consume<false>(fn, [this, &type_name](const detail::json::value& val) {
if (val.data.index() == detail::json::value::object_index) {
auto& obj = get<detail::json::object>(val.data);
auto& obj = std::get<detail::json::object>(val.data);
if (auto mem_ptr = find_member(&obj, "@type")) {
if (mem_ptr->val->data.index() == detail::json::value::string_index) {
type_name = std::get<string_view>(mem_ptr->val->data);
......@@ -227,7 +254,7 @@ bool json_reader::begin_object(type_id_t, string_view) {
FN_DECL;
return consume<false>(fn, [this](const detail::json::value& val) {
if (val.data.index() == detail::json::value::object_index) {
push(&get<detail::json::object>(val.data));
push(&std::get<detail::json::object>(val.data));
return true;
} else {
emplace_error(sec::runtime_error, class_name, fn, current_field_name(),
......@@ -260,8 +287,8 @@ bool json_reader::end_object() {
bool json_reader::begin_field(string_view name) {
SCOPE(position::object);
if (auto member = find_member(top<position::object>(), name)) {
field_.push_back(name);
if (auto member = find_member(top<position::object>(), name)) {
push(member->val);
return true;
} else {
......@@ -273,10 +300,10 @@ bool json_reader::begin_field(string_view name) {
bool json_reader::begin_field(string_view name, bool& is_present) {
SCOPE(position::object);
field_.push_back(name);
if (auto member = find_member(top<position::object>(), name);
member != nullptr
&& member->val->data.index() != detail::json::value::null_index) {
field_.push_back(name);
push(member->val);
is_present = true;
} else {
......@@ -285,8 +312,8 @@ bool json_reader::begin_field(string_view name, bool& is_present) {
return true;
}
bool json_reader::begin_field(string_view name, span<const type_id_t> types,
size_t& index) {
bool json_reader::begin_field(string_view name,
span<const type_id_t> types, size_t& index) {
bool is_present = false;
if (begin_field(name, is_present, types, index)) {
if (is_present) {
......@@ -304,6 +331,7 @@ bool json_reader::begin_field(string_view name, span<const type_id_t> types,
bool json_reader::begin_field(string_view name, bool& is_present,
span<const type_id_t> types, size_t& index) {
SCOPE(position::object);
field_.push_back(name);
if (auto member = find_member(top<position::object>(), name);
member != nullptr
&& member->val->data.index() != detail::json::value::null_index) {
......@@ -312,7 +340,6 @@ bool json_reader::begin_field(string_view name, bool& is_present,
if (auto i = std::find(types.begin(), types.end(), id);
i != types.end()) {
index = static_cast<size_t>(std::distance(types.begin(), i));
field_.push_back(name);
push(member->val);
is_present = true;
return true;
......@@ -327,6 +354,7 @@ bool json_reader::end_field() {
SCOPE(position::object);
// Note: no pop() here, because the value(s) were already consumed. Only
// update the field_ for debugging.
if (!field_.empty())
field_.pop_back();
return true;
}
......@@ -379,7 +407,7 @@ bool json_reader::begin_sequence(size_t& size) {
FN_DECL;
return consume<false>(fn, [this, &size](const detail::json::value& val) {
if (val.data.index() == detail::json::value::array_index) {
auto& ls = get<detail::json::array>(val.data);
auto& ls = std::get<detail::json::array>(val.data);
size = ls.size();
push(sequence{ls.begin(), ls.end()});
return true;
......@@ -410,7 +438,7 @@ bool json_reader::begin_associative_array(size_t& size) {
FN_DECL;
return consume<false>(fn, [this, &size](const detail::json::value& val) {
if (val.data.index() == detail::json::value::object_index) {
auto* obj = std::addressof(get<detail::json::object>(val.data));
auto* obj = std::addressof(std::get<detail::json::object>(val.data));
pop();
size = obj->size();
push(members{obj->begin(), obj->end()});
......@@ -507,11 +535,19 @@ bool json_reader::integer(T& x) {
if (detail::bounds_checker<T>::check(i64)) {
x = static_cast<T>(i64);
return true;
} else {
}
emplace_error(sec::runtime_error, class_name, fn,
"integer out of bounds");
"signed integer out of bounds");
return false;
} else if (val.data.index() == detail::json::value::unsigned_index) {
auto u64 = std::get<uint64_t>(val.data);
if (detail::bounds_checker<T>::check(u64)) {
x = static_cast<T>(u64);
return true;
}
emplace_error(sec::runtime_error, class_name, fn,
"unsigned integer out of bounds");
return false;
} else {
emplace_error(sec::runtime_error, class_name, fn, current_field_name(),
type_clash("json::integer", val));
......@@ -565,10 +601,17 @@ bool json_reader::value(float& x) {
bool json_reader::value(double& x) {
FN_DECL;
return consume<true>(fn, [this, &x](const detail::json::value& val) {
if (val.data.index() == detail::json::value::double_index) {
switch (val.data.index()) {
case detail::json::value::double_index:
x = std::get<double>(val.data);
return true;
} else {
case detail::json::value::integer_index:
x = std::get<int64_t>(val.data);
return true;
case detail::json::value::unsigned_index:
x = std::get<uint64_t>(val.data);
return true;
default:
emplace_error(sec::runtime_error, class_name, fn, current_field_name(),
type_clash("json::real", val));
return false;
......
// 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 <cstdint>
#include <fstream>
namespace caf {
// -- properties ---------------------------------------------------------------
bool json_value::is_integer() const noexcept {
return val_->is_integer()
|| (val_->is_unsigned()
&& std::get<uint64_t>(val_->data) <= INT64_MAX);
}
bool json_value::is_unsigned() const noexcept {
return val_->is_unsigned()
|| (val_->is_integer() && std::get<int64_t>(val_->data) >= 0);
}
bool json_value::is_number() const noexcept {
switch (val_->data.index()) {
default:
return false;
case detail::json::value::integer_index:
case detail::json::value::unsigned_index:
case detail::json::value::double_index:
return true;
}
}
// -- conversion ---------------------------------------------------------------
int64_t json_value::to_integer(int64_t fallback) const {
switch (val_->data.index()) {
default:
return fallback;
case detail::json::value::integer_index:
return std::get<int64_t>(val_->data);
case detail::json::value::unsigned_index: {
auto val = std::get<uint64_t>(val_->data);
if (val <= INT64_MAX)
return static_cast<int64_t>(val);
return fallback;
}
case detail::json::value::double_index:
return static_cast<int64_t>(std::get<double>(val_->data));
}
}
uint64_t json_value::to_unsigned(uint64_t fallback) const {
switch (val_->data.index()) {
default:
return fallback;
case detail::json::value::integer_index: {
auto val = std::get<int64_t>(val_->data);
if (val >= 0)
return static_cast<uint64_t>(val);
return fallback;
}
case detail::json::value::unsigned_index:
return std::get<uint64_t>(val_->data);
case detail::json::value::double_index:
return static_cast<int64_t>(std::get<double>(val_->data));
}
}
double json_value::to_double(double fallback) const {
switch (val_->data.index()) {
default:
return fallback;
case detail::json::value::integer_index:
return static_cast<double>(std::get<int64_t>(val_->data));
case detail::json::value::unsigned_index:
return static_cast<double>(std::get<uint64_t>(val_->data));
case detail::json::value::double_index:
return std::get<double>(val_->data);
};
}
bool json_value::to_bool(bool fallback) const {
if (is_bool()) {
return std::get<bool>(val_->data);
}
return fallback;
}
string_view json_value::to_string() const {
return to_string(string_view{});
}
string_view json_value::to_string(string_view fallback) const {
if (is_string()) {
return std::get<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(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(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
......@@ -7,24 +7,21 @@
#include "caf/detail/append_hex.hpp"
#include "caf/detail/print.hpp"
#include <cctype>
namespace caf {
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",
constexpr std::string_view 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)];
char last_non_ws_char(const std::vector<char>& buf) {
auto not_ws = [](char c) { return !std::isspace(c); };
auto last = buf.rend();
auto i = std::find_if(buf.rbegin(), last, not_ws);
return (i != last) ? *i : '\0';
}
} // namespace
......@@ -99,7 +96,7 @@ bool json_writer::begin_object(type_id_t id, 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, ...
......@@ -135,7 +132,7 @@ bool json_writer::begin_field(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;
}
......@@ -157,8 +154,8 @@ bool json_writer::begin_field(string_view name, bool is_present) {
}
}
bool json_writer::begin_field(string_view name, span<const type_id_t> types,
size_t index) {
bool json_writer::begin_field(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;
......@@ -217,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;
}
......@@ -249,7 +246,14 @@ bool json_writer::begin_sequence(size_t) {
bool json_writer::end_sequence() {
if (pop_if(type::array)) {
--indentation_level_;
close_nested('[', ']');
// Check whether the array was empty and compress the output in that case.
if (last_non_ws_char(buf_) == '[') {
while (std::isspace(buf_.back()))
buf_.pop_back();
} else {
nl();
}
add(']');
return true;
} else {
return false;
......@@ -279,7 +283,14 @@ bool json_writer::begin_associative_array(size_t) {
bool json_writer::end_associative_array() {
if (pop_if(type::object)) {
--indentation_level_;
close_nested('{', '}');
// Check whether the array was empty and compress the output in that case.
if (last_non_ws_char(buf_) == '{') {
while (std::isspace(buf_.back()))
buf_.pop_back();
} else {
nl();
}
add('}');
if (!stack_.empty())
stack_.back().filled = true;
return true;
......@@ -470,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;
......@@ -490,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;
......@@ -517,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;
}
......@@ -536,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));
}
......@@ -570,16 +581,10 @@ void json_writer::sep() {
}
}
void json_writer::close_nested(char open, char close) {
auto not_ws = [](char c) { return !std::isspace(c); };
auto i = std::find_if(buf_.rbegin(), buf_.rend(), not_ws);
if (*i == open) {
while (std::isspace(buf_.back()))
buf_.pop_back();
} else {
nl();
}
add(close);
// -- free functions -----------------------------------------------------------
std::string_view as_json_type_name(json_writer::type t) {
return json_type_names[static_cast<uint8_t>(t)];
}
} // namespace caf
......@@ -85,6 +85,10 @@ void stringify(std::string& str, size_t, int64_t val) {
str += std::to_string(val);
}
void stringify(std::string& str, size_t, uint64_t val) {
str += std::to_string(val);
}
void stringify(std::string& str, size_t, double val) {
str += std::to_string(val);
}
......@@ -106,6 +110,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 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 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<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);
}
......@@ -113,7 +113,7 @@ fixture::fixture() {
} // namespace
CAF_TEST_FIXTURE_SCOPE(json_reader_tests, fixture)
BEGIN_FIXTURE_SCOPE(fixture)
CAF_TEST(json baselines) {
size_t baseline_index = 0;
......@@ -179,4 +179,4 @@ SCENARIO("mappers enable custom type names in JSON input") {
}
}
CAF_TEST_FIXTURE_SCOPE_END()
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_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_unsigned(), 0u);
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_unsigned(), 0u);
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 negative integer") {
auto val = unbox(json_value::parse("-1"));
CHECK(!val.is_null());
CHECK(!val.is_undefined());
CHECK(val.is_integer());
CHECK(!val.is_unsigned());
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(), -1);
CHECK_EQ(val.to_unsigned(), 0u);
CHECK_EQ(val.to_double(), -1.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), "-1");
CHECK_EQ(printed(val), "-1");
CHECK_EQ(deep_copy(val), val);
}
TEST_CASE("from small integer") {
// A small integer can be represented as both int64_t and uint64_t.
auto val = unbox(json_value::parse("42"));
CHECK(!val.is_null());
CHECK(!val.is_undefined());
CHECK(val.is_integer());
CHECK(val.is_unsigned());
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_unsigned(), 42u);
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 UINT64_MAX") {
auto val = unbox(json_value::parse("18446744073709551615"));
CHECK(!val.is_null());
CHECK(!val.is_undefined());
CHECK(!val.is_integer());
CHECK(val.is_unsigned());
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_unsigned(), UINT64_MAX);
CHECK_EQ(val.to_double(), static_cast<double>(UINT64_MAX));
CHECK_EQ(val.to_bool(), false);
CHECK_EQ(val.to_string(), ""sv);
CHECK_EQ(val.to_object().size(), 0u);
CHECK_EQ(to_string(val), "18446744073709551615");
CHECK_EQ(printed(val), "18446744073709551615");
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_unsigned(), 42u);
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_unsigned(), 0u);
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_unsigned(), 0u);
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_unsigned(), 0u);
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_unsigned(), 0u);
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_unsigned(), 0u);
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_unsigned(), 0u);
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_unsigned(), 0u);
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);
}
......@@ -38,7 +38,7 @@ struct fixture {
} // namespace
CAF_TEST_FIXTURE_SCOPE(json_writer_tests, fixture)
BEGIN_FIXTURE_SCOPE(fixture)
SCENARIO("the JSON writer converts builtin types to strings") {
GIVEN("an integer") {
......@@ -314,4 +314,33 @@ SCENARIO("the JSON writer annotates variant fields") {
}
}
CAF_TEST_FIXTURE_SCOPE_END()
SCENARIO("the JSON compresses empty lists and objects") {
GIVEN("a map with an empty list value") {
std::map<std::string, std::vector<int>> obj;
obj["xs"] = std::vector<int>{};
obj["ys"] = std::vector<int>{1, 2, 3};
WHEN("converting it to JSON with indentation factor 2") {
THEN("the JSON contains a compressed representation of the empty list") {
std::string out = R"({
"xs": [],
"ys": [
1,
2,
3
]
})";
CHECK_EQ(to_json_string(obj, 2, true, true), out);
}
}
}
GIVEN("an empty map") {
std::map<std::string, std::vector<int>> obj;
WHEN("converting it to JSON with indentation factor 2") {
THEN("the JSON contains a compressed representation of the empty map") {
CHECK_EQ(to_json_string(obj, 2, true, true), "{}"s);
}
}
}
}
END_FIXTURE_SCOPE()
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