From: Philippe Proulx Date: Fri, 8 Dec 2023 18:32:28 +0000 (+0000) Subject: src/cpp-common: add `bt2c::JsonVal` class and derived classes X-Git-Url: http://drtracing.org/?a=commitdiff_plain;h=27e686d896e77a466b56c43c2a8efb7e6b2bdd43;p=babeltrace.git src/cpp-common: add `bt2c::JsonVal` class and derived classes This patch adds a simple set of classes to contain decoded JSON values. All the new classes inherit the abstract base `JsonVal`. A `JsonVal` instance contains its type (enumeration) as well as a text location (`TextLoc`) which indicates where the JSON value is located within some original JSON text. The complete class hierarchy is: JsonVal JsonNullVal JsonBoolVal JsonSIntVal JsonUIntVal JsonRealVal JsonStrVal JsonCompoundVal (template) ArrayJsonVal ObjJsonVal In reality, `JsonNullVal`, `JsonBoolVal`, `JsonSIntVal`, `JsonUIntVal`, `JsonRealVal`, and `JsonStrVal` are aliases of `JsonScalarVal` with specific template parameters. Each of the classes above has its own inner `UP` alias which is the type of a unique pointer to a specific `const` JSON value. `json-val.hpp` also offers various createJsonVal() functions which rely on their parameter count and types to create specific JSON values. Here's a simple usage example using default text locations: bt2c::JsonArrayVal::Container vals; // Append JSON signed integer value vals.push_back(bt2c::createJsonVal(23LL, bt2c::TextLoc {})); // Append JSON boolean value vals.push_back(bt2c::createJsonVal(true, bt2c::TextLoc {})); // Append JSON null value vals.push_back(bt2c::createJsonVal(bt2c::TextLoc {})); // Append JSON string value vals.push_back(bt2c::createJsonVal("salut la gang", bt2c::TextLoc {})); // Create JSON array value, moving `vals` auto arrayJsonVal = bt2c::createJsonVal(std::move(vals), bt2c::TextLoc {}); // Inspect JSON array value, printing only JSON signed int. values for (auto& val : *arrayJsonVal) { // Type of `val` is `const Json::UP&` if (val->isSInt()) { std::cout << *val->asSInt() << std::endl; } } This is part of an effort to support CTF2‑SPEC‑2.0 [1]. It will be easier to convert CTF 2 metadata stream fragments using JSON value objects than using the listener mode version of bt2c::parseJson() directly. [1]: https://diamon.org/ctf/CTF2-SPEC-2.0.html Signed-off-by: Philippe Proulx Change-Id: I35a431f881da33f516513529d1bec8bb2a905e26 Reviewed-on: https://review.lttng.org/c/babeltrace/+/7466 Reviewed-on: https://review.lttng.org/c/babeltrace/+/12683 --- diff --git a/src/Makefile.am b/src/Makefile.am index 8197f4c5..9f6bcf29 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -168,6 +168,8 @@ cpp_common_libcpp_common_la_SOURCES = \ cpp-common/bt2c/file-utils.hpp \ cpp-common/bt2c/fmt.hpp \ cpp-common/bt2c/glib-up.hpp \ + cpp-common/bt2c/json-val.cpp \ + cpp-common/bt2c/json-val.hpp \ cpp-common/bt2c/libc-up.hpp \ cpp-common/bt2c/logging.hpp \ cpp-common/bt2c/make-span.hpp \ diff --git a/src/cpp-common/bt2c/json-val.cpp b/src/cpp-common/bt2c/json-val.cpp new file mode 100644 index 00000000..4daac672 --- /dev/null +++ b/src/cpp-common/bt2c/json-val.cpp @@ -0,0 +1,157 @@ +/* + * Copyright (c) 2022-2024 Philippe Proulx + * + * SPDX-License-Identifier: MIT + */ + +#include + +#include "common/assert.h" +#include "cpp-common/bt2s/make-unique.hpp" + +#include "json-val.hpp" + +namespace bt2c { + +JsonVal::JsonVal(const Type type, TextLoc&& loc) noexcept : _mType {type}, _mLoc {std::move(loc)} +{ +} + +const JsonNullVal& JsonVal::asNull() const noexcept +{ + BT_ASSERT_DBG(this->isNull()); + return static_cast(*this); +} + +const JsonBoolVal& JsonVal::asBool() const noexcept +{ + BT_ASSERT_DBG(this->isBool()); + return static_cast(*this); +} + +const JsonSIntVal& JsonVal::asSInt() const noexcept +{ + BT_ASSERT_DBG(this->isSInt()); + return static_cast(*this); +} + +const JsonUIntVal& JsonVal::asUInt() const noexcept +{ + BT_ASSERT_DBG(this->isUInt()); + return static_cast(*this); +} + +const JsonRealVal& JsonVal::asReal() const noexcept +{ + BT_ASSERT_DBG(this->isReal()); + return static_cast(*this); +} + +const JsonStrVal& JsonVal::asStr() const noexcept +{ + BT_ASSERT_DBG(this->isStr()); + return static_cast(*this); +} + +const JsonArrayVal& JsonVal::asArray() const noexcept +{ + BT_ASSERT_DBG(this->isArray()); + return static_cast(*this); +} + +const JsonObjVal& JsonVal::asObj() const noexcept +{ + BT_ASSERT_DBG(this->isObj()); + return static_cast(*this); +} + +JsonNullVal::JsonNullVal(TextLoc loc) noexcept : JsonVal {Type::Null, std::move(loc)} +{ +} + +#ifdef BT_DEBUG_MODE + +namespace { + +/* + * Returns `true` if no JSON value unique pointer within `vals` is + * `nullptr`. + * + * `getValueFunc` is a function which accepts a + * `ContainerT::const_reference` and returns a `const` reference of the + * contained JSON value unique pointer. + */ +template +bool valsNotNull(const ContainerT& vals, GetValueFuncT&& getValueFunc) +{ + return std::all_of(vals.begin(), vals.end(), + [&getValueFunc](typename ContainerT::const_reference elem) { + return static_cast(getValueFunc(elem)); + }); +} + +} /* namespace */ + +#endif /* BT_DEBUG_MODE */ + +JsonArrayVal::JsonArrayVal(Container&& vals, TextLoc loc) : + JsonCompoundVal {std::move(vals), std::move(loc)} +{ +#ifdef BT_DEBUG_MODE + BT_ASSERT_DBG(valsNotNull(_mVals, [](Container::const_reference elem) -> const JsonVal::UP& { + return elem; + })); +#endif +} + +JsonObjVal::JsonObjVal(Container&& vals, TextLoc loc) : + JsonCompoundVal {std::move(vals), std::move(loc)} +{ +#ifdef BT_DEBUG_MODE + BT_ASSERT_DBG(valsNotNull(_mVals, [](Container::const_reference elem) -> const JsonVal::UP& { + return elem.second; + })); +#endif +} + +JsonNullVal::UP createJsonVal(TextLoc loc) +{ + return bt2s::make_unique(std::move(loc)); +} + +JsonBoolVal::UP createJsonVal(const bool val, TextLoc loc) +{ + return bt2s::make_unique(val, std::move(loc)); +} + +JsonSIntVal::UP createJsonVal(const long long val, TextLoc loc) +{ + return bt2s::make_unique(val, std::move(loc)); +} + +JsonUIntVal::UP createJsonVal(const unsigned long long val, TextLoc loc) +{ + return bt2s::make_unique(val, std::move(loc)); +} + +JsonRealVal::UP createJsonVal(const double val, TextLoc loc) +{ + return bt2s::make_unique(val, std::move(loc)); +} + +JsonStrVal::UP createJsonVal(std::string val, TextLoc loc) +{ + return bt2s::make_unique(std::move(val), std::move(loc)); +} + +JsonArrayVal::UP createJsonVal(JsonArrayVal::Container&& vals, TextLoc loc) +{ + return bt2s::make_unique(std::move(vals), std::move(loc)); +} + +JsonObjVal::UP createJsonVal(JsonObjVal::Container&& vals, TextLoc loc) +{ + return bt2s::make_unique(std::move(vals), std::move(loc)); +} + +} /* namespace bt2c */ diff --git a/src/cpp-common/bt2c/json-val.hpp b/src/cpp-common/bt2c/json-val.hpp new file mode 100644 index 00000000..7f7f669f --- /dev/null +++ b/src/cpp-common/bt2c/json-val.hpp @@ -0,0 +1,627 @@ +/* + * Copyright (c) 2022-2024 Philippe Proulx + * + * SPDX-License-Identifier: MIT + */ + +#ifndef BABELTRACE_CPP_COMMON_BT2C_JSON_VAL_HPP +#define BABELTRACE_CPP_COMMON_BT2C_JSON_VAL_HPP + +#include +#include +#include +#include +#include +#include + +#include "common/assert.h" + +#include "text-loc.hpp" + +namespace bt2c { + +/* + * Type of JSON value. + */ +enum class JsonValType +{ + Null, + Bool, + SInt, + UInt, + Real, + Str, + Array, + Obj, +}; + +class JsonNullVal; + +template +class JsonScalarVal; + +/* + * JSON boolean value. + */ +using JsonBoolVal = JsonScalarVal; + +/* + * JSON signed integer value. + */ +using JsonSIntVal = JsonScalarVal; + +/* + * JSON unsigned integer value. + */ +using JsonUIntVal = JsonScalarVal; + +/* + * JSON real number value. + */ +using JsonRealVal = JsonScalarVal; + +/* + * JSON string value. + */ +using JsonStrVal = JsonScalarVal; + +class JsonArrayVal; +class JsonObjVal; + +/* + * Abstract base class for any JSON value. + */ +class JsonVal +{ +public: + /* Useful local alias */ + using Type = JsonValType; + + /* Unique pointer to constant JSON value */ + using UP = std::unique_ptr; + +protected: + /* + * Builds a JSON value of type `type` located at `loc`. + */ + explicit JsonVal(Type type, TextLoc&& loc) noexcept; + +public: + /* Deleted copy/move constructors/operators to simplify */ + JsonVal(const JsonVal&) = delete; + JsonVal(JsonVal&&) = delete; + JsonVal& operator=(const JsonVal&) = delete; + JsonVal& operator=(JsonVal&&) = delete; + + /* + * Type of this JSON value. + */ + Type type() const noexcept + { + return _mType; + } + + /* + * Location of this JSON value within some original JSON text. + */ + const TextLoc& loc() const noexcept + { + return _mLoc; + } + + /* + * True if this JSON value is a JSON null value. + */ + bool isNull() const noexcept + { + return _mType == Type::Null; + } + + /* + * True if this JSON value is a JSON boolean value. + */ + bool isBool() const noexcept + { + return _mType == Type::Bool; + } + + /* + * True if this JSON value is a JSON signed integer value. + */ + bool isSInt() const noexcept + { + return _mType == Type::SInt; + } + + /* + * True if this JSON value is a JSON unsigned integer value. + */ + bool isUInt() const noexcept + { + return _mType == Type::UInt; + } + + /* + * True if this JSON value is a JSON real value. + */ + bool isReal() const noexcept + { + return _mType == Type::Real; + } + + /* + * True if this JSON value is a JSON string value. + */ + bool isStr() const noexcept + { + return _mType == Type::Str; + } + + /* + * True if this JSON value is a JSON array value. + */ + bool isArray() const noexcept + { + return _mType == Type::Array; + } + + /* + * True if this JSON value is a JSON object value. + */ + bool isObj() const noexcept + { + return _mType == Type::Obj; + } + + /* + * Returns this JSON value as a JSON null value. + */ + const JsonNullVal& asNull() const noexcept; + + /* + * Returns this JSON value as a JSON boolean value. + */ + const JsonBoolVal& asBool() const noexcept; + + /* + * Returns this JSON value as a JSON signed integer value. + */ + const JsonSIntVal& asSInt() const noexcept; + + /* + * Returns this JSON value as a JSON unsigned integer value. + */ + const JsonUIntVal& asUInt() const noexcept; + + /* + * Returns this JSON value as a JSON real value. + */ + const JsonRealVal& asReal() const noexcept; + + /* + * Returns this JSON value as a JSON string value. + */ + const JsonStrVal& asStr() const noexcept; + + /* + * Returns this JSON value as a JSON array value. + */ + const JsonArrayVal& asArray() const noexcept; + + /* + * Returns this JSON value as a JSON object value. + */ + const JsonObjVal& asObj() const noexcept; + +private: + /* JSON value type */ + Type _mType; + + /* Location of this value within some original JSON text */ + TextLoc _mLoc; +}; + +/* + * JSON null value. + */ +class JsonNullVal : public JsonVal +{ +public: + /* Unique pointer to constant JSON null value */ + using UP = std::unique_ptr; + + /* + * Builds a JSON null value located at `loc`. + */ + explicit JsonNullVal(TextLoc loc) noexcept; +}; + +/* + * JSON scalar value (template for any class which contains a single + * scalar value member of type `ValT`). + */ +template +class JsonScalarVal : public JsonVal +{ +public: + /* Raw value type */ + using Val = ValT; + + /* Unique pointer to constant JSON scalar value */ + using UP = std::unique_ptr>; + + /* + * Builds a JSON scalar value with the raw value `val` and located + * at `loc`. + */ + explicit JsonScalarVal(ValT val, TextLoc loc) noexcept : + JsonVal {TypeV, std::move(loc)}, _mVal {std::move(val)} + { + } + + /* + * Returns the raw value of this JSON value. + */ + const ValT& val() const noexcept + { + return _mVal; + } + + /* + * Returns the raw value of this JSON value. + */ + const ValT& operator*() const noexcept + { + return _mVal; + } + +private: + /* Raw value */ + ValT _mVal; +}; + +/* + * Abstract base class for any JSON compound value class having + * `ContainerT` as the type of its JSON value container. + */ +template +class JsonCompoundVal : public JsonVal +{ +public: + /* JSON value container type */ + using Container = ContainerT; + +protected: + /* + * Builds a JSON compound value of type `TypeV` and located at + * `loc`, moving the JSON values `vals` into this. + */ + explicit JsonCompoundVal(ContainerT&& vals, TextLoc&& loc) : + JsonVal {TypeV, std::move(loc)}, _mVals {std::move(vals)} + { + } + +public: + /* + * Constant beginning iterator of this JSON compound value. + */ + typename ContainerT::const_iterator begin() const noexcept + { + return _mVals.begin(); + } + + /* + * Constant past-the-end iterator of this JSON compound value. + */ + typename ContainerT::const_iterator end() const noexcept + { + return _mVals.end(); + } + + /* + * Size of this JSON compound value. + */ + std::size_t size() const noexcept + { + return _mVals.size(); + } + + /* + * Whether or not this JSON compound value is empty. + */ + bool isEmpty() const noexcept + { + return _mVals.empty(); + } + +protected: + /* Container of JSON values */ + ContainerT _mVals; +}; + +/* + * JSON array value. + */ +class JsonArrayVal : public JsonCompoundVal, JsonValType::Array> +{ +public: + /* Unique pointer to constant JSON array value */ + using UP = std::unique_ptr; + + /* + * Builds a JSON array value located at `loc`, moving the JSON + * values `vals` into this. + */ + explicit JsonArrayVal(Container&& vals, TextLoc loc); + + /* + * Returns the JSON value at index `index` within this JSON array + * value. + */ + const JsonVal& operator[](const std::size_t index) const noexcept + { + BT_ASSERT_DBG(index < this->_mVals.size()); + return *_mVals[index]; + } +}; + +/* + * JSON object value. + */ +class JsonObjVal : + public JsonCompoundVal, JsonValType::Obj> +{ +public: + /* Unique pointer to constant JSON object value */ + using UP = std::unique_ptr; + + /* + * Builds a JSON object value located at `loc`, moving the JSON + * values `vals` into this. + */ + explicit JsonObjVal(Container&& vals, TextLoc loc); + + /* + * Returns the JSON value named `key` within this JSON object + * value, or `nullptr` if not found. + */ + const JsonVal *operator[](const std::string& key) const noexcept + { + const auto it = _mVals.find(key); + + if (it == _mVals.end()) { + return nullptr; + } + + return it->second.get(); + } + + /* + * Returns the JSON value having the key `key`, known to exist, as + * a `JsonValT` reference. + */ + template + const JsonValT& val(const std::string& key) const noexcept + { + const auto val = (*this)[key]; + + BT_ASSERT(val); + return static_cast(*val); + } + + /* + * Returns the raw value of the JSON boolean value, known to exist, + * having the key `key`. + */ + bool rawBoolVal(const std::string& key) const noexcept + { + return *this->val(key); + } + + /* + * Returns the raw value of the JSON unsigned integer value, known + * to exist, having the key `key`. + */ + unsigned long long rawUIntVal(const std::string& key) const noexcept + { + return *this->val(key); + } + + /* + * Returns the raw value of the JSON signed integer value, known to + * exist, having the key `key`. + */ + long long rawSIntVal(const std::string& key) const noexcept + { + return *this->val(key); + } + + /* + * Returns the raw value of the JSON real value, known to exist, + * having the key `key`. + */ + double rawRealVal(const std::string& key) const noexcept + { + return *this->val(key); + } + + /* + * Returns the raw value of the JSON string value, known to exist, + * having the key `key`. + */ + const std::string& rawStrVal(const std::string& key) const noexcept + { + return *this->val(key); + } + + /* + * Returns: + * + * If a JSON value having the key `key` exists: + * The JSON value having the key `key` as a `JsonValT` + * reference. + * + * Otherwise: + * `defJsonVal` + */ + template + const JsonValT& val(const std::string& key, const JsonValT& defJsonVal) const noexcept + { + const auto jsonVal = (*this)[key]; + + return jsonVal ? static_cast(*jsonVal) : defJsonVal; + } + + /* + * Returns: + * + * If a JSON value having the key `key` exists: + * The raw value of the JSON value having the key `key`. + * + * Otherwise: + * `defVal` + */ + template + typename JsonValT::Val rawVal(const std::string& key, + const typename JsonValT::Val defVal) const noexcept + { + const auto jsonVal = (*this)[key]; + + return jsonVal ? *static_cast(*jsonVal) : defVal; + } + + /* + * Returns: + * + * If a JSON value having the key `key` exists: + * The raw value of the JSON boolean value having the key `key`. + * + * Otherwise: + * `defVal` + */ + bool rawVal(const std::string& key, const bool defVal) const noexcept + { + return this->rawVal(key, defVal); + } + + /* + * Returns: + * + * If a JSON value having the key `key` exists: + * The raw value of the JSON unsigned integer value having the + * key `key`. + * + * Otherwise: + * `defVal` + */ + unsigned long long rawVal(const std::string& key, + const unsigned long long defVal) const noexcept + { + return this->rawVal(key, defVal); + } + + /* + * Returns: + * + * If a JSON value having the key `key` exists: + * The raw value of the JSON signed integer value having the key + * `key`. + * + * Otherwise: + * `defVal` + */ + long long rawVal(const std::string& key, const long long defVal) const noexcept + { + return this->rawVal(key, defVal); + } + + /* + * Returns: + * + * If a JSON value having the key `key` exists: + * The raw value of the JSON real value having the key `key`. + * + * Otherwise: + * `defVal` + */ + double rawVal(const std::string& key, const double defVal) const noexcept + { + return this->rawVal(key, defVal); + } + + /* + * Returns: + * + * If a JSON value having the key `key` exists: + * The raw value of the JSON string value having the key `key`. + * + * Otherwise: + * `defVal` + */ + const char *rawVal(const std::string& key, const char * const defVal) const noexcept + { + const auto jsonVal = (*this)[key]; + + return jsonVal ? (*jsonVal->asStr()).c_str() : defVal; + } + + /* + * Returns whether or not this JSON object value contains a value + * named `key`. + */ + bool hasValue(const std::string& key) const noexcept + { + return _mVals.find(key) != _mVals.end(); + } +}; + +/* + * Creates and returns a JSON null value located at `loc`. + */ +JsonNullVal::UP createJsonVal(TextLoc loc); + +/* + * Creates and returns a JSON boolean value having the raw value `val` + * located at `loc`. + */ +JsonBoolVal::UP createJsonVal(bool val, TextLoc loc); + +/* + * Creates and returns a JSON signed integer value having the raw value + * `val` located at `loc`. + */ +JsonSIntVal::UP createJsonVal(long long val, TextLoc loc); + +/* + * Creates and returns a JSON unsigned integer value having the raw + * value `val` located at `loc`. + */ +JsonUIntVal::UP createJsonVal(unsigned long long val, TextLoc loc); + +/* + * Creates and returns a JSON real number value having the raw value + * `val` located at `loc`. + */ +JsonRealVal::UP createJsonVal(double val, TextLoc loc); + +/* + * Creates and returns a JSON string value having the raw value `val` + * located at `loc`. + */ +JsonStrVal::UP createJsonVal(std::string val, TextLoc loc); + +/* + * Creates and returns a JSON array value located at `loc`, moving the + * JSON values `vals`. + */ +JsonArrayVal::UP createJsonVal(JsonArrayVal::Container&& vals, TextLoc loc); + +/* + * Creates and returns a JSON object value located at `loc`, moving the + * JSON values `vals`. + */ +JsonObjVal::UP createJsonVal(JsonObjVal::Container&& vals, TextLoc loc); + +} /* namespace bt2c */ + +#endif /* BABELTRACE_CPP_COMMON_BT2C_JSON_VAL_HPP */