Skip to content

extend JSON convertion to include vectors #965

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 2 commits into from
Apr 17, 2025
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
79 changes: 65 additions & 14 deletions include/behaviortree_cpp/json_export.h
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ namespace BT
{

/**
* To add new type to the JSON library, you should follow these isntructions:
* To add new type to the JSON library, you should follow these instructions:
* https://json.nlohmann.me/features/arbitrary_types/
*
* Considering for instance the type:
Expand Down Expand Up @@ -80,22 +80,31 @@ class JsonExporter
template <typename T>
Expected<T> fromJson(const nlohmann::json& source) const;

/// Register new JSON converters with addConverter<Foo>().
/// You should have used first the macro BT_JSON_CONVERTER
/**
* @brief Register new JSON converters with addConverter<Foo>().
* You should used first the macro BT_JSON_CONVERTER.
* The convertions from/to vector<T> are automatically registered.
*/
template <typename T>
void addConverter();

/**
* @brief addConverter register a to_json function that converts a json to a type T.
* The convertion to std:vector<T> is automatically registered.
*
* @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 ofthe type.
* */
*/
template <typename T>
void addConverter(std::function<void(const T&, nlohmann::json&)> to_json,
bool add_type = true);

/// Register custom from_json converter directly.
/**
* @brief addConverter register a from_json function that converts a json to a type T.
* The convertions from std::vector<T> is automatically registered.
*
* @param from_json the function with signature void(const nlohmann::json&, T&)
*/
template <typename T>
void addConverter(std::function<void(const nlohmann::json&, T&)> from_json);

Expand All @@ -105,6 +114,7 @@ class JsonExporter

std::unordered_map<std::type_index, ToJonConverter> to_json_converters_;
std::unordered_map<std::type_index, FromJonConverter> from_json_converters_;
std::unordered_map<std::type_index, FromJonConverter> from_json_array_converters_;
std::unordered_map<std::string, BT::TypeInfo> type_names_;
};

Expand All @@ -129,6 +139,15 @@ inline Expected<T> JsonExporter::fromJson(const nlohmann::json& source) const
template <typename T>
inline void JsonExporter::addConverter()
{
// we need to get the name of the type
nlohmann::json const js = T{};
// we insert both the name obtained from JSON and demangle
if(js.contains("__type"))
{
type_names_.insert({ std::string(js["__type"]), BT::TypeInfo::Create<T>() });
}
type_names_.insert({ BT::demangle(typeid(T)), BT::TypeInfo::Create<T>() });

ToJonConverter to_converter = [](const BT::Any& entry, nlohmann::json& dst) {
dst = *const_cast<BT::Any&>(entry).castPtr<T>();
};
Expand All @@ -139,16 +158,23 @@ inline void JsonExporter::addConverter()
return { BT::Any(value), BT::TypeInfo::Create<T>() };
};

// we need to get the name of the type
nlohmann::json const js = T{};
// we insert both the name obtained from JSON and demangle
if(js.contains("__type"))
{
type_names_.insert({ std::string(js["__type"]), BT::TypeInfo::Create<T>() });
}
type_names_.insert({ BT::demangle(typeid(T)), BT::TypeInfo::Create<T>() });

from_json_converters_.insert({ typeid(T), from_converter });

//---- include vectors of T
ToJonConverter to_array_converter = [](const BT::Any& entry, nlohmann::json& dst) {
dst = *const_cast<BT::Any&>(entry).castPtr<std::vector<T>>();
};
to_json_converters_.insert({ typeid(std::vector<T>), to_array_converter });

FromJonConverter from_array_converter = [](const nlohmann::json& src) -> Entry {
std::vector<T> value;
for(const auto& item : src)
{
value.push_back(item.get<T>());
}
return { BT::Any(value), BT::TypeInfo::Create<std::vector<T>>() };
};
from_json_array_converters_.insert({ typeid(T), from_array_converter });
}

template <typename T>
Expand All @@ -163,6 +189,18 @@ inline void JsonExporter::addConverter(
}
};
to_json_converters_.insert({ typeid(T), std::move(converter) });
//---------------------------------------------
// add the vector<T> converter
auto vector_converter = [converter](const BT::Any& entry, nlohmann::json& json) {
auto& vec = *const_cast<BT::Any&>(entry).castPtr<std::vector<T>>();
for(const auto& item : vec)
{
nlohmann::json item_json;
converter(BT::Any(item), item_json);
json.push_back(item_json);
}
};
to_json_converters_.insert({ typeid(std::vector<T>), std::move(vector_converter) });
}

template <typename T>
Expand All @@ -176,6 +214,19 @@ JsonExporter::addConverter(std::function<void(const nlohmann::json&, T&)> func)
};
type_names_.insert({ BT::demangle(typeid(T)), BT::TypeInfo::Create<T>() });
from_json_converters_.insert({ typeid(T), std::move(converter) });
//---------------------------------------------
// add the vector<T> converter
auto vector_converter = [func](const nlohmann::json& json) -> Entry {
std::vector<T> tmp;
for(const auto& item : json)
{
T item_tmp;
func(item, item_tmp);
tmp.push_back(item_tmp);
}
return { BT::Any(tmp), BT::TypeInfo::Create<std::vector<T>>() };
};
from_json_array_converters_.insert({ typeid(T), std::move(vector_converter) });
}

