diff --git a/examples/CMakeLists.txt b/examples/CMakeLists.txt
index 362afcc23..1822bcf33 100644
--- a/examples/CMakeLists.txt
+++ b/examples/CMakeLists.txt
@@ -38,6 +38,7 @@ CompileExample("ex02_runtime_ports")
 CompileExample("ex04_waypoints")
 CompileExample("ex05_subtree_model")
 CompileExample("ex06_access_by_ptr")
+CompileExample("ex07_blackboard_backup")
 
 CompileExample("t13_plugin_executor")
 
diff --git a/examples/ex07_blackboard_backup.cpp b/examples/ex07_blackboard_backup.cpp
new file mode 100644
index 000000000..cec31a1f4
--- /dev/null
+++ b/examples/ex07_blackboard_backup.cpp
@@ -0,0 +1,67 @@
+#include "behaviortree_cpp/bt_factory.h"
+#include "dummy_nodes.h"
+
+using namespace BT;
+
+// clang-format off
+static const char* xml_tree = R"(
+<root BTCPP_format="4">
+  <BehaviorTree ID="MainTree">
+    <Sequence>
+      <Script code="val_A:= 'john' "/>
+      <Script code="val_B:= 42 "/>
+      <SaySomething message="{val_A}" />
+      <SaySomething message="hello world" />
+      <SubTree ID="Sub" val="{val_A}" _autoremap="true" />
+      <SaySomething message="{reply}" />
+    </Sequence>
+  </BehaviorTree>
+  <BehaviorTree ID="Sub">
+    <Sequence>
+      <SaySomething message="{val}" />
+      <SaySomething message="{val_B}" />
+      <Script code="reply:= 'done' "/>
+    </Sequence>
+  </BehaviorTree>
+</root>
+ )";
+
+// clang-format on
+
+
+int main()
+{
+  BehaviorTreeFactory factory;
+  factory.registerNodeType<DummyNodes::SaySomething>("SaySomething");
+  factory.registerBehaviorTreeFromText(xml_tree);
+
+  auto tree = factory.createTree("MainTree");
+
+  // We want to create a memory of the blackboard in memory.
+  // This is conveninet when we want to reset its state to the
+  // original one.
+  // It is certainly more efficient than destroying and creating the tree again,
+  // in many casess.
+
+  const auto backup_before_tick = BlackboardBackup(tree);
+  tree.tickWhileRunning();
+
+  // Restore the original status of the blackboard
+  BlackboardRestore(backup_before_tick, tree);
+  tree.tickWhileRunning();
+
+  // Alternatively, we may want to save he values of the element in the blackboard
+  // to file, to restore them again. We use JSON serialization for that.
+  nlohmann::json json_after_tick = ExportTreeToJSON(tree);
+
+  // The JSOn object can be saved to file. See https://github.com/nlohmann/json
+  // for details. For the time being, we just print it in the console
+
+  std::cout << "--- blackboard serialized as JSON: ----\n"
+            << json_after_tick.dump(2) << std::endl;
+
+  // We can restore the values of the blackboards using the JSON
+  ImportTreeFromJSON(json_after_tick, tree);
+
+  return 0;
+}
diff --git a/examples/t12_groot_howto.cpp b/examples/t12_groot_howto.cpp
index e5fb78460..ec3fbeedc 100644
--- a/examples/t12_groot_howto.cpp
+++ b/examples/t12_groot_howto.cpp
@@ -10,18 +10,21 @@
  *  But this time we also show how to connect
  */
 
-// A custom structuree that I want to visualize in Groot2
-struct Position2D {
+// A custom struct  that I want to visualize in Groot2
+struct Position2D
+{
   double x;
   double y;
 };
 
-// Allows Position2D to be visualized in Groot2
-// You still need BT::RegisterJsonDefinition<Position2D>(PositionToJson)
-void PositionToJson(nlohmann::json& j, const Position2D& p)
+// This macro will generate the code that is needed to convert
+// the object to/from JSON.
+// You still need to call BT::RegisterJsonDefinition<Position2D>()
+// in main()
+BT_JSON_CONVERTER(Position2D, pos)
 {
-  j["x"] = p.x;
-  j["y"] = p.y;
+  add_field("x", &pos.x);
+  add_field("y", &pos.y);
 }
 
 // Simple Action that updates an instance of Position2D in the blackboard
@@ -97,7 +100,7 @@ int main()
   factory.registerBehaviorTreeFromText(xml_text);
 
   // Add this to allow Groot2 to visualize your custom type
-  BT::RegisterJsonDefinition<Position2D>(PositionToJson);
+  BT::RegisterJsonDefinition<Position2D>();
 
   auto tree = factory.createTree("MainTree");
 
diff --git a/examples/t13_custom_type.hpp b/examples/t13_custom_type.hpp
index 99f786ed0..9157234d1 100644
--- a/examples/t13_custom_type.hpp
+++ b/examples/t13_custom_type.hpp
@@ -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;
 }
diff --git a/include/behaviortree_cpp/basic_types.h b/include/behaviortree_cpp/basic_types.h
index 4243c39c2..c8e099e19 100644
--- a/include/behaviortree_cpp/basic_types.h
+++ b/include/behaviortree_cpp/basic_types.h
@@ -58,13 +58,50 @@ enum class PortDirection
 
 using StringView = std::string_view;
 
+bool StartWith(StringView str, StringView prefix);
+
 // vector of key/value pairs
 using KeyValueVector = std::vector<std::pair<std::string, std::string>>;
 
+/** Usage: given a function/method like this:
+ *
+ *     Expected<double> getAnswer();
+ *
+ * User code can check result and error message like this:
+ *
+ *     auto res = getAnswer();
+ *     if( res )
+ *     {
+ *         std::cout << "answer was: " << res.value() << std::endl;
+ *     }
+ *     else{
+ *         std::cerr << "failed to get the answer: " << res.error() << std::endl;
+ *     }
+ *
+ * */
+template <typename T>
+using Expected = nonstd::expected<T, std::string>;
 
 struct AnyTypeAllowed
 {};
 
