diff C++/Json.h @ 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 777bc3cb665a
children ea1a73a7d468
line wrap: on
line diff
--- 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_