template <typename T>
Expand Down
27 changes: 1 addition & 26 deletions include/behaviortree_cpp/utils/safe_any.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -162,32 +162,7 @@ class Any
[[nodiscard]] T* castPtr()
{
static_assert(!std::is_same_v<T, float>, "The value has been casted internally to "
"[double]. "
"Use that instead");
static_assert(!SafeAny::details::is_integer<T>() || std::is_same_v<T, uint64_t>, "The"
" va"
"lue"
" ha"
"s "
"bee"
"n "
"cas"
"ted"
" in"
"ter"
"nal"
"ly "
"to "
"[in"
"t64"
"_t]"
". "
"Use"
" th"
"at "
"ins"
"tea"
"d");
"[double]. Use that instead");

return _any.empty() ? nullptr : linb::any_cast<T>(&_any);
}
Expand Down
1 change: 0 additions & 1 deletion src/bt_factory.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,6 @@
#include <filesystem>
#include "behaviortree_cpp/bt_factory.h"
#include "behaviortree_cpp/utils/shared_library.h"
#include "behaviortree_cpp/contrib/json.hpp"
#include "behaviortree_cpp/xml_parsing.h"
#include "wildcards/wildcards.hpp"

Expand Down
64 changes: 54 additions & 10 deletions src/json_export.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,22 @@ bool JsonExporter::toJson(const Any& any, nlohmann::json& dst) const
{
dst = any.cast<double>();
}
else if(type == typeid(std::vector<double>))
{
dst = any.cast<std::vector<double>>();
}
else if(type == typeid(std::vector<int>))
{
dst = any.cast<std::vector<int>>();
}
else if(type == typeid(std::vector<std::string>))
{
dst = any.cast<std::vector<std::string>>();
}
else if(type == typeid(std::vector<bool>))
{
dst = any.cast<std::vector<bool>>();
}
else
{
auto it = to_json_converters_.find(type);
Expand All @@ -49,20 +65,17 @@ JsonExporter::ExpectedEntry JsonExporter::fromJson(const nlohmann::json& source)
{
if(source.is_null())
{
return nonstd::make_unexpected("json object is null");
return Entry{ BT::Any(), BT::TypeInfo::Create<std::nullptr_t>() };
}

if(source.is_string())
{
return Entry{ BT::Any(source.get<std::string>()),
BT::TypeInfo::Create<std::string>() };
}
if(source.is_number_unsigned())
{
return Entry{ BT::Any(source.get<uint64_t>()), BT::TypeInfo::Create<uint64_t>() };
}
if(source.is_number_integer())
{
return Entry{ BT::Any(source.get<int64_t>()), BT::TypeInfo::Create<int64_t>() };
return Entry{ BT::Any(source.get<int>()), BT::TypeInfo::Create<int>() };
}
if(source.is_number_float())
{
Expand All @@ -73,17 +86,48 @@ JsonExporter::ExpectedEntry JsonExporter::fromJson(const nlohmann::json& source)
return Entry{ BT::Any(source.get<bool>()), BT::TypeInfo::Create<bool>() };
}

if(!source.contains("__type"))
// basic vectors
if(source.is_array() && source.size() > 0 && !source.contains("__type"))
{
auto first_element = source[0];
if(first_element.is_string())
{
return Entry{ BT::Any(source.get<std::vector<std::string>>()),
BT::TypeInfo::Create<std::vector<std::string>>() };
}
if(first_element.is_number_integer())
{
return Entry{ BT::Any(source.get<std::vector<int>>()),
BT::TypeInfo::Create<std::vector<int>>() };
}
if(first_element.is_number_float())
{
return Entry{ BT::Any(source.get<std::vector<double>>()),
BT::TypeInfo::Create<std::vector<double>>() };
}
if(first_element.is_boolean())
{
return Entry{ BT::Any(source.get<std::vector<bool>>()),
BT::TypeInfo::Create<std::vector<bool>>() };
}
}

if(!source.contains("__type") && !source.is_array())
{
return nonstd::make_unexpected("Missing field '__type'");
}
auto type_it = type_names_.find(source["__type"]);

auto& from_converters =
source.is_array() ? from_json_array_converters_ : from_json_converters_;
auto type_field = source.is_array() ? source[0]["__type"] : source["__type"];

auto type_it = type_names_.find(type_field);
if(type_it == type_names_.end())
{
return nonstd::make_unexpected("Type not found in registered list");
}
auto func_it = from_json_converters_.find(type_it->second.type());
if(func_it == from_json_converters_.end())
auto func_it = from_converters.find(type_it->second.type());
if(func_it == from_converters.end())
{
return nonstd::make_unexpected("Type not found in registered list");
}
Expand Down
Loading
Loading