+/**
+ * @brief convertFromJSON will parse a json string and use JsonExporter
+ * to convert its content to a given type. It will work only if
+ * the type was previously registered. May throw if it fails.
+ *
+ * @param json_text a valid JSON string
+ * @param type you must specify the typeid()
+ * @return the object, wrapped in Any.
+ */
+[[nodiscard]] Any convertFromJSON(StringView json_text, std::type_index type);
+
+/// Same as the non template version, but with automatic casting
+template <typename T> [[nodiscard]]
+inline T convertFromJSON(StringView str)
+{
+  return convertFromJSON(str, typeid(T)).cast<T>();
+}
 
 /**
  * convertFromString is used to convert a string into a custom type.
@@ -72,11 +109,22 @@ struct AnyTypeAllowed
  * This function is invoked under the hood by TreeNode::getInput(), but only when the
  * input port contains a string.
  *
- * If you have a custom type, you need to implement the corresponding template specialization.
+ * If you have a custom type, you need to implement the corresponding
+ * template specialization.
+ *
+ * If the string starts with the prefix "json:", it will
+ * fall back to convertFromJSON()
  */
 template <typename T> [[nodiscard]]
-inline T convertFromString(StringView /*str*/)
+inline T convertFromString(StringView str)
 {
+  // if string starts with "json:{", try to parse it as json
+  if(StartWith(str, "json:"))
+  {
+    str.remove_prefix(5);
+    return convertFromJSON<T>(str);
+  }
+
   auto type_name = BT::demangle(typeid(T));
 
   std::cerr << "You (maybe indirectly) called BT::convertFromString() for type ["
@@ -172,6 +220,15 @@ constexpr bool IsConvertibleToString()
          std::is_convertible_v<T, std::string_view>;
 }
 
+Expected<std::string> toJsonString(const Any &value);
+
+
+/**
+ * @brief toStr is the reverse operation of convertFromString.
+ *
+ * If T is a custom type and there is no template specialization,
+ * it will try to fall back to toJsonString()
+ */
 template<typename T> [[nodiscard]]
 std::string toStr(const T& value)
 {
@@ -181,6 +238,11 @@ std::string toStr(const T& value)
   }
   else if constexpr(!std::is_arithmetic_v<T>)
   {
+    if(auto str = toJsonString(Any(value)))
+    {
+      return *str;
+    }
+
     throw LogicError(
         StrCat("Function BT::toStr<T>() not specialized for type [",
                BT::demangle(typeid(T)), "]")
@@ -228,25 +290,6 @@ using enable_if = typename std::enable_if<Predicate::value>::type*;
 template <typename Predicate>
 using enable_if_not = typename std::enable_if<!Predicate::value>::type*;
 
-/** Usage: given a function/method like this:
- *
- *     Expected<double> getAnswer();
- *
- * User code can check result and error message like this:
- *
- *     auto res = getAnswer();
- *     if( res )
- *     {
- *         std::cout << "answer was: " << res.value() << std::endl;
- *     }
- *     else{
- *         std::cerr << "failed to get the answer: " << res.error() << std::endl;
- *     }
- *
- * */
-template <typename T>
-using Expected = nonstd::expected<T, std::string>;
-
 #ifdef USE_BTCPP3_OLD_NAMES
 // note: we also use the name Optional instead of expected because it is more intuitive
 // for users that are not up to date with "modern" C++
diff --git a/include/behaviortree_cpp/blackboard.h b/include/behaviortree_cpp/blackboard.h
index ca94a87f8..f58e14bb2 100644
--- a/include/behaviortree_cpp/blackboard.h
+++ b/include/behaviortree_cpp/blackboard.h
@@ -1,14 +1,12 @@
 #pragma once
 
-#include <iostream>
 #include <string>
 #include <memory>
-#include <stdint.h>
 #include <unordered_map>
 #include <mutex>
-#include <sstream>
 
 #include "behaviortree_cpp/basic_types.h"
+#include "behaviortree_cpp/contrib/json.hpp"
 #include "behaviortree_cpp/utils/safe_any.hpp"
 #include "behaviortree_cpp/exceptions.h"
 #include "behaviortree_cpp/utils/locked_reference.hpp"
@@ -78,150 +76,19 @@ class Blackboard
    *  Note that this method may throw an exception if the cast to T failed.
    */
   template <typename T> [[nodiscard]]
-  bool get(const std::string& key, T& value) const
-  {
-    if (auto any_ref = getAnyLocked(key))
-    {
-      value = any_ref.get()->cast<T>();
-      return true;
-    }
-    return false;
-  }
+  bool get(const std::string& key, T& value) const;
 
   /**
    * Version of get() that throws if it fails.
    */
   template <typename T> [[nodiscard]]
-  T get(const std::string& key) const
-  {
-    if (auto any_ref = getAnyLocked(key))
-    {
-      const auto& any = any_ref.get();
-      if(any->empty())
-      {
-        throw RuntimeError("Blackboard::get() error. Entry [", key, "] hasn't been initialized, yet");
-      }
-      return any_ref.get()->cast<T>();
-    }
-    else
-    {
-      throw RuntimeError("Blackboard::get() error. Missing key [", key, "]");
-    }
-  }
+  T get(const std::string& key) const;
 
   /// Update the entry with the given key
   template <typename T>
-  void set(const std::string& key, const T& value)
-  {
-    std::unique_lock lock(mutex_);
+  void set(const std::string& key, const T& value);
 
-    // check local storage
-    auto it = storage_.find(key);
-    if (it == storage_.end())
-    {
-      // create a new entry
-      Any new_value(value);
-      lock.unlock();
-      std::shared_ptr<Blackboard::Entry> entry;
-      // if a new generic port is created with a string, it's type should be AnyTypeAllowed
-      if constexpr (std::is_same_v<std::string, T>)
-      {
-        entry = createEntryImpl(key, PortInfo(PortDirection::INOUT));
-      }
-      else {
-        PortInfo new_port(PortDirection::INOUT, new_value.type(),
-                          GetAnyFromStringFunctor<T>());
-        entry = createEntryImpl(key, new_port);
-      }
-      lock.lock();
-
-      storage_.insert( {key, entry} );
-      entry->value = new_value;
-    }
-    else
-    {
-      // this is not the first time we set this entry, we need to check
-      // if the type is the same or not.
-      Entry& entry = *it->second;
-      std::scoped_lock scoped_lock(entry.entry_mutex);
-
-      Any& previous_any = entry.value;
-
-      Any new_value(value);
-
-      // special case: entry exists but it is not strongly typed... yet
-      if (!entry.info.isStronglyTyped())
-      {
-        // Use the new type to create a new entry that is strongly typed.
-        entry.info = TypeInfo::Create<T>();
-        previous_any = std::move(new_value);
-        return;
-      }
-
-      std::type_index previous_type = entry.info.type();
-
-      // check type mismatch
-      if (previous_type != std::type_index(typeid(T)) &&
-          previous_type != new_value.type())
-      {
-        bool mismatching = true;
-        if (std::is_constructible<StringView, T>::value)
-        {
-          Any any_from_string = entry.info.parseString(value);
-          if (any_from_string.empty() == false)
-          {
-            mismatching = false;
-            new_value = std::move(any_from_string);
-          }
-        }
-        // check if we are doing a safe cast between numbers
-        // for instance, it is safe to use int(100) to set
-        // a uint8_t port, but not int(-42) or int(300)
-        if constexpr(std::is_arithmetic_v<T>)
-        {
-          if(mismatching && isCastingSafe(previous_type, value))
-          {
-            mismatching = false;
-          }
-        }
-
-        if (mismatching)
-        {
-          debugMessage();
-
-          auto msg = StrCat("Blackboard::set(", key, "): once declared, "
-                            "the type of a port shall not change. "
-                            "Previously declared type [", BT::demangle(previous_type),
-                            "], current type [", BT::demangle(typeid(T)), "]");
-          throw LogicError(msg);
-        }
-      }
-      // if doing set<BT::Any>, skip type check
-      if constexpr(std::is_same_v<Any, T>)
-      {
-        previous_any = new_value;
-      }
-      else {
-        // copy only if the type is compatible
-        new_value.copyInto(previous_any);
-      }
-    }
-  }
-
-  void unset(const std::string& key)
-  {
-    std::unique_lock lock(mutex_);
-
-    // check local storage
-    auto it = storage_.find(key);
-    if (it == storage_.end())
-    {
-      // No entry, nothing to do.
-      return;
-    }
-
-    storage_.erase(it);
-  }
+  void unset(const std::string& key);
 
   [[nodiscard]] const TypeInfo* entryInfo(const std::string& key);
 
@@ -250,5 +117,165 @@ class Blackboard
   bool autoremapping_ = false;
 };
 
+/**
+ * @brief ExportBlackboardToJSON will create a JSON
+ * that contains the current values of the blackboard.
+ * Complex types must be registered with JsonExporter::get()
+ */
+nlohmann::json ExportBlackboardToJSON(const Blackboard &blackboard);
+
+/**
+ * @brief ImportBlackboardFromJSON will append elements to the blackboard,
+ * using the values parsed from the JSON file created using ExportBlackboardToJSON.
+ * Complex types must be registered with JsonExporter::get()
+ */
+void ImportBlackboardFromJSON(const nlohmann::json& json, Blackboard& blackboard);
+
+
+//------------------------------------------------------
+
+ template<typename T> inline
+T Blackboard::get(const std::string &key) const
+{
+  if (auto any_ref = getAnyLocked(key))
+  {
+    const auto& any = any_ref.get();
+    if(any->empty())
+    {
+      throw RuntimeError("Blackboard::get() error. Entry [", key, "] hasn't been initialized, yet");
+    }
+    return any_ref.get()->cast<T>();
+  }
+  else
+  {
+    throw RuntimeError("Blackboard::get() error. Missing key [", key, "]");
+  }
+}
+
+
+inline void Blackboard::unset(const std::string &key)
+{
+  std::unique_lock lock(mutex_);
+
+  // check local storage
+  auto it = storage_.find(key);
+  if (it == storage_.end())
+  {
+    // No entry, nothing to do.
+    return;
+  }
+
+  storage_.erase(it);
+}
+
+template<typename T> inline
+    void Blackboard::set(const std::string &key, const T &value)
+{
+  std::unique_lock lock(mutex_);
+
+  // check local storage
+  auto it = storage_.find(key);
+  if (it == storage_.end())
+  {
+    // create a new entry
+    Any new_value(value);
+    lock.unlock();
+    std::shared_ptr<Blackboard::Entry> entry;
+    // if a new generic port is created with a string, it's type should be AnyTypeAllowed
+    if constexpr (std::is_same_v<std::string, T>)
+    {
+      entry = createEntryImpl(key, PortInfo(PortDirection::INOUT));
+    }
+    else {
+      PortInfo new_port(PortDirection::INOUT, new_value.type(),
+                        GetAnyFromStringFunctor<T>());
+      entry = createEntryImpl(key, new_port);
+    }
+    lock.lock();
+
+    storage_.insert( {key, entry} );
+    entry->value = new_value;
+  }
+  else
+  {
+    // this is not the first time we set this entry, we need to check
+    // if the type is the same or not.
+    Entry& entry = *it->second;
+    std::scoped_lock scoped_lock(entry.entry_mutex);
+
+    Any& previous_any = entry.value;
+
+    Any new_value(value);
+
+    // special case: entry exists but it is not strongly typed... yet
+    if (!entry.info.isStronglyTyped())
+    {
+      // Use the new type to create a new entry that is strongly typed.
+      entry.info = TypeInfo::Create<T>();
+      previous_any = std::move(new_value);
+      return;
+    }
+
+    std::type_index previous_type = entry.info.type();
+
+    // check type mismatch
+    if (previous_type != std::type_index(typeid(T)) &&
+        previous_type != new_value.type())
+    {
+      bool mismatching = true;
+      if (std::is_constructible<StringView, T>::value)
+      {
+        Any any_from_string = entry.info.parseString(value);
+        if (any_from_string.empty() == false)
+        {
+          mismatching = false;
+          new_value = std::move(any_from_string);
+        }
+      }
+      // check if we are doing a safe cast between numbers
+      // for instance, it is safe to use int(100) to set
+      // a uint8_t port, but not int(-42) or int(300)
+      if constexpr(std::is_arithmetic_v<T>)
+      {
+        if(mismatching && isCastingSafe(previous_type, value))
+        {
+          mismatching = false;
+        }
+      }
+
+      if (mismatching)
+      {
+        debugMessage();
+
+	auto msg = StrCat("Blackboard::set(", key, "): once declared, "
+						   "the type of a port shall not change. "
+						   "Previously declared type [", BT::demangle(previous_type),
+			  "], current type [", BT::demangle(typeid(T)), "]");
+	throw LogicError(msg);
+      }
+    }
+    // if doing set<BT::Any>, skip type check
+    if constexpr(std::is_same_v<Any, T>)
+    {
+      previous_any = new_value;
+    }
+    else {
+      // copy only if the type is compatible
+      new_value.copyInto(previous_any);
+    }
+  }
+}
+
+template<typename T> inline
+    bool Blackboard::get(const std::string &key, T &value) const
+{
+  if (auto any_ref = getAnyLocked(key))
+  {
+    value = any_ref.get()->cast<T>();
+    return true;
+  }
+  return false;
+}
+
 }   // namespace BT
 
diff --git a/include/behaviortree_cpp/bt_factory.h b/include/behaviortree_cpp/bt_factory.h
index fc771181f..82c8902a6 100644
--- a/include/behaviortree_cpp/bt_factory.h
+++ b/include/behaviortree_cpp/bt_factory.h
@@ -507,6 +507,44 @@ class BehaviorTreeFactory
 
 };
 
+/**
+ * @brief BlackboardClone make a copy of the content of the
+ * blackboard
+ * @param src   source
+ * @param dst   destination
+ */
+void BlackboardClone(const Blackboard& src, Blackboard& dst);
+
+/**
+ * @brief BlackboardBackup uses BlackboardClone to backup
+ * all the blackboards of the tree
+ *
+ * @param tree source
+ * @return destination (the backup)
+ */
+std::vector<Blackboard::Ptr> BlackboardBackup(const BT::Tree& tree);
+
+/**
+ * @brief BlackboardRestore uses BlackboardClone to restore
+ * all the blackboards of the tree
+ *
+ * @param backup a vectror of blackboards
+ * @param tree the destination
+ */
+void BlackboardRestore(const std::vector<Blackboard::Ptr>& backup, BT::Tree& tree);
+
+/**
+ * @brief ExportTreeToJSON it calls ExportBlackboardToJSON
+ * for all the blackboards in the tree
+ */
+nlohmann::json ExportTreeToJSON(const BT::Tree &tree);
+
+/**
+ * @brief ImportTreeFromJSON it calls ImportBlackboardFromJSON
+ * for all the blackboards in the tree
+ */
+void ImportTreeFromJSON(const nlohmann::json &json, BT::Tree &tree);
+
 }   // namespace BT
 
 #endif   // BT_FACTORY_H
diff --git a/include/behaviortree_cpp/json_export.h b/include/behaviortree_cpp/json_export.h
index 4b668ad70..db4e20c47 100644
--- a/include/behaviortree_cpp/json_export.h
+++ b/include/behaviortree_cpp/json_export.h
@@ -1,28 +1,51 @@
 #pragma once
 
+#include "behaviortree_cpp/basic_types.h"
 #include "behaviortree_cpp/utils/safe_any.hpp"
-#include "behaviortree_cpp/blackboard.h"
+#include "behaviortree_cpp/contrib/expected.hpp"
 
 // 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:
+*  To add new type to the JSON library, you should 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);
+*
+*  To avoid repeating yourself, we provide the macro BT_JSON_CONVERTION
+*  that implements both those function, at once. Usage:
 *
-*   void to_json(json& j, const Foo& f);
+*  BT_JSON_CONVERTER(Point2D, point)
+*  {
+*     add_field("x", &point.x);
+*     add_field("y", &point.y);
+*  }
 *
-*  Later, you MUST register this calling:
+*  Later, you MUST register the type using:
 *
-*   RegisterJsonDefinition<Foo>();
+*  BT::RegisterJsonDefinition<Point2D>();
+*/
+
+//-----------------------------------------------------------------------------------
+
+/**
+*  Use RegisterJsonDefinition<Foo>();
 */
 
-class JsonExporter{
+class JsonExporter {
 
   public:
   static JsonExporter& get() {
@@ -34,90 +57,101 @@ class JsonExporter{
    * @brief toJson adds the content of "any" to the JSON "destination".
    *
    * It will return false if the conversion toJson is not possible
-   * ( you might need to register the converter with addConverter() ).
+   * If it is a custom type, you might register it first with addConverter().
    */
   bool toJson(const BT::Any& any, nlohmann::json& destination) const;
 
-  template <typename T>
-  void toJson(const T& val, nlohmann::json& dst) const {
-    dst = val;
-  }
+  /// This information is needed to create a BT::Blackboard::entry
+  using Entry = std::pair<BT::Any, BT::TypeInfo>;
 
-  /// 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)} );
-  }
+  using ExpectedEntry = nonstd::expected_lite::expected<Entry, std::string>;
 
-  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)} );
-  }
+  /**
+   * @brief fromJson will return an Entry (value wrappedn in Any + TypeInfo)
+   * from a json source.
+   * If it is a custom type, you might register it first with addConverter().
+   * @param source
+   * @return
+   */
+  ExpectedEntry fromJson(const nlohmann::json& source) const;
 
-  /// 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)} );
-  }
+  /// Same as the other, but providing the specific type
+  ExpectedEntry fromJson(const nlohmann::json& source, std::type_index type) const;
 
-  private:
+  /// Register new JSON converters with addConverter<Foo>().
+  /// You should have used first the macro BT_JSON_CONVERTER
+  template <typename T> void addConverter();
+
+private:
 
   using ToJonConverter = std::function<void(const BT::Any&, nlohmann::json&)>;
-  std::unordered_map<std::type_index, ToJonConverter> type_converters_;
-};
+  using FromJonConverter = std::function<Entry(const nlohmann::json&)>;
 
