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)