Mercurial > code
changeset 311:ed3cc10761e4
Json:
* Split Json class into several classes
* Add unit tests
* Json is considered usable
author | David Demelier <markand@malikania.fr> |
---|---|
date | Fri, 13 Feb 2015 13:42:21 +0100 |
parents | 46e2211fed58 |
children | ea1a73a7d468 |
files | C++/Json.cpp C++/Json.h C++/Tests/Hash/main.cpp C++/Tests/Json/CMakeLists.txt C++/Tests/Json/data/array-all.json C++/Tests/Json/data/array.json C++/Tests/Json/data/object-all.json C++/Tests/Json/data/object.json C++/Tests/Json/data/simple.json C++/Tests/Json/main.cpp CMakeLists.txt cmake/FindJansson.cmake |
diffstat | 12 files changed, 1103 insertions(+), 355 deletions(-) [+] |
line wrap: on
line diff
--- a/C++/Json.cpp Wed Feb 11 19:52:13 2015 +0100 +++ b/C++/Json.cpp Fri Feb 13 13:42:21 2015 +0100 @@ -18,258 +18,245 @@ #include "Json.h" -Json Json::fromString(const std::string &data, int flags) -{ - return from(json_loads, data.c_str(), flags); -} +/* -------------------------------------------------------- + * JsonValue implementation + * -------------------------------------------------------- */ -Json Json::fromFile(const std::string &path, int flags) -{ - return from(json_load_file, path.c_str(), flags); -} - -Json::Json() - : m_handle(json_object(), json_decref) +JsonValue::JsonValue(const JsonValue &value) + : m_handle(json_deep_copy(value.m_handle.get()), json_decref) { } -Json::Json(bool value) +JsonValue &JsonValue::operator=(const JsonValue &value) +{ + m_handle = Handle(json_deep_copy(value.m_handle.get()), json_decref); +} + +JsonValue::JsonValue(json_t *json) + : m_handle(json, json_decref) +{ +} + +JsonValue::JsonValue() + : m_handle(json_null(), json_decref) +{ +} + +JsonValue::JsonValue(bool value) : m_handle(json_boolean(value), json_decref) { } -Json::Json(int value) +JsonValue::JsonValue(int value) : m_handle(json_integer(value), json_decref) { } -Json::Json(double value) +JsonValue::JsonValue(double value) : m_handle(json_real(value), json_decref) { } -Json::Json(const std::string &value) +JsonValue::JsonValue(std::string value) : m_handle(json_string(value.c_str()), json_decref) { } -Json::Json(const Json &json) - : m_handle(json_deep_copy(json.m_handle.get()), json_decref) - , m_list(json.m_list) - , m_map(json.m_map) -{ -} - -Json::Json(Json &&value) - : m_handle(value.m_handle.release(), json_decref) - , m_list(std::move(value.m_list)) - , m_map(std::move(value.m_map)) +JsonValue::JsonValue(const char *value) + : m_handle(json_string(value), json_decref) { } -Json::Json(std::initializer_list<Json> list) - : m_handle(json_array(), json_decref) +JsonType JsonValue::typeOf() const { - m_list.reserve(list.size()); - - for (const auto &v : list) { - m_list.push_back(v); - json_array_append(m_handle.get(), m_list.back().m_handle.get()); - } + return static_cast<JsonType>(json_typeof(m_handle.get())); } -Json &Json::operator=(const Json &json) -{ - m_handle = Handle(json.m_handle.get(), json_decref); - m_list = json.m_list; - m_map = json.m_map; - - return *this; -} - -Json &Json::operator=(Json &&value) -{ - m_handle = Handle(value.m_handle.release(), json_decref); - m_list = std::move(value.m_list); - m_map = std::move(value.m_map); - - return *this; -} - -int Json::typeOf() const -{ - return json_typeof(m_handle.get()); -} - -bool Json::isObject() const +bool JsonValue::isObject() const { return json_is_object(m_handle.get()) != 0; } -bool Json::isArray() const +bool JsonValue::isArray() const { return json_is_array(m_handle.get()) != 0; } -bool Json::isString() const +bool JsonValue::isString() const { return json_is_string(m_handle.get()) != 0; } -bool Json::isReal() const +bool JsonValue::isReal() const { return json_is_real(m_handle.get()) != 0; } -bool Json::isTrue() const -{ - return json_is_true(m_handle.get()) != 0; -} - -bool Json::isFalse() const +bool JsonValue::isTrue() const { return json_is_true(m_handle.get()) != 0; } -bool Json::isNull() const +bool JsonValue::isFalse() const +{ + return json_is_false(m_handle.get()) != 0; +} + +bool JsonValue::isNull() const { return json_is_null(m_handle.get()) != 0; } -bool Json::isNumber() const +bool JsonValue::isNumber() const { return json_is_number(m_handle.get()) != 0; } -bool Json::isInteger() const +bool JsonValue::isInteger() const { return json_is_integer(m_handle.get()) != 0; } -bool Json::isBoolean() const +bool JsonValue::isBoolean() const { return json_is_boolean(m_handle.get()) != 0; } -unsigned Json::size() const noexcept -{ - return m_list.size(); -} - -void Json::append(const Json &value) -{ - m_list.push_back(value); - json_array_append(m_handle.get(), m_list.back().m_handle.get()); -} - -void Json::append(Json &&value) -{ - m_list.push_back(std::move(value)); - json_array_append(m_handle.get(), m_list.back().m_handle.get()); -} - -void Json::insert(const Json &value, int index) -{ - m_list.insert(m_list.begin() + index, value); - json_array_insert(m_handle.get(), index, m_list[index].m_handle.get()); -} - -void Json::insert(Json &&value, int index) -{ - m_list.insert(m_list.begin() + index, std::move(value)); - json_array_insert(m_handle.get(), index, m_list[index].m_handle.get()); -} - -void Json::set(const Json &value, const std::string &name) -{ - m_map[name] = value; - json_object_set(m_handle.get(), name.c_str(), m_map[name].m_handle.get()); -} - -void Json::set(Json &&value, const std::string &name) -{ - m_map[name] = std::move(value); - json_object_set(m_handle.get(), name.c_str(), m_map[name].m_handle.get()); -} - -std::string Json::toString() const +std::string JsonValue::toString() const { auto value = json_string_value(m_handle.get()); return (value == nullptr) ? "" : value; } -int Json::toInteger() const noexcept +int JsonValue::toInteger() const noexcept { return json_integer_value(m_handle.get()); } -double Json::toReal() const noexcept +double JsonValue::toReal() const noexcept { return json_real_value(m_handle.get()); } -std::string Json::dump(int flags) +JsonObject JsonValue::toObject() const { - auto v = json_dumps(m_handle.get(), flags); + json_incref(m_handle.get()); + + return JsonObject(m_handle.get()); +} + +JsonArray JsonValue::toArray() const +{ + json_incref(m_handle.get()); - if (v == nullptr) - throw std::runtime_error("failed to dump"); + return JsonArray(m_handle.get()); +} + +/* -------------------------------------------------------- + * JsonArray + * -------------------------------------------------------- */ - return std::string(v); +JsonArray::JsonArray() + : JsonValue(json_array()) +{ +} + +unsigned JsonArray::size() const noexcept +{ + return json_array_size(m_handle.get()); } -void Json::dump(const std::string &path, int flags) +void JsonArray::push(const JsonValue &value) { - if (json_dump_file(m_handle.get(), path.c_str(), flags) < 0) - throw std::runtime_error("failed to dump"); + json_array_insert(m_handle.get(), 0, value.m_handle.get()); +} + +void JsonArray::append(const JsonValue &value) +{ + json_array_append(m_handle.get(), value.m_handle.get()); } -Json &Json::operator[](int index) +void JsonArray::insert(const JsonValue &value, int index) { - if (!isArray()) - throw std::invalid_argument("not an array"); + json_array_insert(m_handle.get(), index, value.m_handle.get()); +} - if (index >= m_list.size()) - throw std::out_of_range("invalid index"); +JsonValue JsonArray::operator[](int index) const +{ + if (typeOf() != JsonType::Array) + throw JsonError("not an array"); + + auto value = json_array_get(m_handle.get(), index); - return m_list[index]; + if (value == nullptr) + throw JsonError("index out of bounds"); + + json_incref(value); + + return JsonValue(value); } -const Json &Json::operator[](int index) const +/* -------------------------------------------------------- + * JsonObject + * -------------------------------------------------------- */ + +JsonObject::JsonObject() + : JsonValue(json_object()) { - if (!isArray()) - throw std::invalid_argument("not an array"); +} + +JsonValue JsonObject::operator[](const std::string &name) const +{ + if (typeOf() != JsonType::Object) + throw JsonError("not an object"); - if (index >= m_list.size()) - throw std::out_of_range("invalid index"); + auto value = json_object_get(m_handle.get(), name.c_str()); + + if (value == nullptr) + throw JsonError("key " + name + +" not found"); + + json_incref(value); - return m_list[index]; + return JsonValue(value); +} + +void JsonObject::set(const std::string &key, const JsonValue &value) +{ + json_object_set(m_handle.get(), key.c_str(), value.m_handle.get()); } -Json &Json::operator[](const std::string &name) -{ - if (!isObject()) - throw std::invalid_argument("not an object"); +/* -------------------------------------------------------- + * JsonReaderFile + * -------------------------------------------------------- */ - if (m_map.count(name) == 0) - throw std::out_of_range("invalid key"); - - return m_map[name]; +JsonReaderFile::JsonReaderFile(std::string path) + : m_path(std::move(path)) +{ } -const Json &Json::operator[](const std::string &name) const +JsonValue JsonReaderFile::read() { - if (!isObject()) - throw std::invalid_argument("not an object"); + json_error_t error; + json_t *handle = json_load_file(m_path.c_str(), 0, &error); - if (m_map.count(name) == 0) - throw std::out_of_range("invalid key"); + if (handle == nullptr) + throw JsonError{error}; - return m_map.at(name); + return JsonValue(handle); } -bool operator==(const Json &j1, const Json &j2) +/* -------------------------------------------------------- + * JsonWriterFile + * -------------------------------------------------------- */ + +JsonWriterFile::JsonWriterFile(std::string path) + : m_path(std::move(path)) { - return json_equal(j1.m_handle.get(), j2.m_handle.get()) != 0; } + +void JsonWriterFile::write(const JsonValue &value) +{ + if (json_dump_file(value, m_path.c_str(), 0) < 0) + throw JsonError("Failed to write file: " + m_path); +}
--- a/C++/Json.h Wed Feb 11 19:52:13 2015 +0100 +++ b/C++/Json.h Fri Feb 13 13:42:21 2015 +0100 @@ -25,194 +25,159 @@ #include <string> #include <utility> #include <vector> -#include <unordered_map> #include <jansson.h> /** * @file Json.h * @brief A jansson C++ modern wrapper + * + * Because of the Jansson implementation, all these classes are implicitly + * shared. + * + * This means that you can't set any value to an existing value as it would + * change a value which may be used somewhere else, instead you must set + * or replace elements in JsonObject and JsonArray respectively. + * + * However, copy constructors are implemented as deep copy so take care of + * not copying values mistakenly. */ /** - * @class Json - * @brief Json value - * - * This class contains one or more json values. + * @class JsonType + * @brief Json value type */ -class Json final { -public: - using Handle = std::unique_ptr<json_t, void (*)(json_t *)>; - using Array = std::vector<Json>; - using Map = std::unordered_map<std::string, Json>; +enum class JsonType { + Object = JSON_OBJECT, //!< Object + Array = JSON_ARRAY, //!< Array + String = JSON_STRING, //!< String + Integer = JSON_INTEGER, //!< Integer + Real = JSON_REAL, //!< Floating point + True = JSON_TRUE, //!< Boolean true + False = JSON_FALSE, //!< Boolean false + Null = JSON_NULL //!< Empty or null +}; - /** - * @class Error - * @brief Json error - */ - class Error : public std::exception { - private: - friend class Json; - - std::string m_text; - std::string m_source; - int m_line; - int m_column; - unsigned m_position; +class JsonObject; +class JsonArray; - Error(const json_error_t error) - : m_text(error.text) - , m_source(error.source) - , m_line(error.line) - , m_column(error.column) - , m_position(error.position) - { - } - - public: - const char *what() const noexcept override - { - return m_text.c_str(); - } - }; +/** + * @class JsonValue + * @brief Encapsulate any JSON value + */ +class JsonValue { +public: + using Handle = std::unique_ptr<json_t, void (*)(json_t *)>; -private: - Handle m_handle; - Array m_list; - Map m_map; - - Json(Handle &&handle) - : m_handle(std::move(handle)) - { - } + friend class JsonObject; + friend class JsonArray; + friend class JsonDocument; - template <typename Func, typename... Args> - static Json from(Func func, Args&&... args) - { - json_error_t error; - json_t *value = func(std::forward<Args>(args)..., &error); - - if (!value) - throw Error(error); - - return Json(Handle(value, json_decref)); - } +protected: + /** + * The unique_ptr handle of json_t, will automatically decrease + * the reference count in its deleter. + */ + Handle m_handle; public: /** - * Load data from a string. + * Create a JsonValue from a native Jansson type. This function + * will increment the json_t reference count. * - * @param data the data - * @param flags the optional flags - * @return the json value - * @throw Error on failures + * @param json the value */ - static Json fromString(const std::string &data, int flags = 0); + JsonValue(json_t *json); /** - * Load data from a file. + * Deep copy of that element. * - * @param path the path - * @param flags the optional flags - * @return the json value - * @throw Error on failures + * @param value the other value */ - static Json fromFile(const std::string &path, int flags = 0); + JsonValue(const JsonValue &value); /** - * Create a json value of type JSON_OBJECT. + * Assign a deep copy of the other element. + * + * @return *this */ - explicit Json(); + JsonValue &operator=(const JsonValue &); + + /** + * Default move constructor. + * + * @param other the other value + */ + JsonValue(JsonValue &&other) = default; /** - * Create a JSON_NULL object. + * Default move assignment. * - * @param n the null value (nullptr) + * @param other the other value */ - explicit Json(std::nullptr_t n); + JsonValue &operator=(JsonValue &&) = default; /** - * Create a boolean object. - * - * @param value the boolean value + * Create an empty value (JsonType::Null). */ - explicit Json(bool value); + JsonValue(); /** - * Create a JSON_INTEGER object. + * Create a boolean value. * * @param value the value */ - explicit Json(int value); + JsonValue(bool value); /** - * Create a JSON_REAL object. + * Create a integer value (JsonType::Integer). * * @param value the value */ - explicit Json(double value); + JsonValue(int value); /** - * Create a JSON_STRING object. + * Create a real value (JsonType::Real). * * @param value the value */ - explicit Json(const std::string &value); + JsonValue(double value); + + /** + * Create a string value (JsonType::String). + * @param value + */ + JsonValue(std::string value); /** - * Create a JSON_ARRAY object. + * Create from a C string (JsonType::String). * - * @param list the list of children + * @param value the string */ - explicit Json(std::initializer_list<Json> list); + JsonValue(const char *value); /** - * Create a JSON_STRING object. + * Create from a string literal (JsonType::String). * - * @param str the string + * @param value the value */ - template <size_t N> - explicit Json(const char str[N]) - : m_handle(json_string(str), json_decref) + template <size_t Size> + inline JsonValue(char (&value)[Size]) + : m_handle(json_string(value), json_decref) { } /** - * Copy the object, this does a deep copy. - * - * @param json the other value - */ - Json(const Json &json); - - /** - * Copy the object, this does a deep copy. - * - * @param json the other value - * @return *this + * Default destructor. */ - Json &operator=(const Json &json); - - /** - * Move the object. - * - * @param json the other value - */ - Json(Json &&); - - /** - * Move the object. - * - * @param json the other value - * @return *this - */ - Json &operator=(Json &&); + virtual ~JsonValue() = default; /** * Get the type of value. * * @return the type */ - int typeOf() const; + JsonType typeOf() const; /** * Tells if the json value is an JSON_OBJECT. @@ -285,59 +250,6 @@ bool isBoolean() const; /** - * Get the number of values in the array - * - * @return the number or 0 - */ - unsigned size() const noexcept; - - /** - * Insert a copy of the value at the end. - * - * @param value the value to insert - */ - void append(const Json &value); - - /** - * Move the value at the end. - * - * @param value the value to insert - */ - void append(Json &&value); - - /** - * Insert a copy of the value at the specified index. - * - * @param value the value to insert - * @param index the position - */ - void insert(const Json &value, int index); - - /** - * Move the value at the specified index. - * - * @param value the value to insert - * @param index the position - */ - void insert(Json &&value, int index); - - /** - * Set a copy of value to the key 'name'. - * - * @param value the value - * @param name the key name. - */ - void set(const Json &value, const std::string &name); - - /** - * Move value to the key 'name'. - * - * @param value the value - * @param name the key name. - */ - void set(Json &&value, const std::string &name); - - /** * Get the string value. * * @return the string @@ -359,68 +271,367 @@ double toReal() const noexcept; /** - * Dump the value as a string. + * Convert to object. + * + * @return an object + */ + JsonObject toObject() const; + + /** + * Convert to array. * - * @param flags the optional flags - * @return the string + * @return an array */ - std::string dump(int flags = 0); + JsonArray toArray() const; + + /** + * Convert to native Jansson type. + * + * You should not call json_incref or json_decref on it as it is + * automatically done. + * + * @return the json_t handle + * @warning use this function with care + */ + inline operator json_t *() noexcept + { + return m_handle.get(); + } /** - * Dump the value to a path. + * Overloaded function. * - * @param path the path - * @param flags the optional flags + * @return the json_t handle */ - void dump(const std::string &path, int flags = 0); + inline operator const json_t *() const noexcept + { + return m_handle.get(); + } +}; + +/** + * @class JsonArray + * @brief Manipulate JSON arrays + */ +class JsonArray final : public JsonValue { +public: + using JsonValue::JsonValue; + + /** + * Create an empty array. + */ + JsonArray(); /** - * Get the value at the specified index. + * Get the number of values in the array + * + * @return the number or 0 + */ + unsigned size() const noexcept; + + /** + * Insert the value at the beginning. * + * @param value the value + */ + void push(const JsonValue &value); + + /** + * Insert a copy of the value at the end. + * + * @param value the value to insert + */ + void append(const JsonValue &value); + + /** + * Insert a copy of the value at the specified index. + * + * @param value the value to insert * @param index the position - * @return the reference to the value - * @throw std::invalid_argument on error - * @throw std::out_of_range on bad arguments */ - Json &operator[](int index); + void insert(const JsonValue &value, int index); /** * Get the value at the specified index. * * @param index the position - * @return the reference to the value + * @return the value * @throw std::invalid_argument on error * @throw std::out_of_range on bad arguments */ - const Json &operator[](int index) const; + JsonValue operator[](int index) const; /** - * Get the value at the specified key. + * Iterate over the array value (not recursively). + * + * The function must have the following signature: + * void f(int, const JsonValue &) * - * @param name the key - * @return the reference to the value + * @param function the function to call + */ + template <typename Func> + void forAll(Func function) const + { + json_t *value; + size_t index; + + json_array_foreach(m_handle.get(), index, value) { + json_incref(value); + function(static_cast<int>(index), JsonValue(value)); + } + } +}; + +/** + * @class JsonObject + * @brief Object wrapper + */ +class JsonObject final : public JsonValue { +public: + using JsonValue::JsonValue; + + /** + * Create empty object. + */ + JsonObject(); + + /** + * Get the value at the specified key. The returned value is a borrowed reference, + * + * @param key the key + * @return the value * @throw std::invalid_argument on error * @throw std::out_of_range on bad arguments */ - Json &operator[](const std::string &name); + JsonValue operator[](const std::string &key) const; + + /** + * Set the value as key in the object. + * + * @param key the key + * @param value the value + */ + void set(const std::string &key, const JsonValue &value); + + /** + * Iterate over the object keys (not recursively). + * + * The function must have the following signature: + * void f(const std::string &key, const JsonValue &) + * + * @param function the function to call + */ + template <typename Func> + void forAll(Func function) const + { + json_t *value; + const char *key; + + json_object_foreach(m_handle.get(), key, value) { + json_incref(value); + function(std::string(key), JsonValue(value)); + } + } +}; + +/** + * @class JsonError + * @brief Error thrown for any error + */ +class JsonError final : public std::exception { +private: + friend class Json; + + std::string m_text; + std::string m_source; + int m_line{}; + int m_column{}; + unsigned m_position{}; + +public: + /** + * Custom error with no line, no column and no position. + * + * @param error the error message + */ + inline JsonError(std::string error) + : m_text(std::move(error)) + { + } + + /** + * Error from a json_error_t. + * + * @param error the error + */ + inline JsonError(const json_error_t &error) + : m_text(error.text) + , m_source(error.source) + , m_line(error.line) + , m_column(error.column) + , m_position(error.position) + { + } + + /** + * Get the error message. + * + * @return the message + */ + const char *what() const noexcept override + { + return m_text.c_str(); + } + + /** + * Get the text message. + * + * @return the text + */ + inline const std::string &text() const noexcept + { + return m_text; + } + + /** + * Get the source. + * + * @return the source + */ + inline const std::string &source() const noexcept + { + return m_source; + } + + /** + * Get the line. + * + * @return the line + */ + inline int line() const noexcept + { + return m_line; + } /** - * Get the value at the specified key. + * Get the column. + * + * @return the column + */ + inline int column() const noexcept + { + return m_column; + } + + /** + * Get the position. * - * @param name the key - * @return the reference to the value - * @throw std::invalid_argument on error - * @throw std::out_of_range on bad arguments + * @return the position + */ + inline int position() const noexcept + { + return m_position; + } +}; + +/** + * @class JsonReader + * @brief Base class for JSON reading + */ +class JsonReader { +public: + /** + * Default constructor. */ - const Json &operator[](const std::string &name) const; + JsonReader() = default; + + /** + * Default destructor. + */ + virtual ~JsonReader() = default; + + /** + * Read the source and return a value. + * + * @return a value + */ + virtual JsonValue read() = 0; +}; + +/** + * @class JsonWriter + * @brief Base class for writing + */ +class JsonWriter { +public: + /** + * Default destructor. + */ + JsonWriter() = default; /** - * Compare the values. + * Default destructor. + */ + virtual ~JsonWriter() = default; + + /** + * Write to the source the value. + * + * @param value the value + */ + virtual void write(const JsonValue &value) = 0; +}; + +/** + * @class JsonReaderFile + * @brief Read JSON data from a file + */ +class JsonReaderFile final : public JsonReader { +private: + std::string m_path; + +public: + /** + * Construct a JsonReader to a file. This function does not open + * the file immediately. * - * @param j1 the first value - * @param j2 the second value + * @param path the path + */ + JsonReaderFile(std::string path); + + /** + * Read the file and extract a JsonValue. + * + * @return a JsonValue (which will be JsonType::Object or JsonType::Array) + * @throw JsonError on errors */ - friend bool operator==(const Json &j1, const Json &j2); + JsonValue read() override; +}; + +/** + * @class JsonWriterFile + * @brief Write JSON data to file + */ +class JsonWriterFile final : public JsonWriter { +private: + std::string m_path; + +public: + /** + * Construct a JsonReader to a file. This function does not open + * the file immediately. + * + * @param path + */ + JsonWriterFile(std::string path); + + /** + * Write to the file. + * + * @param value the value + * @throw JsonError on errors + */ + void write(const JsonValue &value) override; }; #endif // !_JSON_H_
--- a/C++/Tests/Hash/main.cpp Wed Feb 11 19:52:13 2015 +0100 +++ b/C++/Tests/Hash/main.cpp Fri Feb 13 13:42:21 2015 +0100 @@ -61,4 +61,4 @@ testing::InitGoogleTest(&argc, argv); return RUN_ALL_TESTS(); -} \ No newline at end of file +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/C++/Tests/Json/CMakeLists.txt Fri Feb 13 13:42:21 2015 +0100 @@ -0,0 +1,39 @@ +# +# CMakeLists.txt -- tests for Json +# +# Copyright (c) 2013, 2014 David Demelier <markand@malikania.fr> +# +# Permission to use, copy, modify, and/or distribute this software for any +# purpose with or without fee is hereby granted, provided that the above +# copyright notice and this permission notice appear in all copies. +# +# THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES +# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR +# ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +# WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +# ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF +# OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +# + +project(json) + +find_package(Jansson REQUIRED) + +set( + SOURCES + ${code_SOURCE_DIR}/C++/Json.cpp + ${code_SOURCE_DIR}/C++/Json.h + ${json_SOURCE_DIR}/data/array.json + ${json_SOURCE_DIR}/data/array-all.json + ${json_SOURCE_DIR}/data/object.json + ${json_SOURCE_DIR}/data/object-all.json + ${json_SOURCE_DIR}/data/simple.json + main.cpp +) + +define_test(json "${SOURCES}") + +target_include_directories(json PRIVATE ${Jansson_INCLUDE_DIRS}) +target_link_libraries(json ${Jansson_LIBRARIES}) +target_compile_definitions(json PRIVATE "BINARY=\"${json_BINARY_DIR}\"" "SOURCE=\"${json_SOURCE_DIR}\"")
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/C++/Tests/Json/data/array-all.json Fri Feb 13 13:42:21 2015 +0100 @@ -0,0 +1,9 @@ +[ + 123, + 9.2, + false, + true, + null, + {}, + [] +]
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/C++/Tests/Json/data/array.json Fri Feb 13 13:42:21 2015 +0100 @@ -0,0 +1,1 @@ +[1, 2, 3]
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/C++/Tests/Json/data/object-all.json Fri Feb 13 13:42:21 2015 +0100 @@ -0,0 +1,9 @@ +{ + "integer": 123, + "real": 9.2, + "false": false, + "true": true, + "null": null, + "object": {}, + "array": [] +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/C++/Tests/Json/data/object.json Fri Feb 13 13:42:21 2015 +0100 @@ -0,0 +1,4 @@ +{ + "name": "simple", + "description": "basic JSON file" +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/C++/Tests/Json/data/simple.json Fri Feb 13 13:42:21 2015 +0100 @@ -0,0 +1,2 @@ +{ +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/C++/Tests/Json/main.cpp Fri Feb 13 13:42:21 2015 +0100 @@ -0,0 +1,457 @@ +/* + * main.cpp -- test the jansson wrapper + * + * Copyright (c) 2013, 2014 David Demelier <markand@malikania.fr> + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include <gtest/gtest.h> + +#include "Json.h" + +/* -------------------------------------------------------- + * Miscellaneous + * -------------------------------------------------------- */ + +TEST(Misc, copy) +{ + JsonObject object; + + object.set("integer", 123); + object.set("true", true); + + JsonObject object2(object); + + ASSERT_TRUE(object2.isObject()); + ASSERT_EQ(123, object2["integer"].toInteger()); + ASSERT_TRUE(object2["true"].isTrue()); +} + +TEST(Misc, copyAssign) +{ + JsonObject object; + + { + JsonObject tmp; + + tmp.set("integer", 123); + tmp.set("true", true); + + object = tmp; + } + + ASSERT_TRUE(object.isObject()); + ASSERT_EQ(123, object["integer"].toInteger()); + ASSERT_TRUE(object["true"].isTrue()); +} + +/* -------------------------------------------------------- + * JsonValue constructors + * -------------------------------------------------------- */ + +TEST(Constructors, null) +{ + try { + JsonValue value; + + ASSERT_TRUE(value.isNull()); + } catch (const JsonError &ex) { + FAIL() << ex.what(); + } +} + +TEST(Constructors, boolean) +{ + try { + JsonValue value{true}; + + ASSERT_TRUE(value.isTrue()); + ASSERT_TRUE(value.isBoolean()); + ASSERT_FALSE(value.isFalse()); + } catch (const JsonError &ex) { + FAIL() << ex.what(); + } +} + +TEST(Constructors, integer) +{ + try { + JsonValue value{123}; + + ASSERT_TRUE(value.isInteger()); + ASSERT_TRUE(value.isNumber()); + ASSERT_FALSE(value.isReal()); + ASSERT_EQ(123, value.toInteger()); + } catch (const JsonError &ex) { + FAIL() << ex.what(); + } +} + +TEST(Constructors, real) +{ + try { + JsonValue value{9.2}; + + ASSERT_TRUE(value.isReal()); + ASSERT_TRUE(value.isNumber()); + ASSERT_FALSE(value.isInteger()); + ASSERT_EQ(9.2, value.toReal()); + } catch (const JsonError &ex) { + FAIL() << ex.what(); + } +} + +TEST(Constructors, string) +{ + try { + JsonValue value("hello"); + + ASSERT_TRUE(value.isString()); + ASSERT_EQ("hello", value.toString()); + } catch (const JsonError &ex) { + FAIL() << ex.what(); + } +} + +/* -------------------------------------------------------- + * I/O + * -------------------------------------------------------- */ + +TEST(Open, simple) +{ + try { + JsonReaderFile reader(SOURCE "/data/simple.json"); + + (void)reader.read(); + } catch (const JsonError &ex) { + FAIL() << ex.what(); + } +} + +TEST(Write, simpleArray) +{ + try { + // Write + { + JsonArray array; + + array.append(123); + array.append(true); + + JsonWriterFile(BINARY "/write-array.json").write(array); + } + + // Read + { + JsonArray array = JsonReaderFile(BINARY "/write-array.json").read().toArray(); + + ASSERT_TRUE(array.isArray()); + ASSERT_EQ(123, array[0].toInteger()); + ASSERT_TRUE(array[1].isTrue()); + } + } catch (const JsonError &ex) { + FAIL() << ex.what(); + } +} + +TEST(Write, simpleObject) +{ + try { + // Write + { + JsonObject object; + + object.set("integer", 123); + object.set("true", true); + + JsonWriterFile(BINARY "/write-object.json").write(object); + } + + // Read + { + JsonObject object = JsonReaderFile(BINARY "/write-object.json").read().toObject(); + + ASSERT_TRUE(object.isObject()); + ASSERT_EQ(123, object["integer"].toInteger()); + ASSERT_TRUE(object["true"]); + } + } catch (const JsonError &ex) { + FAIL() << ex.what(); + } +} + +/* -------------------------------------------------------- + * Object + * -------------------------------------------------------- */ + +TEST(Object, simple) +{ + try { + JsonReaderFile reader(SOURCE "/data/object.json"); + JsonValue value = reader.read(); + + ASSERT_TRUE(value.isObject()); + ASSERT_FALSE(value.isArray()); + + JsonObject object = value.toObject(); + + ASSERT_TRUE(object.isObject()); + + JsonValue name = object["name"]; + JsonValue description = object["description"]; + + ASSERT_TRUE(name.isString()); + ASSERT_TRUE(description.isString()); + ASSERT_EQ("simple", name.toString()); + ASSERT_EQ("basic JSON file", description.toString()); + } catch (const JsonError &ex) { + FAIL() << ex.what(); + } +} + +TEST(Object, all) +{ + try { + JsonReaderFile reader(SOURCE "/data/object-all.json"); + JsonValue value = reader.read(); + JsonObject object = value.toObject(); + + ASSERT_TRUE(object["integer"].isInteger()); + ASSERT_TRUE(object["integer"].isNumber()); + ASSERT_EQ(123, object["integer"].toInteger()); + + ASSERT_TRUE(object["real"].isReal()); + ASSERT_TRUE(object["real"].isNumber()); + ASSERT_EQ(9.2, object["real"].toReal()); + + ASSERT_TRUE(object["false"].isBoolean()); + ASSERT_TRUE(object["false"].isFalse()); + ASSERT_FALSE(object["false"].isTrue()); + + ASSERT_TRUE(object["true"].isBoolean()); + ASSERT_TRUE(object["true"].isTrue()); + ASSERT_FALSE(object["true"].isFalse()); + + ASSERT_TRUE(object["null"].isNull()); + + ASSERT_TRUE(object["object"].isObject()); + ASSERT_TRUE(object["array"].isArray()); + } catch (const JsonError &ex) { + FAIL() << ex.what(); + } +} + +TEST(Object, forAll) +{ + try { + JsonReaderFile reader{SOURCE "/data/object-all.json"}; + JsonValue value = reader.read(); + JsonObject object = value.toObject(); + + object.forAll([] (const auto &key, const auto &value) { + if (key == "integer") { + ASSERT_TRUE(value.isInteger()); + ASSERT_EQ(123, value.toInteger()); + } else if (key == "real") { + ASSERT_TRUE(value.isReal()); + ASSERT_EQ(9.2, value.toReal()); + } else if (key == "false") { + ASSERT_TRUE(value.isFalse()); + } else if (key == "true") { + ASSERT_TRUE(value.isTrue()); + } else if (key == "null") { + ASSERT_TRUE(value.isNull()); + } else if (key == "object") { + ASSERT_TRUE(value.isObject()); + } else if (key == "array") { + ASSERT_TRUE(value.isArray()); + } + }); + } catch (const JsonError &ex) { + FAIL() << ex.what(); + } +} + +TEST(Object, set) +{ + try { + JsonObject object; + + object.set("integer", 123); + object.set("string", "hello"); + object.set("true", true); + + ASSERT_EQ(123, object["integer"].toInteger()); + ASSERT_EQ("hello", object["string"].toString()); + ASSERT_TRUE(object["true"].isTrue()); + } catch (const JsonError &ex) { + FAIL() << ex.what(); + } +} + +/* -------------------------------------------------------- + * Array + * -------------------------------------------------------- */ + +TEST(Array, simple) +{ + try { + JsonReaderFile reader{SOURCE "/data/array.json"}; + JsonValue value = reader.read(); + + ASSERT_TRUE(value.isArray()); + ASSERT_FALSE(value.isObject()); + + JsonArray array = value.toArray(); + + ASSERT_TRUE(array.isArray()); + + ASSERT_EQ(3, array.size()); + ASSERT_EQ(1, array[0].toInteger()); + ASSERT_EQ(2, array[1].toInteger()); + ASSERT_EQ(3, array[2].toInteger()); + } catch (const JsonError &ex) { + FAIL() << ex.what(); + } +} + +TEST(Array, all) +{ + try { + JsonReaderFile reader{SOURCE "/data/array-all.json"}; + JsonValue value = reader.read(); + JsonArray array = value.toArray(); + + ASSERT_TRUE(array[0].isInteger()); + ASSERT_TRUE(array[0].isNumber()); + ASSERT_EQ(123, array[0].toInteger()); + + ASSERT_TRUE(array[1].isReal()); + ASSERT_TRUE(array[1].isNumber()); + ASSERT_EQ(9.2, array[1].toReal()); + + ASSERT_TRUE(array[2].isBoolean()); + ASSERT_TRUE(array[2].isFalse()); + ASSERT_FALSE(array[2].isTrue()); + + ASSERT_TRUE(array[3].isBoolean()); + ASSERT_TRUE(array[3].isTrue()); + ASSERT_FALSE(array[3].isFalse()); + + ASSERT_TRUE(array[4].isNull()); + + ASSERT_TRUE(array[5].isObject()); + ASSERT_TRUE(array[6].isArray()); + } catch (const JsonError &ex) { + FAIL() << ex.what(); + } +} + +TEST(Array, forAll) +{ + try { + JsonReaderFile reader{SOURCE "/data/array-all.json"}; + JsonValue value = reader.read(); + JsonArray array = value.toArray(); + + array.forAll([] (auto index, const auto &value) { + if (index == 0) { + ASSERT_TRUE(value.isInteger()); + ASSERT_EQ(123, value.toInteger()); + } else if (index == 1) { + ASSERT_TRUE(value.isReal()); + ASSERT_EQ(9.2, value.toReal()); + } else if (index == 2) { + ASSERT_TRUE(value.isFalse()); + } else if (index == 3) { + ASSERT_TRUE(value.isTrue()); + } else if (index == 4) { + ASSERT_TRUE(value.isNull()); + } else if (index == 5) { + ASSERT_TRUE(value.isObject()); + } else if (index == 6) { + ASSERT_TRUE(value.isArray()); + } + }); + } catch (const JsonError &ex) { + FAIL() << ex.what(); + } +} + +TEST(Array, push) +{ + try { + JsonArray array; + + ASSERT_TRUE(array.isArray()); + + array.push(1); + array.push("hello"); + array.push(true); + + ASSERT_EQ(3, array.size()); + ASSERT_TRUE(array[0].isTrue()); + ASSERT_EQ("hello", array[1].toString()); + ASSERT_EQ(1, array[2].toInteger()); + } catch (const JsonError &ex) { + FAIL() << ex.what(); + } +} + +TEST(Array, append) +{ + try { + JsonArray array; + + ASSERT_TRUE(array.isArray()); + + array.append(1); + array.append("hello"); + array.append(true); + + ASSERT_EQ(3, array.size()); + ASSERT_EQ(1, array[0].toInteger()); + ASSERT_EQ("hello", array[1].toString()); + ASSERT_TRUE(array[2].isTrue()); + } catch (const JsonError &ex) { + FAIL() << ex.what(); + } +} + +TEST(Array, insert) +{ + try { + JsonArray array; + + ASSERT_TRUE(array.isArray()); + + array.insert(1, 0); + array.insert("hello", 1); + array.insert(true, 0); + + ASSERT_EQ(3, array.size()); + ASSERT_TRUE(array[0].isTrue()); + ASSERT_EQ(1, array[1].toInteger()); + ASSERT_EQ("hello", array[2].toString()); + } catch (const JsonError &ex) { + FAIL() << ex.what(); + } +} + +int main(int argc, char **argv) +{ + testing::InitGoogleTest(&argc, argv); + + return RUN_ALL_TESTS(); +}
--- a/CMakeLists.txt Wed Feb 11 19:52:13 2015 +0100 +++ b/CMakeLists.txt Fri Feb 13 13:42:21 2015 +0100 @@ -52,6 +52,7 @@ option(WITH_DYNLIB "Enable DynLib tests" On) option(WITH_FLAGS "Enable Flags tests" On) option(WITH_HASH "Enable hash functions tests" On) +option(WITH_JSON "Enable Jansson wrapper tests" On) option(WITH_OPTIONPARSER "Enable option parser tests" On) option(WITH_PACK "Enable pack functions" On) option(WITH_PARSER "Enable parser tests" On) @@ -84,6 +85,10 @@ add_subdirectory(C++/Tests/Hash) endif () +if (WITH_JSON) + add_subdirectory(C++/Tests/Json) +endif () + if (WITH_OPTIONPARSER) add_subdirectory(C++/Tests/OptionParser) endif ()
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/cmake/FindJansson.cmake Fri Feb 13 13:42:21 2015 +0100 @@ -0,0 +1,24 @@ +# FindJansson +# ----------- +# +# Find Jansson library, this modules defines: +# +# Jansson_INCLUDE_DIRS, where to find jansson.h +# Jansson_LIBRARIES, where to find library +# Jansson_FOUND, if it is found + +find_path(Jansson_INCLUDE_DIR NAMES jansson.h) +find_library(Jansson_LIBRARY NAMES libjansson jansson) + +include(FindPackageHandleStandardArgs) + +find_package_handle_standard_args( + Jansson + FOUND_VAR Jansson_FOUND + REQUIRED_VARS Jansson_LIBRARY Jansson_INCLUDE_DIR +) + +set(Jansson_LIBRARIES ${Jansson_LIBRARY}) +set(Jansson_INCLUDE_DIRS ${Jansson_INCLUDE_DIR}) + +mark_as_advanced(Jansson_INCLUDE_DIR Jansson_LIBRARY)