Commit 0facd386 authored by Dominik Charousset's avatar Dominik Charousset

Add new API for customizing type names in JSON

parent a6583d46
...@@ -112,6 +112,16 @@ public: ...@@ -112,6 +112,16 @@ public:
field_type_suffix_ = suffix; field_type_suffix_ = suffix;
} }
/// Returns the type ID mapper used by the writer.
[[nodiscard]] const type_id_mapper* mapper() const noexcept {
return mapper_;
}
/// Changes the type ID mapper for the writer.
void mapper(const type_id_mapper* ptr) noexcept {
mapper_ = ptr;
}
// -- modifiers -------------------------------------------------------------- // -- modifiers --------------------------------------------------------------
/// Parses @p json_text into an internal representation. After loading the /// Parses @p json_text into an internal representation. After loading the
...@@ -247,6 +257,12 @@ private: ...@@ -247,6 +257,12 @@ private:
/// Keeps track of the current field for better debugging output. /// Keeps track of the current field for better debugging output.
std::vector<string_view> field_; std::vector<string_view> field_;
/// The mapper implementation we use by default.
default_type_id_mapper default_mapper_;
/// Configures which ID mapper we use to translate between type IDs and names.
const type_id_mapper* mapper_ = &default_mapper_;
}; };
} // namespace caf } // namespace caf
...@@ -115,6 +115,16 @@ public: ...@@ -115,6 +115,16 @@ public:
field_type_suffix_ = suffix; field_type_suffix_ = suffix;
} }
/// Returns the type ID mapper used by the writer.
[[nodiscard]] const type_id_mapper* mapper() const noexcept {
return mapper_;
}
/// Changes the type ID mapper for the writer.
void mapper(const type_id_mapper* ptr) noexcept {
mapper_ = ptr;
}
// -- 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
...@@ -251,6 +261,11 @@ private: ...@@ -251,6 +261,11 @@ private:
// followed by a newline. // followed by a newline.
void sep(); 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 ------------------------------------------------------- // -- member variables -------------------------------------------------------
// The current level of indentation. // The current level of indentation.
...@@ -280,7 +295,14 @@ private: ...@@ -280,7 +295,14 @@ private:
// Configures whether we omit the top-level '@type' annotation. // Configures whether we omit the top-level '@type' annotation.
bool skip_object_type_annotation_ = false; bool skip_object_type_annotation_ = false;
// Configures how we generate type annotations for fields.
string_view field_type_suffix_ = field_type_suffix_default; string_view field_type_suffix_ = field_type_suffix_default;
// The mapper implementation we use by default.
default_type_id_mapper default_mapper_;
// Configures which ID mapper we use to translate between type IDs and names.
const type_id_mapper* mapper_ = &default_mapper_;
}; };
} // namespace caf } // namespace caf
...@@ -4,13 +4,14 @@ ...@@ -4,13 +4,14 @@
#pragma once #pragma once
#include "caf/detail/core_export.hpp"
#include "caf/fwd.hpp"
#include "caf/timespan.hpp"
#include <chrono> #include <chrono>
#include <cstdint> #include <cstdint>
#include <string> #include <string>
#include "caf/detail/core_export.hpp"
#include "caf/timespan.hpp"
namespace caf { namespace caf {
/// A portable timestamp with nanosecond resolution anchored at the UNIX epoch. /// A portable timestamp with nanosecond resolution anchored at the UNIX epoch.
...@@ -23,6 +24,9 @@ CAF_CORE_EXPORT timestamp make_timestamp(); ...@@ -23,6 +24,9 @@ CAF_CORE_EXPORT timestamp make_timestamp();
/// Prints `x` in ISO 8601 format, e.g., `2018-11-15T06:25:01.462`. /// Prints `x` in ISO 8601 format, e.g., `2018-11-15T06:25:01.462`.
CAF_CORE_EXPORT std::string timestamp_to_string(timestamp x); CAF_CORE_EXPORT std::string timestamp_to_string(timestamp x);
/// Converts an ISO 8601 formatted timestamp into its native representation.
CAF_CORE_EXPORT expected<timestamp> timestamp_from_string(string_view str);
/// Appends the timestamp `x` in ISO 8601 format, e.g., /// Appends the timestamp `x` in ISO 8601 format, e.g.,
/// `2018-11-15T06:25:01.462`, to `y`. /// `2018-11-15T06:25:01.462`, to `y`.
CAF_CORE_EXPORT void append_timestamp_to_string(std::string& x, timestamp y); CAF_CORE_EXPORT void append_timestamp_to_string(std::string& x, timestamp y);
......
...@@ -96,13 +96,34 @@ type_id_t type_id_or_invalid() { ...@@ -96,13 +96,34 @@ type_id_t type_id_or_invalid() {
return invalid_type_id; return invalid_type_id;
} }
/// Returns the type name of given `type` or an empty string if `type` is an /// Returns the type name for @p type or an empty string if @p type is an
/// invalid ID. /// invalid ID.
CAF_CORE_EXPORT string_view query_type_name(type_id_t type); CAF_CORE_EXPORT string_view query_type_name(type_id_t type);
/// Returns the type of given `name` or `invalid_type_id` if no type matches. /// Returns the type for @p name or `invalid_type_id` if @p name is unknown.
CAF_CORE_EXPORT type_id_t query_type_id(string_view name); CAF_CORE_EXPORT type_id_t query_type_id(string_view name);
/// Translates between human-readable type names and type IDs.
class CAF_CORE_EXPORT type_id_mapper {
public:
virtual ~type_id_mapper();
/// Returns the type name for @p type or an empty string if @p type is an
/// invalid ID.
virtual string_view operator()(type_id_t type) const = 0;
/// Returns the type for @p name or `invalid_type_id` if @p name is unknown.
virtual type_id_t operator()(string_view name) const = 0;
};
/// Dispatches to @ref query_type_name and @ref query_type_id.
class default_type_id_mapper : public type_id_mapper {
public:
string_view operator()(type_id_t type) const override;
type_id_t operator()(string_view name) const override;
};
} // namespace caf } // namespace caf
// -- CAF_BEGIN_TYPE_ID_BLOCK -------------------------------------------------- // -- CAF_BEGIN_TYPE_ID_BLOCK --------------------------------------------------
......
...@@ -180,7 +180,7 @@ void json_reader::reset() { ...@@ -180,7 +180,7 @@ void json_reader::reset() {
bool json_reader::fetch_next_object_type(type_id_t& type) { bool json_reader::fetch_next_object_type(type_id_t& type) {
string_view type_name; string_view type_name;
if (fetch_next_object_name(type_name)) { if (fetch_next_object_name(type_name)) {
if (auto id = query_type_id(type_name); id != invalid_type_id) { if (auto id = (*mapper_)(type_name); id != invalid_type_id) {
type = id; type = id;
return true; return true;
} else { } else {
...@@ -308,7 +308,7 @@ bool json_reader::begin_field(string_view name, bool& is_present, ...@@ -308,7 +308,7 @@ bool json_reader::begin_field(string_view name, bool& is_present,
member != nullptr member != nullptr
&& member->val->data.index() != detail::json::value::null_index) { && member->val->data.index() != detail::json::value::null_index) {
auto ft = field_type(top<position::object>(), name, field_type_suffix_); auto ft = field_type(top<position::object>(), name, field_type_suffix_);
if (auto id = query_type_id(ft); id != invalid_type_id) { if (auto id = (*mapper_)(ft); id != invalid_type_id) {
if (auto i = std::find(types.begin(), types.end(), id); if (auto i = std::find(types.begin(), types.end(), id);
i != types.end()) { i != types.end()) {
index = static_cast<size_t>(std::distance(types.begin(), i)); index = static_cast<size_t>(std::distance(types.begin(), i));
......
...@@ -7,6 +7,8 @@ ...@@ -7,6 +7,8 @@
#include "caf/detail/append_hex.hpp" #include "caf/detail/append_hex.hpp"
#include "caf/detail/print.hpp" #include "caf/detail/print.hpp"
#include <cctype>
namespace caf { namespace caf {
namespace { namespace {
...@@ -85,7 +87,7 @@ bool json_writer::begin_object(type_id_t id, string_view name) { ...@@ -85,7 +87,7 @@ bool json_writer::begin_object(type_id_t id, string_view name) {
add(R"_("@type": )_"); add(R"_("@type": )_");
pop(); pop();
CAF_ASSERT(top() == type::element); CAF_ASSERT(top() == type::element);
if (auto tname = query_type_name(id); !tname.empty()) { if (auto tname = (*mapper_)(id); !tname.empty()) {
add('"'); add('"');
add(tname); add(tname);
add('"'); add('"');
...@@ -170,12 +172,12 @@ bool json_writer::begin_field(string_view name, span<const type_id_t> types, ...@@ -170,12 +172,12 @@ bool json_writer::begin_field(string_view name, span<const type_id_t> types,
pop(); pop();
CAF_ASSERT(top() == type::element); CAF_ASSERT(top() == type::element);
pop(); pop();
if (auto tname = query_type_name(types[index]); !tname.empty()) { if (auto tname = (*mapper_)(types[index]); !tname.empty()) {
add('"'); add('"');
add(tname); add(tname);
add('"'); add('"');
} else { } else {
emplace_error(sec::runtime_error, "query_type_name failed"); emplace_error(sec::runtime_error, "failed to retrieve type name");
return false; return false;
} }
return end_key_value_pair() && begin_field(name); return end_key_value_pair() && begin_field(name);
...@@ -247,8 +249,7 @@ bool json_writer::begin_sequence(size_t) { ...@@ -247,8 +249,7 @@ bool json_writer::begin_sequence(size_t) {
bool json_writer::end_sequence() { bool json_writer::end_sequence() {
if (pop_if(type::array)) { if (pop_if(type::array)) {
--indentation_level_; --indentation_level_;
nl(); close_nested('[', ']');
add(']');
return true; return true;
} else { } else {
return false; return false;
...@@ -278,8 +279,7 @@ bool json_writer::begin_associative_array(size_t) { ...@@ -278,8 +279,7 @@ bool json_writer::begin_associative_array(size_t) {
bool json_writer::end_associative_array() { bool json_writer::end_associative_array() {
if (pop_if(type::object)) { if (pop_if(type::object)) {
--indentation_level_; --indentation_level_;
nl(); close_nested('{', '}');
add('}');
if (!stack_.empty()) if (!stack_.empty())
stack_.back().filled = true; stack_.back().filled = true;
return true; return true;
...@@ -570,4 +570,16 @@ void json_writer::sep() { ...@@ -570,4 +570,16 @@ 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);
}
} // namespace caf } // namespace caf
...@@ -2,8 +2,10 @@ ...@@ -2,8 +2,10 @@
// the main distribution directory for license terms and copyright or visit // the main distribution directory for license terms and copyright or visit
// https://github.com/actor-framework/actor-framework/blob/master/LICENSE. // https://github.com/actor-framework/actor-framework/blob/master/LICENSE.
#include "caf/deep_to_string.hpp"
#include "caf/timestamp.hpp" #include "caf/timestamp.hpp"
#include "caf/deep_to_string.hpp"
#include "caf/detail/parse.hpp"
#include "caf/expected.hpp"
namespace caf { namespace caf {
...@@ -15,6 +17,14 @@ std::string timestamp_to_string(timestamp x) { ...@@ -15,6 +17,14 @@ std::string timestamp_to_string(timestamp x) {
return deep_to_string(x.time_since_epoch().count()); return deep_to_string(x.time_since_epoch().count());
} }
expected<timestamp> timestamp_from_string(string_view str) {
timestamp result;
if (auto err = detail::parse(str, result); !err)
return result;
else
return err;
}
void append_timestamp_to_string(std::string& x, timestamp y) { void append_timestamp_to_string(std::string& x, timestamp y) {
x += timestamp_to_string(y); x += timestamp_to_string(y);
} }
......
...@@ -22,4 +22,16 @@ type_id_t query_type_id(string_view name) { ...@@ -22,4 +22,16 @@ type_id_t query_type_id(string_view name) {
return invalid_type_id; return invalid_type_id;
} }
type_id_mapper::~type_id_mapper() {
// nop
}
string_view default_type_id_mapper::operator()(type_id_t type) const {
return query_type_name(type);
}
type_id_t default_type_id_mapper::operator()(string_view name) const {
return query_type_id(name);
}
} // namespace caf } // namespace caf
...@@ -12,6 +12,8 @@ ...@@ -12,6 +12,8 @@
using namespace caf; using namespace caf;
using namespace std::literals;
namespace { namespace {
struct fixture { struct fixture {
...@@ -124,4 +126,57 @@ CAF_TEST(json baselines) { ...@@ -124,4 +126,57 @@ CAF_TEST(json baselines) {
} }
} }
SCENARIO("mappers enable custom type names in JSON input") {
GIVEN("a custom mapper") {
class custom_mapper : public type_id_mapper {
string_view operator()(type_id_t type) const override {
switch (type) {
case type_id_v<std::string>:
return "String";
case type_id_v<int32_t>:
return "Int";
default:
return query_type_name(type);
}
}
type_id_t operator()(string_view name) const override {
if (name == "String")
return type_id_v<std::string>;
else if (name == "Int")
return type_id_v<int32_t>;
else
return query_type_id(name);
}
};
custom_mapper mapper_instance;
WHEN("reading a variant from JSON") {
using value_type = std::variant<int32_t, std::string>;
THEN("the custom mapper translates between external and internal names") {
json_reader reader;
reader.mapper(&mapper_instance);
auto value = value_type{};
auto input1 = R"_({"@value-type": "String", "value": "hello world"})_"s;
if (CHECK(reader.load(input1))) {
if (!CHECK(reader.apply(value)))
MESSAGE("reader reported error: " << reader.get_error());
if (CHECK(std::holds_alternative<std::string>(value)))
CHECK_EQ(std::get<std::string>(value), "hello world"s);
} else {
MESSAGE("reader reported error: " << reader.get_error());
}
reader.reset();
auto input2 = R"_({"@value-type": "Int", "value": 42})_"sv;
if (CHECK(reader.load(input2))) {
if (!CHECK(reader.apply(value)))
MESSAGE("reader reported error: " << reader.get_error());
if (CHECK(std::holds_alternative<int32_t>(value)))
CHECK_EQ(std::get<int32_t>(value), 42);
} else {
MESSAGE("reader reported error: " << reader.get_error());
}
}
}
}
}
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