view 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 source

/*
 * Json.h -- jansson C++11 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.
 */

#ifndef _JSON_H_
#define _JSON_H_

#include <initializer_list>
#include <memory>
#include <stdexcept>
#include <string>
#include <utility>
#include <vector>

#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 JsonType
 * @brief Json value type
 */
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 JsonObject;
class JsonArray;

/**
 * @class JsonValue
 * @brief Encapsulate any JSON value
 */
class JsonValue {
public:
	using Handle = std::unique_ptr<json_t, void (*)(json_t *)>;

	friend class JsonObject;
	friend class JsonArray;
	friend class JsonDocument;

protected:
	/**
	 * The unique_ptr handle of json_t, will automatically decrease
	 * the reference count in its deleter.
	 */
	Handle m_handle;

public:
	/**
	 * Create a JsonValue from a native Jansson type. This function
	 * will increment the json_t reference count.
	 *
	 * @param json the value
	 */
	JsonValue(json_t *json);

	/**
	 * Deep copy of that element.
	 *
	 * @param value the other value
	 */
	JsonValue(const JsonValue &value);

	/**
	 * Assign a deep copy of the other element.
	 *
	 * @return *this
	 */
	JsonValue &operator=(const JsonValue &);

	/**
	 * Default move constructor.
	 *
	 * @param other the other value
	 */
	JsonValue(JsonValue &&other) = default;

	/**
	 * Default move assignment.
	 *
	 * @param other the other value
	 */
	JsonValue &operator=(JsonValue &&) = default;

	/**
	 * Create an empty value (JsonType::Null).
	 */
	JsonValue();

	/**
	 * Create a boolean value.
	 *
	 * @param value the value
	 */
	JsonValue(bool value);

	/**
	 * Create a integer value (JsonType::Integer).
	 *
	 * @param value the value
	 */
	JsonValue(int value);

	/**
	 * Create a real value (JsonType::Real).
	 *
	 * @param value the value
	 */
	JsonValue(double value);

	/**
	 * Create a string value (JsonType::String).
	 * @param value
	 */
	JsonValue(std::string value);

	/**
	 * Create from a C string (JsonType::String).
	 *
	 * @param value the string
	 */
	JsonValue(const char *value);

	/**
	 * Create from a string literal (JsonType::String).
	 *
	 * @param value the value
	 */
	template <size_t Size>
	inline JsonValue(char (&value)[Size])
		: m_handle(json_string(value), json_decref)
	{
	}

	/**
	 * Default destructor.
	 */
	virtual ~JsonValue() = default;

	/**
	 * Get the type of value.
	 *
	 * @return the type
	 */
	JsonType typeOf() const;

	/**
	 * Tells if the json value is an JSON_OBJECT.
	 *
	 * @return true or false
	 */
	bool isObject() const;

	/**
	 * Tells if the json value is an JSON_ARRAY.
	 *
	 * @return true or false
	 */
	bool isArray() const;

	/**
	 * Tells if the json value is an JSON_STRING.
	 *
	 * @return true or false
	 */
	bool isString() const;

	/**
	 * Tells if the json value is an JSON_REAL.
	 *
	 * @return true or false
	 */
	bool isReal() const;

	/**
	 * Tells if the json value is an JSON_TRUE.
	 *
	 * @return true or false
	 */
	bool isTrue() const;

	/**
	 * Tells if the json value is an JSON_FALSE.
	 *
	 * @return true or false
	 */
	bool isFalse() const;

	/**
	 * Tells if the json value is an JSON_NULL.
	 *
	 * @return true or false
	 */
	bool isNull() const;

	/**
	 * Tells if the json value is an JSON_INTEGER or JSON_REAL.
	 *
	 * @return true or false
	 */
	bool isNumber() const;

	/**
	 * Tells if the json value is an JSON_INTEGER.
	 *
	 * @return true or false
	 */
	bool isInteger() const;

	/**
	 * Tells if the json value is an JSON_TRUE or JSON_FALSE.
	 *
	 * @return true or false
	 */
	bool isBoolean() const;

	/**
	 * Get the string value.
	 *
	 * @return the string
	 */
	std::string toString() const;

	/**
	 * Get the integer value.
	 *
	 * @return the value or 0
	 */
	int toInteger() const noexcept;

	/**
	 * Get the real value.
	 *
	 * @return the value or 0
	 */
	double toReal() const noexcept;

	/**
	 * Convert to object.
	 *
	 * @return an object
	 */
	JsonObject toObject() const;

	/**
	 * Convert to array.
	 *
	 * @return an array
	 */
	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();
	}

	/**
	 * Overloaded function.
	 *
	 * @return the json_t handle
	 */
	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 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
	 */
	void insert(const JsonValue &value, int index);

	/**
	 * Get the value at the specified index.
	 *
	 * @param index the position
	 * @return the value
	 * @throw std::invalid_argument on error
	 * @throw std::out_of_range on bad arguments
	 */
	JsonValue operator[](int index) const;

	/**
	 * Iterate over the array value (not recursively).
	 *
	 * The function must have the following signature:
	 *    void f(int, const JsonValue &)
	 *
	 * @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
	 */
	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 column.
	 *
	 * @return the column
	 */
	inline int column() const noexcept
	{
		return m_column;
	}

	/**
	 * Get the position.
	 *
	 * @return the position
	 */
	inline int position() const noexcept
	{
		return m_position;
	}
};

/**
 * @class JsonReader
 * @brief Base class for JSON reading
 */
class JsonReader {
public:
	/**
	 * Default constructor.
	 */
	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;

	/**
	 * 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 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
	 */
	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_