-/* Function to use to register a specific implementation of nlohmann::to_json
+  std::unordered_map<std::type_index, ToJonConverter> to_json_converters_;
+  std::unordered_map<std::type_index, FromJonConverter> from_json_converters_;
+  std::unordered_map<std::string, BT::TypeInfo> type_names_;
+};
 
-  Example:
 
-  namespace nlohmann {
-    void to_json(nlohmann::json& j, const Position2D& p)
-    {
-      j["x"] = p.x;
-      j["y"] = p.y;
-    }
-  } // namespace nlohmann
+//-------------------------------------------------------------------
 
-  // In you main function
-  RegisterJsonDefinition<Position2D>()
-*/
-template <typename T> inline void RegisterJsonDefinition()
+template<typename T> inline
+void JsonExporter::addConverter()
 {
-  JsonExporter::get().addConverter<T>();
-}
-
-/* Function to use to register a specific implementation of "to_json"
+  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} );
 
-  Example:
+  FromJonConverter from_converter = [](const nlohmann::json& dst) -> Entry
+  {
+    T value;
+    using namespace nlohmann;
+    from_json(dst, value);
+    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>()} );
 
-  RegisterJsonDefinition([](nlohmann::json& j, const Position2D& p)
-    {
-      j["x"] = p.x;
-      j["y"] = p.y;
-    } );
-*/
+  from_json_converters_.insert( {typeid(T), from_converter} );
+}
 
-template <typename T> inline
-void RegisterJsonDefinition(std::function<void(nlohmann::json&, const T&)> func)
+template <typename T> inline void RegisterJsonDefinition()
 {
-  JsonExporter::get().addConverter<T>(func);
+  JsonExporter::get().addConverter<T>();
 }
 
-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)\
 
diff --git a/include/behaviortree_cpp/tree_node.h b/include/behaviortree_cpp/tree_node.h
index dc86f76b0..a8eed5bcf 100644
--- a/include/behaviortree_cpp/tree_node.h
+++ b/include/behaviortree_cpp/tree_node.h
@@ -440,14 +440,21 @@ inline Result TreeNode::getInput(const std::string& key, T& destination) const
     // pure string, not a blackboard key
     if (!remapped_res)
     {
-      destination = ParseString(port_value_str);
+      try {
+        destination = ParseString(port_value_str);
+      }
+      catch(std::exception& ex)
+      {
+        return nonstd::make_unexpected(StrCat("getInput(): ", ex.what()));
+      }
       return {};
     }
     const auto& remapped_key = remapped_res.value();
 
     if (!config().blackboard)
     {
-      return nonstd::make_unexpected("getInput(): trying to access an invalid Blackboard");
+      return nonstd::make_unexpected("getInput(): trying to access "
+                                     "an invalid Blackboard");
     }
 
     if (auto any_ref = config().blackboard->getAnyLocked(std::string(remapped_key)))
diff --git a/sample_nodes/movebase_node.h b/sample_nodes/movebase_node.h
index 486d34139..8700d5536 100644
--- a/sample_nodes/movebase_node.h
+++ b/sample_nodes/movebase_node.h
@@ -1,21 +1,23 @@
 #pragma once
 
+#include "behaviortree_cpp/action_node.h"
 #include "behaviortree_cpp/json_export.h"
-#include "behaviortree_cpp/behavior_tree.h"
 
 // Custom type
 struct Pose2D
 {
-    double x, y, theta;
+  double x, y, theta;
 };
 
-// Use this to register this function into JsonExporter:
+// Add this to you main() to register this function into JsonExporter:
 //
 // BT::JsonExporter::get().addConverter<Pose2D>();
-inline void to_json(nlohmann::json& dest, const Pose2D& pose) {
-    dest["x"] = pose.x;
-    dest["y"] = pose.y;
-    dest["theta"] = pose.theta;
+
+BT_JSON_CONVERTER(Pose2D, pose)
+{
+  add_field("x", &pose.x);
+  add_field("y", &pose.y);
+  add_field("theta", &pose.theta);
 }
 
 
diff --git a/src/basic_types.cpp b/src/basic_types.cpp
index 143f0ff5d..ccdffcadb 100644
--- a/src/basic_types.cpp
+++ b/src/basic_types.cpp
@@ -1,4 +1,6 @@
 #include "behaviortree_cpp/basic_types.h"
+#include "behaviortree_cpp/json_export.h"
+
 #include <cstdlib>
 #include <cstring>
 #include <clocale>
@@ -400,4 +402,31 @@ bool IsAllowedPortName(StringView str)
   return true;
 }
 
+Any convertFromJSON(StringView json_text, std::type_index type)
+{
+  nlohmann::json json = nlohmann::json::parse(json_text);
+  auto res = JsonExporter::get().fromJson(json, type);
+  if(!res)
+  {
+    throw std::runtime_error(res.error());
+  }
+  return res->first;
+}
+
+Expected<std::string> toJsonString(const Any& value)
+{
+  nlohmann::json json;
+  if(JsonExporter::get().toJson(value, json))
+  {
+    return StrCat("json:", json.dump());
+  }
+  return nonstd::make_unexpected("toJsonString failed");
+}
+
+bool StartWith(StringView str, StringView prefix)
+{
+  return str.size() >= prefix.size() &&
+         strncmp(str.data(), prefix.data(), prefix.size()) == 0;
+}
+
 }   // namespace BT
diff --git a/src/blackboard.cpp b/src/blackboard.cpp
index 52f2ebd9e..636d4ddd2 100644
--- a/src/blackboard.cpp
+++ b/src/blackboard.cpp
@@ -1,4 +1,5 @@
 #include "behaviortree_cpp/blackboard.h"
+#include "behaviortree_cpp/json_export.h"
 
 namespace BT
 {
@@ -224,4 +225,37 @@ Blackboard::createEntryImpl(const std::string& key, const TypeInfo& info)
   return entry;
 }
 
