Commit 9549ca47 authored by Dominik Charousset's avatar Dominik Charousset

Omit empty fields in JSON output by default

parent 07e53a43
...@@ -33,6 +33,12 @@ public: ...@@ -33,6 +33,12 @@ public:
null, /// The literal "null" (terminal type). null, /// The literal "null" (terminal type).
}; };
// -- constants --------------------------------------------------------------
/// The value returned for `skip_empty_fields()` for a default-constructed
/// JSON writer.
static constexpr bool skip_empty_fields_default = true;
// -- constructors, destructors, and assignment operators -------------------- // -- constructors, destructors, and assignment operators --------------------
json_writer(); json_writer();
...@@ -71,6 +77,17 @@ public: ...@@ -71,6 +77,17 @@ public:
return indentation_factor_ == 0; return indentation_factor_ == 0;
} }
/// 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;
}
// -- modifiers -------------------------------------------------------------- // -- modifiers --------------------------------------------------------------
/// Removes all characters from the buffer and restores the writer to its /// Removes all characters from the buffer and restores the writer to its
...@@ -225,6 +242,10 @@ private: ...@@ -225,6 +242,10 @@ private:
// Bookkeeping for where we are in the current object. // Bookkeeping for where we are in the current object.
std::vector<entry> stack_; std::vector<entry> stack_;
// Configures whether we omit empty fields entirely (true) or render empty
// fields as `$field: null` (false).
bool skip_empty_fields_ = skip_empty_fields_default;
}; };
} // namespace caf } // namespace caf
...@@ -113,7 +113,20 @@ bool json_writer::begin_field(string_view name) { ...@@ -113,7 +113,20 @@ bool json_writer::begin_field(string_view name) {
} }
bool json_writer::begin_field(string_view name, bool is_present) { bool json_writer::begin_field(string_view name, bool is_present) {
if (begin_key_value_pair()) { if (skip_empty_fields_ && !is_present) {
auto t = top();
switch (t) {
case type::object:
push(type::member);
return true;
default: {
std::string str = "expected object, found ";
str += 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); CAF_ASSERT(top() == type::key);
detail::print_escaped(buf_, name); detail::print_escaped(buf_, name);
add(": "); add(": ");
......
...@@ -336,6 +336,17 @@ bool inspect(Inspector& f, rectangle& x) { ...@@ -336,6 +336,17 @@ bool inspect(Inspector& f, rectangle& x) {
return !(x == y); return !(x == y);
} }
struct dummy_user {
std::string name;
caf::optional<std::string> nickname;
};
template <class Inspector>
bool inspect(Inspector& f, dummy_user& x) {
return f.object(x).fields(f.field("name", x.name),
f.field("nickname", x.nickname));
}
// -- type IDs for for all unit test suites ------------------------------------ // -- type IDs for for all unit test suites ------------------------------------
#define ADD_TYPE_ID(type) CAF_ADD_TYPE_ID(core_test, type) #define ADD_TYPE_ID(type) CAF_ADD_TYPE_ID(core_test, type)
...@@ -350,6 +361,7 @@ CAF_BEGIN_TYPE_ID_BLOCK(core_test, caf::first_custom_type_id) ...@@ -350,6 +361,7 @@ CAF_BEGIN_TYPE_ID_BLOCK(core_test, caf::first_custom_type_id)
ADD_TYPE_ID((dummy_enum_class)) ADD_TYPE_ID((dummy_enum_class))
ADD_TYPE_ID((dummy_struct)) ADD_TYPE_ID((dummy_struct))
ADD_TYPE_ID((dummy_tag_type)) ADD_TYPE_ID((dummy_tag_type))
ADD_TYPE_ID((dummy_user))
ADD_TYPE_ID((fail_on_copy)) ADD_TYPE_ID((fail_on_copy))
ADD_TYPE_ID((float_actor)) ADD_TYPE_ID((float_actor))
ADD_TYPE_ID((foo_actor)) ADD_TYPE_ID((foo_actor))
......
...@@ -16,9 +16,13 @@ namespace { ...@@ -16,9 +16,13 @@ namespace {
struct fixture { struct fixture {
template <class T> template <class T>
expected<std::string> to_json_string(T&& x, size_t indentation_factor) { expected<std::string>
to_json_string(T&& x, size_t indentation,
bool skip_empty_fields
= json_writer::skip_empty_fields_default) {
json_writer writer; json_writer writer;
writer.indentation(indentation_factor); writer.indentation(indentation);
writer.skip_empty_fields(skip_empty_fields);
if (writer.apply(std::forward<T>(x))) { if (writer.apply(std::forward<T>(x))) {
auto buf = writer.str(); auto buf = writer.str();
return {std::string{buf.begin(), buf.end()}}; return {std::string{buf.begin(), buf.end()}};
...@@ -171,4 +175,24 @@ SCENARIO("the JSON writer converts nested structs to strings") { ...@@ -171,4 +175,24 @@ SCENARIO("the JSON writer converts nested structs to strings") {
} }
} }
SCENARIO("the JSON writer omits or nulls missing values") {
GIVEN("a dummy_user object without nickname") {
dummy_user user;
user.name = "Bjarne";
WHEN("converting it to JSON with skip_empty_fields = true (default)") {
THEN("the JSON output omits the field 'nickname'") {
std::string out = R"({"@type": "dummy_user", "name": "Bjarne"})";
CHECK_EQ(to_json_string(user, 0), out);
}
}
WHEN("converting it to JSON with skip_empty_fields = false") {
THEN("the JSON output includes the field 'nickname' with a null value") {
std::string out
= R"({"@type": "dummy_user", "name": "Bjarne", "nickname": null})";
CHECK_EQ(to_json_string(user, 0, false), out);
}
}
}
}
CAF_TEST_FIXTURE_SCOPE_END() CAF_TEST_FIXTURE_SCOPE_END()
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment