Skip to content

Commit

Permalink
WIP
Browse files Browse the repository at this point in the history
  • Loading branch information
facontidavide committed Feb 27, 2024
1 parent 3073f29 commit 66eed7d
Show file tree
Hide file tree
Showing 5 changed files with 123 additions and 104 deletions.
4 changes: 2 additions & 2 deletions examples/t13_custom_type.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -36,8 +36,8 @@ template <> inline

Vector4D output;
output.w = convertFromString<double>(parts[0]);
output.x = convertFromString<double>(parts[1]);
output.y = convertFromString<double>(parts[2]);
output.x = convertFromString<double>(parts[1]);
output.y = convertFromString<double>(parts[2]);
output.z = convertFromString<double>(parts[3]);
return output;
}
Expand Down
152 changes: 86 additions & 66 deletions include/behaviortree_cpp/json_export.h
Original file line number Diff line number Diff line change
Expand Up @@ -6,23 +6,46 @@
// Use the version nlohmann::json embedded in BT.CPP
#include "behaviortree_cpp/contrib/json.hpp"

namespace BT
{
namespace BT {

/**
* To add new type, you must follow these isntructions:
* https://json.nlohmann.me/features/arbitrary_types/
*
* For instance the type Foo requires the implementation:
* Considering for instance the type:
*
* struct Point2D {
* double x;
* double y;
* };
*
* This would require the implementation of:
*
* void to_json(nlohmann::json& j, const Point2D& point);
* void from_json(const nlohmann::json& j, Point2D& point);
*
* void to_json(json& j, const Foo& f);
* To avoir repeating yourself, we provide the macro BT_JSON_CONVERTION
* that implements both those function, at once.
* Usage:
*
* Later, you MUST register this calling:
* BT_JSON_CONVERTER(Point2D, point)
* {
* add_field("x", &point.x);
* add_field("y", &point.y);
* }
*
* RegisterJsonDefinition<Foo>();
* Later, you MUST register the type in main using:
*
* RegisterJsonDefinition<Point2D>();
*/

class JsonExporter{
//-----------------------------------------------------------------------------------

/**
* Use RegisterJsonDefinition<Foo>();
*/

class JsonExporter {

public:
static JsonExporter& get() {
Expand All @@ -43,81 +66,78 @@ class JsonExporter{
dst = val;
}

/// Register new JSON converters with addConverter<Foo>(),
/// But works only if this function is implemented:
///
/// void nlohmann::to_json(nlohmann::json& destination, const Foo& foo)
template <typename T> void addConverter()
{
auto converter = [](const BT::Any& entry, nlohmann::json& dst) {
nlohmann::to_json(dst, entry.cast<T>());
};
type_converters_.insert( {typeid(T), std::move(converter)} );
}

template <typename T> void addConverter(std::function<void(nlohmann::json&, const T&)> func)
{
auto converter = [func](const BT::Any& entry, nlohmann::json& dst) {
func(dst, entry.cast<T>());
};
type_converters_.insert( {typeid(T), std::move(converter)} );
}

/// Register directly your own converter.
template <typename T>
void addConverter(std::function<void(const T&, nlohmann::json&)> to_json)
{
auto converter = [=](const BT::Any& entry, nlohmann::json& dst) {
to_json(entry.cast<T>(), dst);
};
type_converters_.insert( {typeid(T), std::move(converter)} );
}
/// Register new JSON converters with addConverter<Foo>().
/// You should have used first the macro BT_JSON_CONVERTER
template <typename T> void addConverter();

private:
private:

using ToJonConverter = std::function<void(const BT::Any&, nlohmann::json&)>;
std::unordered_map<std::type_index, ToJonConverter> type_converters_;
using FromJonConverter = std::function<BT::Any(const nlohmann::json&)>;

std::unordered_map<std::type_index, ToJonConverter> to_json_converters_;
std::unordered_map<std::string, FromJonConverter> from_json_converters_;
};

/* Function to use to register a specific implementation of nlohmann::to_json
//-------------------------------------------------------------------

Example:
template<typename T> inline
void JsonExporter::addConverter()
{
ToJonConverter to_converter = [](const BT::Any& entry, nlohmann::json& dst)
{
using namespace nlohmann;
to_json(dst, *const_cast<BT::Any&>(entry).castPtr<T>());
};
to_json_converters_.insert( {typeid(T), to_converter} );

namespace nlohmann {
void to_json(nlohmann::json& j, const Position2D& p)
{
j["x"] = p.x;
j["y"] = p.y;
}
} // namespace nlohmann
FromJonConverter from_converter = [](const nlohmann::json& dst) -> BT::Any
{
T value;
using namespace nlohmann;
from_json(dst, value);
return BT::Any(value);
};

// we need to get the name of the type
nlohmann::json const js = T{};
auto const type_name = js.contains("__type") ?
std::string(js["__type"]) :
BT::demangle(typeid(T));

from_json_converters_.insert( {type_name, from_converter} );
}

// In you main function
RegisterJsonDefinition<Position2D>()
*/
template <typename T> inline void RegisterJsonDefinition()
{
JsonExporter::get().addConverter<T>();
}

/* Function to use to register a specific implementation of "to_json"
Example:
RegisterJsonDefinition([](nlohmann::json& j, const Position2D& p)
{
j["x"] = p.x;
j["y"] = p.y;
} );
*/

template <typename T> inline
void RegisterJsonDefinition(std::function<void(nlohmann::json&, const T&)> func)
{
JsonExporter::get().addConverter<T>(func);
}

nlohmann::json ExportBlackboardToJSON(BT::Blackboard& blackboard);

} // namespace BT

//------------------------------------------------
//------------------------------------------------
//------------------------------------------------

// Macro to implement to_json() and from_json()

#define BT_JSON_CONVERTER(Type, value) \
template <class AddField> void _JsonTypeDefinition(Type&, AddField&); \
\
inline void to_json(nlohmann::json& js, const Type& p, bool add_type_name = false) { \
auto op = [&js](const char* name, auto* val) { to_json(js[name], *val); }; \
_JsonTypeDefinition(const_cast<Type&>(p), op); \
js["__type"] = #Type; \
} \
\
inline void from_json(const nlohmann::json& js, Type& p) { \
auto op = [&js](const char* name, auto* v) { js.at(name).get_to(*v); }; \
_JsonTypeDefinition(p, op); \
} \
\
template <class AddField> inline \
void _JsonTypeDefinition(Type& value, AddField& add_field)\

1 change: 0 additions & 1 deletion src/bt_factory.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,6 @@
*/

#include <filesystem>
#include <fstream>
#include "behaviortree_cpp/bt_factory.h"
#include "behaviortree_cpp/utils/shared_library.h"
#include "behaviortree_cpp/contrib/json.hpp"
Expand Down
4 changes: 2 additions & 2 deletions src/json_export.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -26,8 +26,8 @@ bool JsonExporter::toJson(const Any &any, nlohmann::json &dst) const
}
else
{
auto it = type_converters_.find(type);
if(it != type_converters_.end())
auto it = to_json_converters_.find(type);
if(it != to_json_converters_.end())
{
it->second(any, dst);
}
Expand Down
66 changes: 33 additions & 33 deletions tests/gtest_json.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@

//----------- Custom types ----------

namespace TestTypes {

struct Vector3D {
double x;
double y;
Expand All @@ -21,37 +23,39 @@ struct Pose3D {
Quaternion3D rot;
};

//----------- JSON specialization ----------

void to_json(nlohmann::json& j, const Vector3D& v)
BT_JSON_CONVERTER(Vector3D, v)
{
// compact syntax
j = {{"x", v.x}, {"y", v.y}, {"z", v.z}};
add_field("x", &v.x);
add_field("y", &v.y);
add_field("z", &v.z);
}

void to_json(nlohmann::json& j, const Quaternion3D& q)
BT_JSON_CONVERTER(Quaternion3D, v)
{
// verbose syntax
j["w"] = q.w;
j["x"] = q.x;
j["y"] = q.y;
j["z"] = q.z;
add_field("w", &v.w);
add_field("x", &v.x);
add_field("y", &v.y);
add_field("z", &v.z);
}

void to_json(nlohmann::json& j, const Pose3D& p)
BT_JSON_CONVERTER(Pose3D, v)
{
j = {{"pos", p.pos}, {"rot", p.rot}};
add_field("pos", &v.pos);
add_field("rot", &v.rot);
}

} // namespace TestTypes


//----------- JSON specialization ----------

using namespace BT;

TEST(JsonTest, Exporter)
{
JsonExporter exporter;
BT::JsonExporter exporter;

Pose3D pose = { {1,2,3},
{4,5,6,7} };
TestTypes::Pose3D pose = { {1,2,3},
{4,5,6,7} };

nlohmann::json json;
exporter.toJson(BT::Any(69), json["int"]);
Expand All @@ -61,27 +65,23 @@ TEST(JsonTest, Exporter)
ASSERT_FALSE( exporter.toJson(BT::Any(pose), json["pose"]) );

// now it should work
exporter.addConverter<Pose3D>();
exporter.addConverter<TestTypes::Pose3D>();
exporter.toJson(BT::Any(pose), json["pose"]);

nlohmann::json json_expected;
json_expected["int"] = 69;
json_expected["real"] = 3.14;

json_expected["pose"]["pos"]["x"] = 1;
json_expected["pose"]["pos"]["y"] = 2;
json_expected["pose"]["pos"]["z"] = 3;
std::cout << json.dump(2) << std::endl;

json_expected["pose"]["rot"]["w"] = 4;
json_expected["pose"]["rot"]["x"] = 5;
json_expected["pose"]["rot"]["y"] = 6;
json_expected["pose"]["rot"]["z"] = 7;
ASSERT_EQ(json["int"],69);
ASSERT_EQ(json["real"], 3.14);

ASSERT_EQ(json_expected, json);
ASSERT_EQ(json["pose"]["__type"], "Pose3D");
ASSERT_EQ(json["pose"]["pos"]["x"], 1);
ASSERT_EQ(json["pose"]["pos"]["y"], 2);
ASSERT_EQ(json["pose"]["pos"]["z"], 3);

std::cout << json.dump(2) << std::endl;
ASSERT_EQ(json["pose"]["rot"]["w"], 4);
ASSERT_EQ(json["pose"]["rot"]["x"], 5);
ASSERT_EQ(json["pose"]["rot"]["y"], 6);
ASSERT_EQ(json["pose"]["rot"]["z"], 7);
}




0 comments on commit 66eed7d

Please sign in to comment.