+nlohmann::json ExportBlackboardToJSON(const Blackboard &blackboard)
+{
+  nlohmann::json dest;
+  for(auto entry_name: blackboard.getKeys())
+  {
+    std::string name(entry_name);
+    if(auto any_ref = blackboard.getAnyLocked(name))
+    {
+      if(auto any_ptr = any_ref.get())
+      {
+        JsonExporter::get().toJson(*any_ptr, dest[name]);
+      }
+    }
+  }
+  return dest;
+}
+
+void ImportBlackboardFromJSON(const nlohmann::json &json, Blackboard &blackboard)
+{
+  for (auto it = json.begin(); it != json.end(); ++it)
+  {
+    if(auto res = JsonExporter::get().fromJson(it.value()))
+    {
+      auto entry = blackboard.getEntry(it.key());
+      if(!entry) {
+        blackboard.createEntry(it.key(), res->second);
+        entry = blackboard.getEntry(it.key());
+      }
+      entry->value = res->first;
+    }
+  }
+}
+
 }   // namespace BT
diff --git a/src/bt_factory.cpp b/src/bt_factory.cpp
index dc75a0b82..39dea9b5c 100644
--- a/src/bt_factory.cpp
+++ b/src/bt_factory.cpp
@@ -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"
@@ -660,4 +659,63 @@ NodeStatus Tree::tickRoot(TickOption opt, std::chrono::milliseconds sleep_time)
   return status;
 }
 
+void BlackboardClone(const Blackboard &src, Blackboard &dst)
+{
+  dst.clear();
+  for(auto const key_name: src.getKeys())
+  {
+    const auto key = std::string(key_name);
+    const auto entry = src.getEntry(key);
+    dst.createEntry(key, entry->info);
+    auto new_entry =  dst.getEntry(key);
+    new_entry->value = entry->value;
+    new_entry->string_converter = entry->string_converter;
+  }
+}
+
+void BlackboardRestore(const std::vector<Blackboard::Ptr> &backup, Tree &tree)
+{
+  assert(backup.size() == tree.subtrees.size());
+  for(size_t i=0; i<tree.subtrees.size(); i++)
+  {
+    BlackboardClone(*(backup[i]), *(tree.subtrees[i]->blackboard));
+  }
+}
+
+std::vector<Blackboard::Ptr> BlackboardBackup(const Tree &tree)
+{
+  std::vector<Blackboard::Ptr> bb;
+  bb.reserve(tree.subtrees.size());
+  for(const auto& sub: tree.subtrees)
+  {
+    bb.push_back( BT::Blackboard::create() );
+    BlackboardClone(*sub->blackboard, *bb.back());
+  }
+  return bb;
+}
+
+nlohmann::json ExportTreeToJSON(const Tree &tree)
+{
+  std::vector<nlohmann::json> bbs;
+  for(const auto& subtree: tree.subtrees)
+  {
+    bbs.push_back(ExportBlackboardToJSON(*subtree->blackboard));
+  }
+  return bbs;
+}
+
+void ImportTreeFromJSON(const nlohmann::json &json, Tree &tree)
+{
+  if(json.size() != tree.subtrees.size())
+  {
+    std::cerr << "Number of blackboards don't match:"
+              << json.size() << "/" << tree.subtrees.size() << "\n";
+    throw std::runtime_error("Number of blackboards don't match:");
+  }
+  for(size_t i=0; i<tree.subtrees.size(); i++)
+  {
+    ImportBlackboardFromJSON(json.at(i), *tree.subtrees.at(i)->blackboard);
+  }
+}
+
 }   // namespace BT
diff --git a/src/json_export.cpp b/src/json_export.cpp
index 41084e914..2ffb3d86d 100644
--- a/src/json_export.cpp
+++ b/src/json_export.cpp
@@ -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);
     }
@@ -38,21 +38,65 @@ bool JsonExporter::toJson(const Any &any, nlohmann::json &dst) const
   return true;
 }
 
-nlohmann::json ExportBlackboardToJSON(Blackboard &blackboard)
+JsonExporter::ExpectedEntry JsonExporter::fromJson(const nlohmann::json &source) const
 {
-  nlohmann::json dest;
-  for(auto entry_name: blackboard.getKeys())
+  if(source.is_null())
   {
-    std::string name(entry_name);
-    if(auto any_ref = blackboard.getAnyLocked(name))
-    {
-      if(auto any_ptr = any_ref.get())
-      {
-        JsonExporter::get().toJson(*any_ptr, dest[name]);
-      }
-    }
+    return nonstd::make_unexpected("json object is null");
+  }
+  if( source.is_string())
+  {
+    return Entry{BT::Any(source.get<std::string>()),
+                 BT::TypeInfo::Create<std::string>()};
   }
-  return dest;
+  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>()};
+  }
+  if( source.is_number_float())
+  {
+    return Entry{BT::Any(source.get<double>()),
+                 BT::TypeInfo::Create<double>()};
+  }
+  if( source.is_boolean())
+  {
+    return Entry{BT::Any(source.get<bool>()),
+                 BT::TypeInfo::Create<bool>()};
+  }
+
+  if(!source.contains("__type"))
+  {
+    return nonstd::make_unexpected("Missing field '__type'");
+  }
+  auto type_it = type_names_.find(source["__type"]);
+  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())
+  {
+    return nonstd::make_unexpected("Type not found in registered list");
+  }
+  return func_it->second(source);
 }
 
+JsonExporter::ExpectedEntry
+JsonExporter::fromJson(const nlohmann::json &source, std::type_index type) const
+{
+  auto func_it = from_json_converters_.find(type);
+  if(func_it == from_json_converters_.end())
+  {
+    return nonstd::make_unexpected("Type not found in registered list");
+  }
+  return func_it->second(source);
+}
+
+
 }
diff --git a/src/loggers/groot2_publisher.cpp b/src/loggers/groot2_publisher.cpp
index f40ae1c66..d3f00bec2 100644
--- a/src/loggers/groot2_publisher.cpp
+++ b/src/loggers/groot2_publisher.cpp
@@ -1,4 +1,3 @@
-#include "behaviortree_cpp/json_export.h"
 #include "behaviortree_cpp/loggers/groot2_publisher.h"
 #include "behaviortree_cpp/loggers/groot2_protocol.h"
 #include "behaviortree_cpp/xml_parsing.h"
