diff --git a/CMakeLists.txt b/CMakeLists.txt index 5ff8a59..a2c7a12 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -40,6 +40,7 @@ option_if_not_defined(CPPDAP_TSAN "Build dap with thread sanitizer" OFF) option_if_not_defined(CPPDAP_INSTALL_VSCODE_EXAMPLES "Build and install dap examples into vscode extensions directory" OFF) option_if_not_defined(CPPDAP_USE_EXTERNAL_NLOHMANN_JSON_PACKAGE "Use nlohmann_json with find_package() instead of building internal submodule" OFF) option_if_not_defined(CPPDAP_USE_EXTERNAL_RAPIDJSON_PACKAGE "Use RapidJSON with find_package()" OFF) +option_if_not_defined(CPPDAP_USE_EXTERNAL_JSONCPP_PACKAGE "Use JsonCpp with find_package()" OFF) ########################################################### # Directories @@ -71,14 +72,20 @@ endif(CPPDAP_BUILD_TESTS) # JSON library ########################################################### -if(CPPDAP_USE_EXTERNAL_NLOHMANN_JSON_PACKAGE AND CPPDAP_USE_EXTERNAL_RAPIDJSON_PACKAGE) - message(FATAL_ERROR "Don't set both CPPDAP_USE_EXTERNAL_NLOHMANN_JSON_PACKAGE and CPPDAP_USE_EXTERNAL_RAPIDJSON_PACKAGE") +if((CPPDAP_USE_EXTERNAL_NLOHMANN_JSON_PACKAGE AND CPPDAP_USE_EXTERNAL_RAPIDJSON_PACKAGE) OR + (CPPDAP_USE_EXTERNAL_RAPIDJSON_PACKAGE AND CPPDAP_USE_EXTERNAL_JSONCPP_PACKAGE) OR + (CPPDAP_USE_EXTERNAL_JSONCPP_PACKAGE AND CPPDAP_USE_EXTERNAL_NLOHMANN_JSON_PACKAGE)) + message(FATAL_ERROR "At most one of CPPDAP_USE_EXTERNAL_NLOHMANN_JSON_PACKAGE, \ +CPPDAP_USE_EXTERNAL_RAPIDJSON_PACKAGE, and CPPDAP_USE_EXTERNAL_JSONCPP_PACKAGE can be set.") elseif(CPPDAP_USE_EXTERNAL_NLOHMANN_JSON_PACKAGE) find_package(nlohmann_json CONFIG REQUIRED) set(CPPDAP_JSON_LIBRARY "nlohmann") elseif(CPPDAP_USE_EXTERNAL_RAPIDJSON_PACKAGE) find_package(RapidJSON CONFIG REQUIRED) set(CPPDAP_JSON_LIBRARY "rapid") +elseif(CPPDAP_USE_EXTERNAL_JSONCPP_PACKAGE) + find_package(jsoncpp CONFIG REQUIRED) + set(CPPDAP_JSON_LIBRARY "jsoncpp") endif() if(NOT DEFINED CPPDAP_JSON_LIBRARY) @@ -91,6 +98,8 @@ if(NOT DEFINED CPPDAP_JSON_LIBRARY) set(CPPDAP_JSON_LIBRARY "nlohmann") elseif(EXISTS "${CPPDAP_JSON_DIR}/include/rapidjson") set(CPPDAP_JSON_LIBRARY "rapid") + elseif(EXISTS "${CPPDAP_JSON_DIR}/include/json") + set(CPPDAP_JSON_LIBRARY "jsoncpp") else() message(FATAL_ERROR "Could not determine JSON library from ${CPPDAP_JSON_DIR}") endif() @@ -139,6 +148,8 @@ function(cppdap_set_json_links target) target_link_libraries(${target} PRIVATE "$") elseif(CPPDAP_USE_EXTERNAL_RAPIDJSON_PACKAGE) target_link_libraries(${target} PRIVATE rapidjson) + elseif(CPPDAP_USE_EXTERNAL_JSONCPP_PACKAGE) + target_link_libraries(${target} PRIVATE JsonCpp::JsonCpp) else() target_include_directories(${target} PRIVATE "${CPPDAP_JSON_DIR}/include/") endif() diff --git a/README.md b/README.md index 1fbebf2..cd45bed 100644 --- a/README.md +++ b/README.md @@ -19,6 +19,8 @@ cd git submodule update --init ``` +Alternatively, `cppdap` can use the [`RapidJSON` library](https://rapidjson.org/) or the [`JsonCpp` library](https://github.com/open-source-parsers/jsoncpp) for JSON serialization. Use the `CPPDAP_USE_EXTERNAL_NLOHMANN_JSON_PACKAGE`, `CPPDAP_USE_EXTERNAL_RAPIDJSON_PACKAGE`, and `CPPDAP_USE_EXTERNAL_JSONCPP_PACKAGE` CMake cache variables to select which library to use. + ## Building ### Linux and macOS diff --git a/src/json_serializer.h b/src/json_serializer.h index 9bc26d5..32a7ce4 100644 --- a/src/json_serializer.h +++ b/src/json_serializer.h @@ -19,6 +19,8 @@ #include "nlohmann_json_serializer.h" #elif defined(CPPDAP_JSON_RAPID) #include "rapid_json_serializer.h" +#elif defined(CPPDAP_JSON_JSONCPP) +#include "jsoncpp_json_serializer.h" #else #error "Unrecognised cppdap JSON library" #endif @@ -32,6 +34,9 @@ using Serializer = NlohmannSerializer; #elif defined(CPPDAP_JSON_RAPID) using Deserializer = RapidDeserializer; using Serializer = RapidSerializer; +#elif defined(CPPDAP_JSON_JSONCPP) +using Deserializer = JsonCppDeserializer; +using Serializer = JsonCppSerializer; #else #error "Unrecognised cppdap JSON library" #endif diff --git a/src/jsoncpp_json_serializer.cpp b/src/jsoncpp_json_serializer.cpp new file mode 100644 index 0000000..e7dc092 --- /dev/null +++ b/src/jsoncpp_json_serializer.cpp @@ -0,0 +1,271 @@ +// Copyright 2023 Microsoft +// +// 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 +// +// https://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. + +#include "jsoncpp_json_serializer.h" + +#include "null_json_serializer.h" + +#include +#include + +namespace dap { +namespace json { + +JsonCppDeserializer::JsonCppDeserializer(const std::string& str) + : json(new Json::Value(JsonCppDeserializer::parse(str))), ownsJson(true) {} + +JsonCppDeserializer::JsonCppDeserializer(const Json::Value* json) + : json(json), ownsJson(false) {} + +JsonCppDeserializer::~JsonCppDeserializer() { + if (ownsJson) { + delete json; + } +} + +bool JsonCppDeserializer::deserialize(dap::boolean* v) const { + if (!json->isBool()) { + return false; + } + *v = json->asBool(); + return true; +} + +bool JsonCppDeserializer::deserialize(dap::integer* v) const { + if (!json->isInt64()) { + return false; + } + *v = json->asInt64(); + return true; +} + +bool JsonCppDeserializer::deserialize(dap::number* v) const { + if (!json->isNumeric()) { + return false; + } + *v = json->asDouble(); + return true; +} + +bool JsonCppDeserializer::deserialize(dap::string* v) const { + if (!json->isString()) { + return false; + } + *v = json->asString(); + return true; +} + +bool JsonCppDeserializer::deserialize(dap::object* v) const { + v->reserve(json->size()); + for (auto i = json->begin(); i != json->end(); i++) { + JsonCppDeserializer d(&*i); + dap::any val; + if (!d.deserialize(&val)) { + return false; + } + (*v)[i.name()] = val; + } + return true; +} + +bool JsonCppDeserializer::deserialize(dap::any* v) const { + if (json->isBool()) { + *v = dap::boolean(json->asBool()); + } else if (json->type() == Json::ValueType::realValue) { + // json->isDouble() returns true for integers as well, so we need to + // explicitly look for the realValue type. + *v = dap::number(json->asDouble()); + } else if (json->isInt64()) { + *v = dap::integer(json->asInt64()); + } else if (json->isString()) { + *v = json->asString(); + } else if (json->isObject()) { + dap::object obj; + if (!deserialize(&obj)) { + return false; + } + *v = obj; + } else if (json->isArray()) { + dap::array arr; + if (!deserialize(&arr)) { + return false; + } + *v = arr; + } else if (json->isNull()) { + *v = null(); + } else { + return false; + } + return true; +} + +size_t JsonCppDeserializer::count() const { + return json->size(); +} + +bool JsonCppDeserializer::array( + const std::function& cb) const { + if (!json->isArray()) { + return false; + } + for (const auto& value : *json) { + JsonCppDeserializer d(&value); + if (!cb(&d)) { + return false; + } + } + return true; +} + +bool JsonCppDeserializer::field( + const std::string& name, + const std::function& cb) const { + if (!json->isObject()) { + return false; + } + auto value = json->find(name.data(), name.data() + name.size()); + if (value == nullptr) { + return cb(&NullDeserializer::instance); + } + JsonCppDeserializer d(value); + return cb(&d); +} + +Json::Value JsonCppDeserializer::parse(const std::string& text) { + Json::CharReaderBuilder builder; + auto jsonReader = std::unique_ptr(builder.newCharReader()); + Json::Value json; + std::string error; + if (!jsonReader->parse(text.data(), text.data() + text.size(), &json, + &error)) { + // cppdap expects that the JSON layer does not throw exceptions. + std::abort(); + } + return json; +} + +JsonCppSerializer::JsonCppSerializer() + : json(new Json::Value()), ownsJson(true) {} + +JsonCppSerializer::JsonCppSerializer(Json::Value* json) + : json(json), ownsJson(false) {} + +JsonCppSerializer::~JsonCppSerializer() { + if (ownsJson) { + delete json; + } +} + +std::string JsonCppSerializer::dump() const { + Json::StreamWriterBuilder writer; + return Json::writeString(writer, *json); +} + +bool JsonCppSerializer::serialize(dap::boolean v) { + *json = (bool)v; + return true; +} + +bool JsonCppSerializer::serialize(dap::integer v) { + *json = (int64_t)v; + return true; +} + +bool JsonCppSerializer::serialize(dap::number v) { + *json = (double)v; + return true; +} + +bool JsonCppSerializer::serialize(const dap::string& v) { + *json = v; + return true; +} + +bool JsonCppSerializer::serialize(const dap::object& v) { + if (!json->isObject()) { + *json = Json::Value(Json::objectValue); + } + for (auto& it : v) { + JsonCppSerializer s(&(*json)[it.first]); + if (!s.serialize(it.second)) { + return false; + } + } + return true; +} + +bool JsonCppSerializer::serialize(const dap::any& v) { + if (v.is()) { + *json = (bool)v.get(); + } else if (v.is()) { + *json = (int64_t)v.get(); + } else if (v.is()) { + *json = (double)v.get(); + } else if (v.is()) { + *json = v.get(); + } else if (v.is()) { + // reachable if dap::object nested is inside other dap::object + return serialize(v.get()); + } else if (v.is()) { + } else { + // reachable if array or custom serialized type is nested inside other + auto type = get_any_type(v); + auto value = get_any_val(v); + if (type && value) { + return type->serialize(this, value); + } + return false; + } + return true; +} + +bool JsonCppSerializer::array(size_t count, + const std::function& cb) { + *json = Json::Value(Json::arrayValue); + for (size_t i = 0; i < count; i++) { + JsonCppSerializer s(&(*json)[Json::Value::ArrayIndex(i)]); + if (!cb(&s)) { + return false; + } + } + return true; +} + +bool JsonCppSerializer::object( + const std::function& cb) { + struct FS : public FieldSerializer { + Json::Value* const json; + + FS(Json::Value* json) : json(json) {} + bool field(const std::string& name, const SerializeFunc& cb) override { + JsonCppSerializer s(&(*json)[name]); + auto res = cb(&s); + if (s.removed) { + json->removeMember(name); + } + return res; + } + }; + + *json = Json::Value(Json::objectValue); + FS fs{json}; + return cb(&fs); +} + +void JsonCppSerializer::remove() { + removed = true; +} + +} // namespace json +} // namespace dap diff --git a/src/jsoncpp_json_serializer.h b/src/jsoncpp_json_serializer.h new file mode 100644 index 0000000..82d97ba --- /dev/null +++ b/src/jsoncpp_json_serializer.h @@ -0,0 +1,134 @@ +// Copyright 2023 Microsoft +// +// 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 +// +// https://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 dap_jsoncpp_json_serializer_h +#define dap_jsoncpp_json_serializer_h + +#include "dap/protocol.h" +#include "dap/serialization.h" +#include "dap/types.h" + +#include + +namespace dap { +namespace json { + +struct JsonCppDeserializer : public dap::Deserializer { + explicit JsonCppDeserializer(const std::string&); + ~JsonCppDeserializer(); + + // dap::Deserializer compliance + bool deserialize(boolean* v) const override; + bool deserialize(integer* v) const override; + bool deserialize(number* v) const override; + bool deserialize(string* v) const override; + bool deserialize(object* v) const override; + bool deserialize(any* v) const override; + size_t count() const override; + bool array(const std::function&) const override; + bool field(const std::string& name, + const std::function&) const override; + + // Unhide base overloads + template + inline bool field(const std::string& name, T* v) { + return dap::Deserializer::field(name, v); + } + + template ::has_custom_serialization>> + inline bool deserialize(T* v) const { + return dap::Deserializer::deserialize(v); + } + + template + inline bool deserialize(dap::array* v) const { + return dap::Deserializer::deserialize(v); + } + + template + inline bool deserialize(dap::optional* v) const { + return dap::Deserializer::deserialize(v); + } + + template + inline bool deserialize(dap::variant* v) const { + return dap::Deserializer::deserialize(v); + } + + template + inline bool field(const std::string& name, T* v) const { + return dap::Deserializer::deserialize(name, v); + } + + private: + JsonCppDeserializer(const Json::Value*); + static Json::Value parse(const std::string& text); + const Json::Value* const json; + const bool ownsJson; +}; + +struct JsonCppSerializer : public dap::Serializer { + JsonCppSerializer(); + ~JsonCppSerializer(); + + std::string dump() const; + + // dap::Serializer compliance + bool serialize(boolean v) override; + bool serialize(integer v) override; + bool serialize(number v) override; + bool serialize(const string& v) override; + bool serialize(const dap::object& v) override; + bool serialize(const any& v) override; + bool array(size_t count, + const std::function&) override; + bool object(const std::function&) override; + void remove() override; + + // Unhide base overloads + template ::has_custom_serialization>> + inline bool serialize(const T& v) { + return dap::Serializer::serialize(v); + } + + template + inline bool serialize(const dap::array& v) { + return dap::Serializer::serialize(v); + } + + template + inline bool serialize(const dap::optional& v) { + return dap::Serializer::serialize(v); + } + + template + inline bool serialize(const dap::variant& v) { + return dap::Serializer::serialize(v); + } + + inline bool serialize(const char* v) { return dap::Serializer::serialize(v); } + + private: + JsonCppSerializer(Json::Value*); + Json::Value* const json; + const bool ownsJson; + bool removed = false; +}; + +} // namespace json +} // namespace dap + +#endif // dap_jsoncpp_json_serializer_h \ No newline at end of file