From c5561ac3e3cff534ef517c76cba9ddebdc73284e Mon Sep 17 00:00:00 2001 From: David Yackzan Date: Tue, 28 Jan 2025 17:47:20 -0700 Subject: [PATCH] Add type & value fields to primitive json types This aligns with how custom types are represented by the JsonExporter. * Update both toJson & fromJson functions in the JsonExporter * Add & update tests Co-authored by: David Sobek --- src/json_export.cpp | 70 ++++++++++++++++++++++++++++---------------- tests/gtest_json.cpp | 31 ++++++++++++++++---- 2 files changed, 70 insertions(+), 31 deletions(-) diff --git a/src/json_export.cpp b/src/json_export.cpp index 1baac478a..2eb44ea64 100644 --- a/src/json_export.cpp +++ b/src/json_export.cpp @@ -1,5 +1,11 @@ #include "behaviortree_cpp/json_export.h" +namespace +{ +constexpr std::string_view kTypeField = "__type"; +constexpr std::string_view kValueField = "value"; +} // namespace + namespace BT { @@ -16,19 +22,23 @@ bool JsonExporter::toJson(const Any& any, nlohmann::json& dst) const if(any.isString()) { - dst = any.cast(); + dst[kTypeField] = "string"; + dst[kValueField] = any.cast(); } else if(type == typeid(int64_t)) { - dst = any.cast(); + dst[kTypeField] = "int64_t"; + dst[kValueField] = any.cast(); } else if(type == typeid(uint64_t)) { - dst = any.cast(); + dst[kTypeField] = "uint64_t"; + dst[kValueField] = any.cast(); } else if(type == typeid(double)) { - dst = any.cast(); + dst[kTypeField] = "double"; + dst[kValueField] = any.cast(); } else { @@ -51,33 +61,41 @@ JsonExporter::ExpectedEntry JsonExporter::fromJson(const nlohmann::json& source) { return nonstd::make_unexpected("json object is null"); } - if(source.is_string()) - { - return Entry{ BT::Any(source.get()), - BT::TypeInfo::Create() }; - } - if(source.is_number_unsigned()) - { - return Entry{ BT::Any(source.get()), BT::TypeInfo::Create() }; - } - if(source.is_number_integer()) + if(const auto source_type_it = source.find(kTypeField); source_type_it == source.end()) { - return Entry{ BT::Any(source.get()), BT::TypeInfo::Create() }; - } - if(source.is_number_float()) - { - return Entry{ BT::Any(source.get()), BT::TypeInfo::Create() }; - } - if(source.is_boolean()) - { - return Entry{ BT::Any(source.get()), BT::TypeInfo::Create() }; + return nonstd::make_unexpected("Missing field '" + std::string(kTypeField) + "'."); } - if(!source.contains("__type")) + const auto source_value_it = source.find(kValueField); + if(source_value_it != source.end()) { - return nonstd::make_unexpected("Missing field '__type'"); + if(source_value_it->is_string()) + { + return Entry{ BT::Any(source_value_it->get()), + BT::TypeInfo::Create() }; + } + if(source_value_it->is_number_unsigned()) + { + return Entry{ BT::Any(source_value_it->get()), + BT::TypeInfo::Create() }; + } + if(source_value_it->is_number_integer()) + { + return Entry{ BT::Any(source_value_it->get()), + BT::TypeInfo::Create() }; + } + if(source_value_it->is_number_float()) + { + return Entry{ BT::Any(source_value_it->get()), + BT::TypeInfo::Create() }; + } + if(source_value_it->is_boolean()) + { + return Entry{ BT::Any(source_value_it->get()), BT::TypeInfo::Create() }; + } } - auto type_it = type_names_.find(source["__type"]); + + auto type_it = type_names_.find(source[kTypeField]); if(type_it == type_names_.end()) { return nonstd::make_unexpected("Type not found in registered list"); diff --git a/tests/gtest_json.cpp b/tests/gtest_json.cpp index f7b9e5909..752190c52 100644 --- a/tests/gtest_json.cpp +++ b/tests/gtest_json.cpp @@ -3,6 +3,12 @@ #include "behaviortree_cpp/json_export.h" #include "behaviortree_cpp/basic_types.h" +namespace +{ +constexpr std::string_view kTypeField = "__type"; +constexpr std::string_view kValueField = "value"; +} // namespace + //----------- Custom types ---------- namespace TestTypes @@ -95,16 +101,27 @@ TEST_F(JsonTest, TwoWaysConversion) TestTypes::Pose3D pose = { { 1, 2, 3 }, { 4, 5, 6, 7 } }; nlohmann::json json; + exporter.toJson(BT::Any("string_val"), json["string"]); exporter.toJson(BT::Any(69), json["int"]); + exporter.toJson(BT::Any(static_cast(96)), json["uint"]); exporter.toJson(BT::Any(3.14), json["real"]); exporter.toJson(BT::Any(pose), json["pose"]); std::cout << json.dump(2) << std::endl; - ASSERT_EQ(json["int"], 69); - ASSERT_EQ(json["real"], 3.14); + ASSERT_EQ(json["string"][kTypeField], "string"); + ASSERT_EQ(json["string"][kValueField], "string_val"); + + ASSERT_EQ(json["int"][kTypeField], "int64_t"); + ASSERT_EQ(json["int"][kValueField], 69); + + ASSERT_EQ(json["uint"][kTypeField], "uint64_t"); + ASSERT_EQ(json["uint"][kValueField], 96); + + ASSERT_EQ(json["real"][kTypeField], "double"); + ASSERT_EQ(json["real"][kValueField], 3.14); - ASSERT_EQ(json["pose"]["__type"], "Pose3D"); + ASSERT_EQ(json["pose"][kTypeField], "Pose3D"); ASSERT_EQ(json["pose"]["pos"]["x"], 1); ASSERT_EQ(json["pose"]["pos"]["y"], 2); ASSERT_EQ(json["pose"]["pos"]["z"], 3); @@ -126,8 +143,12 @@ TEST_F(JsonTest, TwoWaysConversion) ASSERT_EQ(pose.rot.y, pose2.rot.y); ASSERT_EQ(pose.rot.z, pose2.rot.z); - auto num = exporter.fromJson(json["int"])->first.cast(); - ASSERT_EQ(num, 69); + auto string_val = exporter.fromJson(json["string"])->first.cast(); + ASSERT_EQ(string_val, "string_val"); + auto int_val = exporter.fromJson(json["int"])->first.cast(); + ASSERT_EQ(int_val, 69); + auto uint_val = exporter.fromJson(json["uint"])->first.cast(); + ASSERT_EQ(uint_val, 96); auto real = exporter.fromJson(json["real"])->first.cast(); ASSERT_EQ(real, 3.14); }