diff --git a/tests/gtest_json.cpp b/tests/gtest_json.cpp
index 85cb6b8db..df0e6f62c 100644
--- a/tests/gtest_json.cpp
+++ b/tests/gtest_json.cpp
@@ -1,19 +1,23 @@
 #include <gtest/gtest.h>
+#include "behaviortree_cpp/blackboard.h"
 #include "behaviortree_cpp/json_export.h"
+#include "behaviortree_cpp/basic_types.h"
 
 //----------- Custom types ----------
 
+namespace TestTypes {
+
 struct Vector3D {
-  double x;
-  double y;
-  double z;
+  double x = 0;
+  double y = 0;
+  double z = 0;
 };
 
 struct Quaternion3D {
-  double w;
-  double x;
-  double y;
-  double z;
+  double w = 1;
+  double x = 0;
+  double y = 0;
+  double z = 0;
 };
 
 struct Pose3D {
@@ -21,67 +25,120 @@ 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 ----------
+
+class JsonTest : public testing::Test {
+protected:
+
+  JsonTest() {
+    BT::JsonExporter& exporter = BT::JsonExporter::get();
+    exporter.addConverter<TestTypes::Pose3D>();
+    exporter.addConverter<TestTypes::Vector3D>();
+    exporter.addConverter<TestTypes::Quaternion3D>();
+  }
+};
 
-using namespace BT;
 
-TEST(JsonTest, Exporter)
+TEST_F(JsonTest, TwoWaysConversion)
 {
-  JsonExporter exporter;
+  BT::JsonExporter& exporter = BT::JsonExporter::get();
 
-  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"]);
   exporter.toJson(BT::Any(3.14), json["real"]);
+  exporter.toJson(BT::Any(pose), json["pose"]);
 
-  // expected to throw, because we haven't called addConverter()
-  ASSERT_FALSE( exporter.toJson(BT::Any(pose), json["pose"]) );
+  std::cout << json.dump(2) << std::endl;
 
-  // now it should work
-  exporter.addConverter<Pose3D>();
-  exporter.toJson(BT::Any(pose), json["pose"]);
+  ASSERT_EQ(json["int"], 69);
+  ASSERT_EQ(json["real"], 3.14);
 
-  nlohmann::json json_expected;
-  json_expected["int"] = 69;
-  json_expected["real"] = 3.14;
+  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);
 
-  json_expected["pose"]["pos"]["x"] = 1;
-  json_expected["pose"]["pos"]["y"] = 2;
-  json_expected["pose"]["pos"]["z"] = 3;
+  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);
 
-  json_expected["pose"]["rot"]["w"] = 4;
-  json_expected["pose"]["rot"]["x"] = 5;
-  json_expected["pose"]["rot"]["y"] = 6;
-  json_expected["pose"]["rot"]["z"] = 7;
+  // check the two-ways transform, i.e. "from_json"
+  auto pose2 = exporter.fromJson(json["pose"])->first.cast<TestTypes::Pose3D>();
 
-  ASSERT_EQ(json_expected, json);
+  ASSERT_EQ(pose.pos.x, pose2.pos.x);
+  ASSERT_EQ(pose.pos.y, pose2.pos.y);
+  ASSERT_EQ(pose.pos.z, pose2.pos.z);
 
-  std::cout << json.dump(2) << std::endl;
+  ASSERT_EQ(pose.rot.w, pose2.rot.w);
+  ASSERT_EQ(pose.rot.x, pose2.rot.x);
+  ASSERT_EQ(pose.rot.y, pose2.rot.y);
+  ASSERT_EQ(pose.rot.z, pose2.rot.z);
+
+  auto num = exporter.fromJson(json["int"])->first.cast<int>();
+  ASSERT_EQ(num, 69);
+  auto real = exporter.fromJson(json["real"])->first.cast<double>();
+  ASSERT_EQ(real, 3.14);
+}
+
+TEST_F(JsonTest, ConvertFromString)
+{
+  TestTypes::Vector3D vect;
+  auto const test_json = R"(json:{"x":2.1, "y":4.2, "z":6.3})";
+  ASSERT_NO_THROW(vect = BT::convertFromString<TestTypes::Vector3D>(test_json));
+
+  ASSERT_EQ(vect.x, 2.1);
+  ASSERT_EQ(vect.y, 4.2);
+  ASSERT_EQ(vect.z, 6.3);
 }
 
+TEST_F(JsonTest, BlackboardInOut)
+{
+  auto bb_in = BT::Blackboard::create();
+  bb_in->set("int", 42);
+  bb_in->set("real", 3.14);
+  bb_in->set("vect", TestTypes::Vector3D{1.1, 2.2, 3.3});
+
+  auto json = ExportBlackboardToJSON(*bb_in);
+  std::cout << json.dump(2) << std::endl;
 
+  auto bb_out = BT::Blackboard::create();
+  ImportBlackboardFromJSON(json, *bb_out);
+
+  ASSERT_EQ(bb_out->get<int>("int"), 42);
+  ASSERT_EQ(bb_out->get<double>("real"), 3.14);
+
+  auto vect_out = bb_out->get<TestTypes::Vector3D>("vect");
+  ASSERT_EQ(vect_out.x, 1.1);
+  ASSERT_EQ(vect_out.y, 2.2);
+  ASSERT_EQ(vect_out.z, 3.3);
+}
 
 
diff --git a/tests/gtest_ports.cpp b/tests/gtest_ports.cpp
index 7a8a14f8a..80c361f4c 100644
--- a/tests/gtest_ports.cpp
+++ b/tests/gtest_ports.cpp
@@ -1,5 +1,7 @@
 #include <gtest/gtest.h>
 #include "behaviortree_cpp/bt_factory.h"
+#include "behaviortree_cpp/xml_parsing.h"
+#include "behaviortree_cpp/json_export.h"
 
 using namespace BT;
 
