Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
36 changes: 28 additions & 8 deletions include/behaviortree_cpp/json_export.h
Original file line number Diff line number Diff line change
Expand Up @@ -112,19 +112,33 @@ class JsonExporter
*
* @param to_json the function with signature void(const T&, nlohmann::json&)
* @param add_type if true, add a field called [__type] with the name of the type.
* @param type_name the name stored in [__type]. If empty, the demangled name of T
* is used. Pass the same name to the from_json overload.
*/
template <typename T>
void addConverter(std::function<void(const T&, nlohmann::json&)> to_json,
bool add_type = true);
bool add_type = true, const std::string& type_name = {});

/// Same as above with add_type = true. Without this overload, a string literal
/// passed as second argument would silently convert to add_type.
template <typename T>
void addConverter(std::function<void(const T&, nlohmann::json&)> to_json,
const char* type_name)
{
addConverter<T>(std::move(to_json), true, type_name);
}

/**
* @brief addConverter register a from_json function that converts a json to a type T.
* The conversions from std::vector<T> is automatically registered.
*
* @param from_json the function with signature void(const nlohmann::json&, T&)
* @param type_name an additional name of T accepted in the field [__type]. The
* demangled name of T is always accepted.
*/
template <typename T>
void addConverter(std::function<void(const nlohmann::json&, T&)> from_json);
void addConverter(std::function<void(const nlohmann::json&, T&)> from_json,
const std::string& type_name = {});

private:
using ToJonConverter = std::function<void(const BT::Any&, nlohmann::json&)>;
Expand Down Expand Up @@ -196,14 +210,16 @@ inline void JsonExporter::addConverter()
}

template <typename T>
inline void JsonExporter::addConverter(
std::function<void(const T&, nlohmann::json&)> func, bool add_type)
inline void
JsonExporter::addConverter(std::function<void(const T&, nlohmann::json&)> func,
bool add_type, const std::string& type_name)
{
auto converter = [func, add_type](const BT::Any& entry, nlohmann::json& json) {
const std::string name = type_name.empty() ? BT::demangle(typeid(T)) : type_name;
auto converter = [func, add_type, name](const BT::Any& entry, nlohmann::json& json) {
func(entry.cast<T>(), json);
if(add_type)
{
json["__type"] = BT::demangle(typeid(T));
json["__type"] = name;
}
};
//---------------------------------------------
Expand All @@ -222,15 +238,19 @@ inline void JsonExporter::addConverter(
}

template <typename T>
inline void
JsonExporter::addConverter(std::function<void(const nlohmann::json&, T&)> func)
inline void JsonExporter::addConverter(
std::function<void(const nlohmann::json&, T&)> func, const std::string& type_name)
{
auto converter = [func](const nlohmann::json& json) -> Entry {
T tmp;
func(json, tmp);
return { BT::Any(tmp), BT::TypeInfo::Create<T>() };
};
type_names_.insert({ BT::demangle(typeid(T)), BT::TypeInfo::Create<T>() });
if(!type_name.empty())
{
type_names_.insert({ type_name, BT::TypeInfo::Create<T>() });
}
from_json_converters_.insert({ typeid(T), std::move(converter) });
//---------------------------------------------
// add the vector<T> converter
Expand Down
55 changes: 55 additions & 0 deletions tests/gtest_json.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,11 @@ struct Time
uint32_t nsec;
};

struct Temperature
{
double celsius = 0;
};

// NOLINTBEGIN(misc-use-internal-linkage)
BT_JSON_CONVERTER(Vector3D, v)
{
Expand Down Expand Up @@ -82,6 +87,16 @@ inline void jsonToTime(const nlohmann::json& j, Time& t)
t.sec = static_cast<uint32_t>(sec);
t.nsec = static_cast<uint32_t>((sec - t.sec) * 1e9);
}

inline void jsonFromTemperature(const Temperature& t, nlohmann::json& j)
{
j["celsius"] = t.celsius;
}

inline void jsonToTemperature(const nlohmann::json& j, Temperature& t)
{
t.celsius = j["celsius"];
}
// NOLINTEND(misc-use-internal-linkage)

} // namespace TestTypes
Expand Down Expand Up @@ -177,6 +192,46 @@ TEST_F(JsonTest, CustomTime)
}
}

// Issue #877: the name stored in "__type" can be chosen when registering lambdas
TEST_F(JsonTest, CustomTypeName)
{
BT::JsonExporter& exporter = BT::JsonExporter::get();
const std::string name = "Temperature";
// a string literal as second argument is the type name, not add_type
using TestTypes::Temperature;
exporter.addConverter<Temperature>(TestTypes::jsonFromTemperature, "Temperature");
exporter.addConverter<TestTypes::Temperature>(TestTypes::jsonToTemperature, name);

TestTypes::Temperature temp = { 21.5 };
nlohmann::json json;
ASSERT_TRUE(exporter.toJson(BT::Any(temp), json));
ASSERT_EQ(json["__type"], name);

auto res = exporter.fromJson<TestTypes::Temperature>(json);
ASSERT_TRUE(res) << res.error();
ASSERT_EQ(res->celsius, 21.5);

// the demangled name is still accepted
json["__type"] = BT::demangle(typeid(TestTypes::Temperature));
ASSERT_TRUE(exporter.fromJson<TestTypes::Temperature>(json));

// vectors use the same name
std::vector<TestTypes::Temperature> temps = { { 1.5 }, { 2.5 } };
nlohmann::json json_vec;
ASSERT_TRUE(exporter.toJson(BT::Any(temps), json_vec));
ASSERT_EQ(json_vec[1]["__type"], name);

auto res_vec = exporter.fromJson<std::vector<TestTypes::Temperature>>(json_vec);
ASSERT_TRUE(res_vec) << res_vec.error();
ASSERT_EQ(res_vec->size(), 2u);
ASSERT_EQ((*res_vec)[1].celsius, 2.5);

// without a name, the demangled name of the type is used
TestTypes::Time stamp = { 3, 8000000 };
ASSERT_TRUE(exporter.toJson(BT::Any(stamp), json));
ASSERT_EQ(json["__type"], BT::demangle(typeid(TestTypes::Time)));
}

TEST_F(JsonTest, ConvertFromString)
{
TestTypes::Vector3D vect;
Expand Down
Loading