Remove jsoncpp dependency
authorChanggyu Choi <changyu.choi@samsung.com>
Fri, 25 Apr 2025 10:41:12 +0000 (19:41 +0900)
committerChanggyu Choi <changyu.choi@samsung.com>
Mon, 28 Apr 2025 03:22:08 +0000 (12:22 +0900)
We use rapidjson instead of jsoncpp.

Signed-off-by: Changgyu Choi <changyu.choi@samsung.com>
12 files changed:
CMakeLists.txt
packaging/tizen-action-framework.spec
src/common/CMakeLists.txt
src/common/action_model.cc
src/common/action_schema.cc
src/common/utils/safe_json.hpp [new file with mode: 0644]
src/pkgmgr_plugin_parser/CMakeLists.txt
src/pkgmgr_plugin_parser/json_action_schema_parser.cc
test/unit_tests/action_model_converter_test.cc
test/unit_tests/action_model_test.cc
tool/action_tool/CMakeLists.txt
tool/action_tool/tools/execute.cc

index 2e99f32743b7f5a422c8e6198bff6df4dec5fcb3..3cdc91df00b164a72bcb24d02916862e8731e043 100644 (file)
@@ -45,7 +45,6 @@ PKG_CHECK_MODULES(CAPI_APPFW_SERVICE_APPLICATION_DEPS REQUIRED capi-appfw-servic
 PKG_CHECK_MODULES(DLOG_DEPS REQUIRED dlog)
 PKG_CHECK_MODULES(GLIB_DEPS REQUIRED glib-2.0)
 PKG_CHECK_MODULES(GMOCK_DEPS REQUIRED gmock)
-PKG_CHECK_MODULES(JSONCPP_DEPS REQUIRED jsoncpp)
 PKG_CHECK_MODULES(PKGMGR_PARSER_DEPS REQUIRED pkgmgr-parser)
 PKG_CHECK_MODULES(PKGMGR_INFO_DEPS REQUIRED pkgmgr-info)
 PKG_CHECK_MODULES(PKGMGR_INSTALLER_DEPS REQUIRED pkgmgr-installer)
index fcb87e7d8ed916b90e8f76aa20aceed0790cffb8..9d724f8fda15e138ede3d486f353da2758c662d5 100644 (file)
@@ -21,7 +21,6 @@ BuildRequires:  pkgconfig(capi-appfw-service-application)
 BuildRequires:  pkgconfig(dlog)
 BuildRequires:  pkgconfig(glib-2.0)
 BuildRequires:  pkgconfig(gmock)
-BuildRequires:  pkgconfig(jsoncpp)
 BuildRequires:  pkgconfig(pkgmgr-parser)
 BuildRequires:  pkgconfig(pkgmgr-info)
 BuildRequires:  pkgconfig(pkgmgr-installer)
index 07764f888daedd63a93428de2a134b5f1d492465..9315f1c1e6e7f1eb12b4fc68d48520f4a71ba193 100644 (file)
@@ -10,7 +10,6 @@ TARGET_INCLUDE_DIRECTORIES(${TARGET_TIZEN_ACTION_COMMON} PUBLIC "${CMAKE_CURRENT
 
 APPLY_PKG_CONFIG(${TARGET_TIZEN_ACTION_COMMON} PUBLIC
   DLOG_DEPS
-  JSONCPP_DEPS
   TIZEN_DATABASE_DEPS
 )
 
index a384fd59e75e716e03146b7d2c9d4ac977acf1f3..9785eb820070ffd86745059f2ac790ecf2042408 100644 (file)
 
 #include "action_model.h"
 
-#include <jsoncpp/json/json.h>
-
 #include <utility>
 
 #include "common/utils/logging.hh"
+#include "common/utils/safe_json.hpp"
 
 namespace common {
-namespace {
-
-Json::Value ParseJsonFromString(const std::string& json_str) {
-  Json::Value root;
-  Json::CharReaderBuilder builder;
-  JSONCPP_STRING err;
-  const std::unique_ptr<Json::CharReader> reader(builder.newCharReader());
-  if (!reader->parse(json_str.c_str(), json_str.c_str() + json_str.length(),
-                     &root, &err)) {
-    LOG(ERROR) << "Failed to parse json string: " << err;
-    return {};
-  }
-  return root;
-}
-
-}  // namespace
 
 ActionModel::ActionModel(std::string json_str)
     : json_str_(std::move(json_str)) {
-  Json::Value root = ParseJsonFromString(json_str_);
-  action_id_ = root["name"].asString();
-  for (auto const& it : root["params"].getMemberNames()) {
-    std::string key = it;
-    std::string value = root["params"][key].asString();
-    parameters_.emplace_back(std::move(key), std::move(value));
+  SafeJson root(json_str_);
+  action_id_ = root.get<std::string>("name");
+  for (auto& [key, item] : root.members("params")) {
+    std::string value = item->GetString();
+    parameters_.emplace_back(key, std::move(value));
   }
 }
 
index d1c0dd82ea06fcaa81f9a924f2d913fcc516db5f..cd584609b4d1e01dad988e8b13510dd7f2e22236 100644 (file)
 
 #include "common/action_schema.h"
 
-#include <json/json.h>
-
-#include <string>
 #include <map>
+#include <string>
 
 #include "common/utils/logging.hh"
+#include "common/utils/safe_json.hpp"
 
 namespace {
 
-Json::Value ParseJsonFromString(const std::string& json_str) {
-  Json::Value root;
-  Json::CharReaderBuilder builder;
-  JSONCPP_STRING err;
-  const std::unique_ptr<Json::CharReader> reader(builder.newCharReader());
-  if (!reader->parse(json_str.c_str(), json_str.c_str() + json_str.length(),
-      &root, &err)) {
-    LOG(ERROR) << "Failed to parse json string: " << err;
-    return {};
-  }
-  return root;
-}
-
 std::map<std::string, common::ParameterType> kTypeMap = {
-  {"integer", common::ParameterType::IntType},
-  {"string", common::ParameterType::StringType},
-  {"double", common::ParameterType::DoubleType},
-  {"bool", common::ParameterType::BoolType},
-  {"json", common::ParameterType::JsonType},
+    {"integer", common::ParameterType::IntType},
+    {"string", common::ParameterType::StringType},
+    {"double", common::ParameterType::DoubleType},
+    {"bool", common::ParameterType::BoolType},
+    {"json", common::ParameterType::JsonType},
 };
 
 common::ParameterType ConvertStringToParameterType(const std::string& type) {
@@ -69,52 +55,59 @@ namespace common {
 
 ActionSchema::ActionSchema() {}
 
+#include <iostream>
 ActionSchema::ActionSchema(const std::string& json_str) {
   try {
-    Json::Value root = ParseJsonFromString(json_str);
-
-    action_id_ = root["name"].asString();
-    appid_ = root["appId"].asString();  // TODO: Change to details::appId
-    category_ = root["category"].asString();
-    description_ = root["desc"].asString();
-    type_ = GetActionTypeFromString(root["type"].asString());
-
+    SafeJson root(json_str);
+    action_id_ = root.get<std::string>("name");
+    appid_ = root.get<std::string>("appId");  // TODO: Change to details::appId
+    category_ = root.get<std::string>("category");
+    description_ = root.get<std::string>("desc");
+    type_ = GetActionTypeFromString(root.get<std::string>("type"));
     // params
-    for (auto const& it : root["params"].getMemberNames()) {
-      std::string name = it;
-      common::ParameterType type =
-          ConvertStringToParameterType(root["params"][it]["type"].asString());
-      std::string description = root["params"][it]["desc"].asString();
-      bool is_required = root["params"][it]["isRequired"].asBool();
+    for (const auto& [name, item] : root.members("params")) {
+      common::ParameterType type = ConvertStringToParameterType(
+          SafeJson::get<std::string>(*item, "type"));
+      std::string description = SafeJson::get<std::string>(*item, "desc");
+      bool is_required = false;
+      try {
+        is_required = SafeJson::get<bool>(*item, "isRequired");
+      } catch (std::runtime_error&) {
+        is_required = false;
+      }
+
       parameters_.emplace_back(name, type, "", description, is_required);
     }
 
     // returns
-    for (auto const& it : root["returns"].getMemberNames()) {
-      std::string name = it;
-      common::ParameterType type =
-          ConvertStringToParameterType(root["returns"][it]["type"].asString());
-      std::string description = root["returns"][it]["desc"].asString();
+    for (const auto& [name, value] : root.members("returns")) {
+      common::ParameterType type = ConvertStringToParameterType(
+          SafeJson::get<std::string>(*value, "type"));
+      std::string description = SafeJson::get<std::string>(*value, "desc");
       // ActionParameterType -> another class???
       returns_.emplace_back(name, type, "", description, false);
     }
 
-    for (auto const& it : root["required-privileges"])
-      privileges_.push_back(it.asString());
-  } catch (const Json::Exception& e) {
+    for (auto const& it : root.array("required-privileges"))
+      privileges_.push_back(it->GetString());
+  } catch (...) {
     // TODO: how to handle invalid json case?
-    LOG(ERROR) << "Failed to parse json string: " << e.what();
+    LOG(ERROR) << "Failed to parse json string";
   }
 }
 
-ActionSchema::ActionSchema(std::string action_id, std::string appid,
-    std::string category, std::string description,
-    std::vector<ActionParameter> parameters,
-    std::vector<std::string> privileges)
-    : action_id_(std::move(action_id)), appid_(std::move(appid)),
-      category_(std::move(category)), description_(std::move(description)),
-      parameters_(std::move(parameters)), privileges_(std::move(privileges)) {
-}
+ActionSchema::ActionSchema(std::string action_id,
+                           std::string appid,
+                           std::string category,
+                           std::string description,
+                           std::vector<ActionParameter> parameters,
+                           std::vector<std::string> privileges)
+    : action_id_(std::move(action_id)),
+      appid_(std::move(appid)),
+      category_(std::move(category)),
+      description_(std::move(description)),
+      parameters_(std::move(parameters)),
+      privileges_(std::move(privileges)) {}
 
 const std::string& ActionSchema::GetActionId() const {
   return action_id_;
diff --git a/src/common/utils/safe_json.hpp b/src/common/utils/safe_json.hpp
new file mode 100644 (file)
index 0000000..c63c8f3
--- /dev/null
@@ -0,0 +1,173 @@
+/*
+ * Copyright (c) 2025 Samsung Electronics Co., Ltd All Rights Reserved
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef COMMON_UTILS_SAFE_JSON_HH_
+#define COMMON_UTILS_SAFE_JSON_HH_
+
+#include <rapidjson/document.h>
+#include <sstream>
+#include <stdexcept>
+#include <vector>
+
+namespace common {
+
+class SafeJson {
+ public:
+  SafeJson() = default;
+
+  SafeJson(const std::string& json) {
+    doc_.Parse(json.c_str());
+    if (doc_.HasParseError()) {
+      throw std::runtime_error("Invalid JSON format");
+    }
+  }
+
+  template <typename T>
+  T get(std::vector<std::string> path) const {
+    const rapidjson::Value* val = traverse(path);
+    return extract<T>(*val);
+  }
+
+  template <typename T>
+  T get(const std::string& dotPath) const {
+    std::vector<std::string> path = split(dotPath, '.');
+    return get<T>(std::move(path));
+  }
+
+  const rapidjson::Value& getRaw(std::vector<std::string> path) const {
+    return *traverse(path);
+  }
+
+  std::vector<const rapidjson::Value*> array(
+      std::vector<std::string> path) const {
+    const rapidjson::Value* val = traverse(path);
+    if (!val->IsArray()) {
+      throw std::runtime_error("Expected array at path");
+    }
+    std::vector<const rapidjson::Value*> result;
+    for (auto& v : val->GetArray()) {
+      result.push_back(&v);
+    }
+    return result;
+  }
+
+  std::vector<const rapidjson::Value*> array(const std::string& dotPath) const {
+    std::vector<std::string> path = split(dotPath, '.');
+    return array(std::move(path));
+  }
+
+  std::vector<std::pair<std::string, const rapidjson::Value*>> members(
+      std::vector<std::string> path) const {
+    const rapidjson::Value* val = traverse(path);
+    if (!val->IsObject()) {
+      throw std::runtime_error("Expected object for member iteration");
+    }
+
+    std::vector<std::pair<std::string, const rapidjson::Value*>> result;
+    for (auto it = val->MemberBegin(); it != val->MemberEnd(); ++it) {
+      result.emplace_back(it->name.GetString(), &it->value);
+    }
+    return result;
+  }
+
+  std::vector<std::pair<std::string, const rapidjson::Value*>> members(
+      const std::string& dotPath) const {
+    std::vector<std::string> path = split(dotPath, '.');
+    return members(std::move(path));
+  }
+
+  template <typename T>
+  static T get(const rapidjson::Value& node, std::vector<std::string> path) {
+    const rapidjson::Value* curr = &node;
+    for (const auto& key : path) {
+      if (!curr->IsObject() || !curr->HasMember(key.c_str())) {
+        throw std::runtime_error("Missing key: " + key);
+      }
+      curr = &(*curr)[key.c_str()];
+    }
+    return SafeJson().extract<T>(*curr);
+  }
+
+  template <typename T>
+  static T get(const rapidjson::Value& node, const std::string& dotPath) {
+    std::vector<std::string> path = SafeJson().split(dotPath, '.');
+    return get<T>(node, std::move(path));
+  }
+
+ private:
+  rapidjson::Document doc_;
+
+  const rapidjson::Value* traverse(std::vector<std::string> path) const {
+    const rapidjson::Value* curr = &doc_;
+    for (const auto& key : path) {
+      if (!curr->IsObject()) {
+        throw std::runtime_error("Expected object at " + key);
+      }
+      if (!curr->HasMember(key.c_str())) {
+        throw std::runtime_error("Missing key: " + key);
+      }
+      curr = &(*curr)[key.c_str()];
+    }
+    return curr;
+  }
+
+  template <typename T>
+  T extract(const rapidjson::Value& val) const;
+
+  std::vector<std::string> split(const std::string& s, char delim) const {
+    std::vector<std::string> elems;
+    std::stringstream ss(s);
+    std::string item;
+    while (std::getline(ss, item, delim)) {
+      elems.push_back(item);
+    }
+    return elems;
+  }
+};
+
+// νŠΉμˆ˜ν™”
+template <>
+inline std::string SafeJson::extract<std::string>(
+    const rapidjson::Value& val) const {
+  if (!val.IsString())
+    throw std::runtime_error("Type mismatch: expected string");
+  return val.GetString();
+}
+
+template <>
+inline int SafeJson::extract<int>(const rapidjson::Value& val) const {
+  if (!val.IsInt())
+    throw std::runtime_error("Type mismatch: expected int");
+  return val.GetInt();
+}
+
+template <>
+inline double SafeJson::extract<double>(const rapidjson::Value& val) const {
+  if (!val.IsNumber())
+    throw std::runtime_error("Type mismatch: expected number");
+  return val.GetDouble();
+}
+
+template <>
+inline bool SafeJson::extract<bool>(const rapidjson::Value& val) const {
+  if (!val.IsBool())
+    throw std::runtime_error("Type mismatch: expected bool");
+  return val.GetBool();
+}
+
+}  // namespace common
+
+#endif  // COMMON_UTILS_SAFE_JSON_HH_
index 8a76bbca470caf61b8420c0cdc6092fe6afd3544..042bfda76a46abdc4f2fe2947c4087401eed689c 100644 (file)
@@ -7,7 +7,6 @@ TARGET_INCLUDE_DIRECTORIES(${TARGET_TIZEN_ACTION_PLUGIN} PUBLIC ${CMAKE_CURRENT_
 APPLY_PKG_CONFIG(${TARGET_TIZEN_ACTION_PLUGIN} PUBLIC
   DLOG_DEPS
   GLIB_DEPS
-  JSONCPP_DEPS
   PKGMGR_INFO_DEPS
   PKGMGR_INSTALLER_DEPS
   PKGMGR_PARSER_DEPS
index 0e95337effdf7d81d167a57cbe7a11b5a625002f..edb285acfc5578a4a23edfd6a94d46320ac21a08 100644 (file)
 
 #include "pkgmgr_plugin_parser/json_action_schema_parser.hh"
 
-#include <json/json.h>
-
 #include <algorithm>
 #include <fstream>
 #include <string>
 #include <vector>
 
 #include "common/utils/logging.hh"
+#include "common/utils/safe_json.hpp"
 #include "pkgmgr_plugin_parser/action_schema.hh"
 
-namespace {
-
-std::string JsonToString(const Json::Value& val) {
-  Json::StreamWriterBuilder builder;
-  builder["indentation"] = "";
-  const std::unique_ptr<Json::StreamWriter> writer(builder.newStreamWriter());
-  return Json::writeString(builder, val);
-}
-
-}
-
 namespace parser {
 
 ActionSchema JsonActionSchemaParser::Parse(const std::string& pkgid,
-    const std::string& path) {
-  Json::CharReaderBuilder rbuilder;
-  rbuilder["collectComments"] = false;
-
+                                           const std::string& path) {
   std::ifstream ifs(path);
-  Json::Value root;
-  std::string error;
-  if (!Json::parseFromStream(rbuilder, ifs, &root, &error)) {
-    LOG(ERROR) << "Failed to read json file: " << error;
-    return {};
-  }
+  std::string json((std::istreambuf_iterator<char>(ifs)),
+                   std::istreambuf_iterator<char>());
 
-  // TODO
-  std::string name = root["name"].asString();
-  std::string category = root["category"].asString();
-  const auto priv_array = root["required-privileges"];
+  common::SafeJson root(json);
+  std::string name = root.get<std::string>("name");
+  std::string category = root.get<std::string>("category");
+  const auto priv_array = root.array("required-privileges");
   std::vector<std::string> privs;
   privs.reserve(priv_array.size());
   std::transform(priv_array.begin(), priv_array.end(),
-      std::back_inserter(privs),
-      [](const auto& priv) { return priv.asString(); });
-  std::string json_str = JsonToString(root);
-
+                 std::back_inserter(privs),
+                 [](const auto& priv) { return priv->GetString(); });
   LOG(DEBUG) << "Parased action for pkgid[ " << pkgid << " ] : " << name;
-  LOG(DEBUG) << json_str;
+  LOG(DEBUG) << json;
 
   return ActionSchema(std::move(pkgid), std::move(name), std::move(category),
-      std::move(privs), std::move(json_str));
+                      std::move(privs), std::move(json));
 }
 
 }  // namespace parser
index b621b6a8715622a4e85a7ef671100165f2259af4..f9415f357f51329c39ee2b1b06fd0a43aa575dce 100644 (file)
@@ -8,7 +8,7 @@ TEST(ActionModelConverterTest, ConvertToAction) {
   std::string json = R"({
     "name": "test_action",
     "params": {
-      "param": "value",
+      "param": "value"
     }
   })";
 
index 8bda0902d6652d81affbd5230b04e61c81661243..15db8cb79d3d5be81d7f92ba22b9ec3ab9986b81 100644 (file)
@@ -8,7 +8,7 @@ TEST(ActionModelTest, JsonInitialization) {
       "name": "new_action",
       "params": {
         "param1": "hello1",
-        "param2": "hello2",
+        "param2": "hello2"
       }
     })");
 
index 9272749eba4dbc0365a4a356852bad19cb1f214f..26f875944ed5ac5097861f9b31788996a625bd15 100644 (file)
@@ -19,7 +19,6 @@ APPLY_PKG_CONFIG(${TARGET_ACTION_FW_TOOL} PUBLIC
   CAPI_APPFW_APP_COMMON_DEPS
   DLOG_DEPS
   GLIB_DEPS
-  JSONCPP_DEPS
   PKGMGR_INFO_DEPS
   PKGMGR_INSTALLER_DEPS
   PKGMGR_PARSER_DEPS
index 030dbbb25a14cff65058a7440621d2bf2d4eda90..bf0a9793f6e18e4bcb96c4c592fa1ad7c96e81cf 100644 (file)
@@ -17,8 +17,6 @@
 #include "action_tool.hh"
 #include "log_private.hh"
 
-#include <jsoncpp/json/json.h>
-
 #include <memory>
 #include <utility>
 
@@ -31,19 +29,6 @@ namespace {
 
 constexpr const char kStubAppId[] = "org.tizen.action-framework.service";
 
-// Json::Value ParseJsonFromString(const std::string& json_str) {
-//   Json::Value root;
-//   Json::CharReaderBuilder builder;
-//   JSONCPP_STRING err;
-//   const std::unique_ptr<Json::CharReader> reader(builder.newCharReader());
-//   if (!reader->parse(json_str.c_str(), json_str.c_str() + json_str.length(),
-//       &root, &err)) {
-//     _E("Failed to parse json string");
-//     return {};
-//   }
-//   return root;
-// }
-
 }  // namespace
 
 namespace rpc = rpc_port::tizen_action_service_proxy;