@@ -274,6 +276,11 @@ struct Point2D {
 template <> [[nodiscard]]
 Point2D BT::convertFromString<Point2D>(StringView str)
 {
+  if(StartWith(str, "json:"))
+  {
+    str.remove_prefix(5);
+    return convertFromJSON<Point2D>(str);
+  }
   const auto parts = BT::splitString(str, ',');
   if (parts.size() != 2)
   {
@@ -284,6 +291,18 @@ Point2D BT::convertFromString<Point2D>(StringView str)
   return {x, y};
 }
 
+template <> [[nodiscard]]
+std::string BT::toStr<Point2D>(const Point2D& point)
+{
+  return std::to_string(point.x) + "," + std::to_string(point.y);
+}
+
+BT_JSON_CONVERTER(Point2D, point)
+{
+  add_field("x", &point.x);
+  add_field("y", &point.y);
+}
+
 
 class DefaultTestAction : public SyncActionNode
 {
@@ -428,20 +447,24 @@ class NodeWithDefaultPoints : public SyncActionNode
 
   NodeStatus tick() override
   {
-    Point2D vectA, vectB, vectC, vectD, input;
-    if (!getInput("pointA", vectA) || vectA != Point2D{1, 2}) {
+    Point2D pointA, pointB, pointC, pointD, pointE, input;
+
+    if (!getInput("pointA", pointA) || pointA != Point2D{1, 2}) {
       throw std::runtime_error("failed pointA");
     }
-    if (!getInput("pointB", vectB) || vectB != Point2D{3, 4}) {
+    if (!getInput("pointB", pointB) || pointB != Point2D{3, 4}) {
       throw std::runtime_error("failed pointB");
     }
-    if (!getInput("pointC", vectC) || vectC != Point2D{5, 6}) {
+    if (!getInput("pointC", pointC) || pointC != Point2D{5, 6}) {
       throw std::runtime_error("failed pointC");
     }
-    if (!getInput("pointD", vectD) || vectD != Point2D{7, 8}) {
+    if (!getInput("pointD", pointD) || pointD != Point2D{7, 8}) {
+      throw std::runtime_error("failed pointD");
+    }
+    if (!getInput("pointE", pointE) || pointE != Point2D{9, 10}) {
       throw std::runtime_error("failed pointD");
     }
-    if (!getInput("input", input) || input != Point2D{9, 10}) {
+    if (!getInput("input", input) || input != Point2D{-1, -2}) {
       throw std::runtime_error("failed input");
     }
     return NodeStatus::SUCCESS;
@@ -453,20 +476,24 @@ class NodeWithDefaultPoints : public SyncActionNode
             BT::InputPort<Point2D>("pointA", Point2D{1, 2}, "default value is [1,2]"),
             BT::InputPort<Point2D>("pointB", "{point}", "default value inside blackboard {point}"),
             BT::InputPort<Point2D>("pointC", "5,6", "default value is [5,6]"),
-            BT::InputPort<Point2D>("pointD", "{=}", "default value inside blackboard {pointD}")};
+            BT::InputPort<Point2D>("pointD", "{=}", "default value inside blackboard {pointD}"),
+            BT::InputPort<Point2D>("pointE", R"(json:{"x":9,"y":10})",
+                                   "default value is [9,10]")};
   }
 };
 
 
-TEST(PortTest, DefaultInputVectors)
+TEST(PortTest, DefaultInputPoint2D)
 {
   std::string xml_txt = R"(
     <root BTCPP_format="4" >
       <BehaviorTree>
-        <NodeWithDefaultPoints input="9,10"/>
+        <NodeWithDefaultPoints input="-1,-2"/>
       </BehaviorTree>
     </root>)";
 
+  JsonExporter::get().addConverter<Point2D>();
+
   BehaviorTreeFactory factory;
   factory.registerNodeType<NodeWithDefaultPoints>("NodeWithDefaultPoints");
   auto tree = factory.createTreeFromText(xml_txt);
@@ -477,6 +504,8 @@ TEST(PortTest, DefaultInputVectors)
   BT::NodeStatus status;
   ASSERT_NO_THROW(status = tree.tickOnce());
   ASSERT_EQ(status, NodeStatus::SUCCESS);
+
+  std::cout << writeTreeNodesModelXML(factory) << std::endl;
 }
 
 class NodeWithDefaultStrings : public SyncActionNode
@@ -531,6 +560,8 @@ TEST(PortTest, DefaultInputStrings)
   BT::NodeStatus status;
   ASSERT_NO_THROW(status = tree.tickOnce());
   ASSERT_EQ(status, NodeStatus::SUCCESS);
+
+  std::cout << writeTreeNodesModelXML(factory) << std::endl;
 }
 
 struct TestStruct
diff --git a/tests/gtest_preconditions.cpp b/tests/gtest_preconditions.cpp
index 26d55cba8..ed95ea115 100644
--- a/tests/gtest_preconditions.cpp
+++ b/tests/gtest_preconditions.cpp
@@ -298,3 +298,47 @@ TEST(Preconditions, Issue615_NoSkipWhenRunning_B)
   tree.rootBlackboard()->set("check", false);
   ASSERT_EQ( tree.tickOnce(), NodeStatus::RUNNING );
 }
+
+
+
+TEST(Preconditions, Remapping)
+{
+  static constexpr auto xml_text = R"(
+  <root BTCPP_format="4">
+
+    <BehaviorTree ID="Main">
+      <Sequence>
+        <Script  code="value:=1" />
+        <SubTree ID="Sub1" param="{value}"/>
+        <TestA _skipIf="value!=1" />
+      </Sequence>
+    </BehaviorTree>
+
+    <BehaviorTree ID="Sub1">
+      <Sequence>
+        <SubTree ID="Sub2" _skipIf="param!=1" />
+      </Sequence>
+    </BehaviorTree>
+
+    <BehaviorTree ID="Sub2">
+      <Sequence>
+        <TestB/>
+      </Sequence>
+    </BehaviorTree>
+  </root>
+  )";
+
+  BehaviorTreeFactory factory;
+
+  std::array<int, 2> counters;
+  RegisterTestTick(factory, "Test", counters);
+
+  factory.registerBehaviorTreeFromText(xml_text);
+  auto tree = factory.createTree("Main");
+
+  auto status = tree.tickWhileRunning();
+
+  ASSERT_EQ(status, BT::NodeStatus::SUCCESS);
+  ASSERT_EQ( counters[0], 1 );
+  ASSERT_EQ( counters[1], 1 );
+}