view modules/json/json.h @ 488:b280d7aca160

Json: add typed valueOr for objects and arrays
author David Demelier <markand@malikania.fr>
date Fri, 13 Nov 2015 19:53:38 +0100
parents d061bad36315
children 4376ec6e5329
line wrap: on
line source

/*
 * json.h -- C++14 JSON manipulation using jansson parser
 *
 * Copyright (c) 2013-2015 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_

/**
 * @file json.h
 * @brief Jansson C++14 wrapper
 *
 * These classes can be used to build or parse JSON documents using jansson library. It is designed to be safe
 * and explicit. It does not implement implicit sharing like jansson so when you access (e.g. Value::toObject) values
 * you get real copies, thus when you read big documents it can has a performance cost.
 */

#include <cassert>
#include <exception>
#include <initializer_list>
#include <map>
#include <string>
#include <utility>
#include <vector>

/**
 * Json namespace.
 */
namespace json {

/**
 * @enum Type
 * @brief Type of Value.
 */
enum class Type {
	Array,		//!< Value is an array []
	Boolean,	//!< Value is boolean
	Int,		//!< Value is integer
	Real,		//!< Value is float
	Object,		//!< Value is object {}
	String,		//!< Value is unicode string
	Null		//!< Value is defined to null
};

/**
 * @class Error
 * @brief Error description.
 */
class Error : public std::exception {
private:
	std::string m_text;
	std::string m_source;
	int m_line;
	int m_column;
	int m_position;

public:
	/**
	 * Create the error.
	 *
	 * @param text the text message
	 * @param source the source (e.g. file name)
	 * @param line the line number
	 * @param column the column number
	 * @param position the position
	 */
	inline Error(std::string text, std::string source, int line, int column, int position) noexcept
		: m_text{std::move(text)}
		, m_source{std::move(source)}
		, m_line{line}
		, m_column{column}
		, m_position{position}
	{
	}

	/**
	 * Get the error message.
	 *
	 * @return the text
	 */
	inline const std::string &text() const noexcept
	{
		return m_text;
	}

	/**
	 * Get the source (e.g. a file name).
	 *
	 * @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;
	}

	/**
	 * Get the error message.
	 *
	 * @return the message
	 */
	const char *what() const noexcept override
	{
		return m_text.c_str();
	}
};

/**
 * @class Buffer
 * @brief Open JSON document from text.
 */
class Buffer {
public:
	std::string text;	//!< The JSON text
};

/**
 * @class File
 * @brief Open JSON document from a file.
 */
class File {
public:
	std::string path;	//!< The path to the file
};

/**
 * @class Value
 * @brief Generic JSON value wrapper.
 */
class Value {
private:
	Type m_type{Type::Null};

	union {
		double m_number;
		bool m_boolean;
		int m_integer;
		std::string m_string;
		std::vector<Value> m_array;
		std::map<std::string, Value> m_object;
	};

	void copy(const Value &);
	void move(Value &&);
	std::string toJson(int indent, int current) const;

	/**
	 * @class BaseIterator
	 * @brief This is the base class for iterator and const_iterator
	 *
	 * This iterator works for both arrays and objects. Because of that purpose, it is only available
	 * as forward iterator.
	 *
	 * When iterator comes from an object, you can use key() otherwise you can use index().
	 */
	template <typename ValueType, typename ArrayIteratorType, typename ObjectIteratorType>
	class BaseIterator : public std::iterator<std::forward_iterator_tag, ValueType> {
	private:
		friend class Value;

		ValueType &m_value;
		ArrayIteratorType m_ita;
		ObjectIteratorType m_itm;

		inline void increment()
		{
			if (m_value.isObject()) {
				m_itm++;
			} else {
				m_ita++;
			}
		}

		BaseIterator(ValueType &value, ObjectIteratorType it)
			: m_value(value)
			, m_itm(it)
		{
		}

		BaseIterator(ValueType &value, ArrayIteratorType it)
			: m_value(value)
			, m_ita(it)
		{
		}

	public:
		/**
		 * Get the iterator key (for objects).
		 *
		 * @pre iterator must be dereferenceable
		 * @pre iterator must come from object
		 * @return the key
		 */
		inline const std::string &key() const noexcept
		{
			assert(m_value.isObject());
			assert(m_itm != m_value.m_object.end());

			return m_itm->first;
		}

		/**
		 * Get the iterator position (for arrays).
		 *
		 * @pre iterator must be dereferenceable
		 * @pre iterator must come from arrays
		 * @return the index
		 */
		inline unsigned index() const noexcept
		{
			assert(m_value.isArray());
			assert(m_ita != m_value.m_array.end());

			return std::distance(m_value.m_array.begin(), m_ita);
		}

		/**
		 * Dereference the iterator.
		 *
		 * @pre iterator be dereferenceable
		 * @return the value
		 */
		inline ValueType &operator*() noexcept
		{
			assert((m_value.isArray()  && m_ita != m_value.m_array.end()) ||
			       (m_value.isObject() && m_itm != m_value.m_object.end()));

			return (m_value.m_type == Type::Object) ? m_itm->second : *m_ita;
		}

		/**
		 * Dereference the iterator as a pointer.
		 *
		 * @pre iterator must be dereferenceable
		 * @return the value
		 */
		inline ValueType *operator->() noexcept
		{
			assert((m_value.isArray()  && m_ita != m_value.m_array.end()) ||
			       (m_value.isObject() && m_itm != m_value.m_object.end()));

			return (m_value.m_type == Type::Object) ? &m_itm->second : &(*m_ita);
		}

		/**
		 * Increment the iterator. (Prefix version).
		 *
		 * @pre iterator must be dereferenceable
		 * @return *this;
		 */
		inline BaseIterator &operator++() noexcept
		{
			assert((m_value.isArray()  && m_ita != m_value.m_array.end()) ||
			       (m_value.isObject() && m_itm != m_value.m_object.end()));

			increment();

			return *this;
		}

		/**
		 * Increment the iterator. (Postfix version).
		 *
		 * @pre iterator must be dereferenceable
		 * @return *this;
		 */
		inline BaseIterator &operator++(int) noexcept
		{
			assert((m_value.isArray()  && m_ita != m_value.m_array.end()) ||
			       (m_value.isObject() && m_itm != m_value.m_object.end()));

			increment();

			return *this;
		}

		/**
		 * Compare two iterators.
		 *
		 * @param it1 the first iterator
		 * @param it2 the second iterator
		 * @return true if they are same
		 */
		bool operator==(const BaseIterator &it) const noexcept
		{
			if (m_value.isObject() && it.m_value.isObject())
				return m_itm == it.m_itm;
			if (m_value.isArray() && it.m_value.isArray())
				return m_ita == it.m_ita;

			return false;
		}

		/**
		 * Test if the iterator is different.
		 *
		 * @param it the iterator
		 * @return true if they are different
		 */
		inline bool operator!=(const BaseIterator &it) const noexcept
		{
			return !(*this == it);
		}
	};

public:
	/**
	 * Forward iterator.
	 */
	using iterator = BaseIterator<Value, typename std::vector<Value>::iterator, typename std::map<std::string, Value>::iterator>;

	/**
	 * Const forward iterator.
	 */
	using const_iterator = BaseIterator<const Value, typename std::vector<Value>::const_iterator, typename std::map<std::string, Value>::const_iterator>;

	/**
	 * Construct a null value.
	 */
	inline Value()
	{
	}

	/**
	 * Create a value with a specified type, this is usually only needed when you want to create an object or
	 * an array.
	 *
	 * For any other types, initialize with sane default value.
	 *
	 * @param type the type
	 */
	Value(Type type);

	/**
	 * Construct a null value.
	 */
	inline Value(std::nullptr_t) noexcept
		: m_type{Type::Null}
	{
	}

	/**
	 * Construct a boolean value.
	 *
	 * @param value the boolean value
	 */
	inline Value(bool value) noexcept
		: m_type{Type::Boolean}
		, m_boolean{value}
	{
	}

	/**
	 * Create value from integer.
	 *
	 * @param value the value
	 */
	inline Value(int value) noexcept
		: m_type{Type::Int}
		, m_integer{value}
	{
	}

	/**
	 * Construct a value from a C-string.
	 *
	 * @param value the C-string
	 */
	inline Value(const char *value)
		: m_type{Type::String}
	{
		new (&m_string) std::string{value ? value : ""};
	}

	/**
	 * Construct a number value.
	 *
	 * @param value the real value
	 */
	inline Value(double value) noexcept
		: m_type{Type::Real}
		, m_number{value}
	{
	}

	/**
	 * Construct a string value.
	 *
	 * @param value the string
	 */
	inline Value(std::string value) noexcept
		: m_type{Type::String}
	{
		new (&m_string) std::string{std::move(value)};
	}

	/**
	 * Create an object from a map.
	 *
	 * @param values the values
	 * @see fromObject
	 */
	inline Value(std::map<std::string, Value> values)
		: Value{Type::Object}
	{
		for (const auto &pair : values) {
			insert(pair.first, pair.second);
		}
	}

	/**
	 * Create an array from a vector.
	 *
	 * @param values the values
	 * @see fromArray
	 */
	inline Value(std::vector<Value> values)
		: Value{Type::Array}
	{
		for (Value value : values) {
			append(std::move(value));
		}
	}

	/**
	 * Construct a value from a buffer.
	 *
	 * @param buffer the text
	 * @throw Error on errors
	 */
	Value(const Buffer &buffer);

	/**
	 * Construct a value from a file.
	 *
	 * @param file the file
	 * @throw Error on errors
	 */
	Value(const File &file);

	/**
	 * Move constructor.
	 *
	 * @param other the value to move from
	 */
	inline Value(Value &&other)
	{
		move(std::move(other));
	}

	/**
	 * Copy constructor.
	 *
	 * @param other the value to copy from
	 */
	inline Value(const Value &other)
	{
		copy(other);
	}

	/**
	 * Copy operator.
	 *
	 * @param other the value to copy from
	 * @return *this
	 */
	inline Value &operator=(const Value &other)
	{
		copy(other);

		return *this;
	}

	/**
	 * Move operator.
	 *
	 * @param other the value to move from
	 */
	inline Value &operator=(Value &&other)
	{
		move(std::move(other));

		return *this;
	}

	/**
	 * Destructor.
	 */
	~Value();

	/**
	 * Get an iterator to the beginning.
	 *
	 * @pre must be an array or object
	 * @return the iterator
	 */
	inline iterator begin() noexcept
	{
		assert(isArray() || isObject());

		return m_type == Type::Object ? iterator(*this, m_object.begin()) : iterator(*this, m_array.begin());
	}

	/**
	 * Overloaded function.
	 *
	 * @pre must be an array or object
	 * @return the iterator
	 */
	inline const_iterator begin() const noexcept
	{
		assert(isArray() || isObject());

		return m_type == Type::Object ? const_iterator(*this, m_object.begin()) : const_iterator(*this, m_array.begin());
	}

	/**
	 * Overloaded function.
	 *
	 * @pre must be an array or object
	 * @return the iterator
	 */
	inline const_iterator cbegin() const noexcept
	{
		assert(isArray() || isObject());

		return m_type == Type::Object ? const_iterator(*this, m_object.cbegin()) : const_iterator(*this, m_array.cbegin());
	}

	/**
	 * Get an iterator to the end.
	 *
	 * @pre must be an array or object
	 * @return the iterator
	 */
	inline iterator end() noexcept
	{
		assert(isArray() || isObject());

		return m_type == Type::Object ? iterator(*this, m_object.end()) : iterator(*this, m_array.end());
	}

	/**
	 * Get an iterator to the end.
	 *
	 * @pre must be an array or object
	 * @return the iterator
	 */
	inline const_iterator end() const noexcept
	{
		assert(isArray() || isObject());

		return m_type == Type::Object ? const_iterator(*this, m_object.end()) : const_iterator(*this, m_array.end());
	}

	/**
	 * Get an iterator to the end.
	 *
	 * @pre must be an array or object
	 * @return the iterator
	 */
	inline const_iterator cend() const noexcept
	{
		assert(isArray() || isObject());

		return m_type == Type::Object ? const_iterator(*this, m_object.cend()) : const_iterator(*this, m_array.cend());
	}

	/**
	 * Get the value type.
	 *
	 * @return the type
	 */
	inline Type typeOf() const noexcept
	{
		return m_type;
	}

	/**
	 * Get the value as boolean.
	 *
	 * @return the value or false if not a boolean
	 */
	bool toBool() const noexcept;

	/**
	 * Get the value as integer.
	 *
	 * @return the value or 0 if not a integer
	 */
	int toInt() const noexcept;

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

	/**
	 * Get the value as string.
	 *
	 * @return the value or empty stirng if not a string
	 */
	std::string toString() const noexcept;

	/**
	 * Check if the value is boolean type.
	 *
	 * @return true if boolean
	 */
	inline bool isBool() const noexcept
	{
		return m_type == Type::Boolean;
	}

	/**
	 * Check if the value is integer type.
	 *
	 * @return true if integer
	 */
	inline bool isInt() const noexcept
	{
		return m_type == Type::Int;
	}

	/**
	 * Check if the value is object type.
	 *
	 * @return true if object
	 */
	inline bool isObject() const noexcept
	{
		return m_type == Type::Object;
	}

	/**
	 * Check if the value is array type.
	 *
	 * @return true if array
	 */
	inline bool isArray() const noexcept
	{
		return m_type == Type::Array;
	}

	/**
	 * Check if the value is integer or real type.
	 *
	 * @return true if integer or real
	 * @see toInt
	 * @see toReal
	 */
	inline bool isNumber() const noexcept
	{
		return m_type == Type::Real || m_type == Type::Int;
	}

	/**
	 * Check if the value is real type.
	 *
	 * @return true if real
	 */
	inline bool isReal() const noexcept
	{
		return m_type == Type::Real;
	}

	/**
	 * Check if the value is null type.
	 *
	 * @return true if null
	 */
	inline bool isNull() const noexcept
	{
		return m_type == Type::Null;
	}

	/**
	 * Check if the value is string type.
	 *
	 * @return true if string
	 */
	inline bool isString() const noexcept
	{
		return m_type == Type::String;
	}

	/**
	 * Get the array or object size.
	 *
	 * @pre must be an array or object
	 * @return the size
	 */
	inline unsigned size() const noexcept
	{
		assert(isArray() || isObject());

		if (m_type == Type::Object) {
			return m_object.size();
		}

		return m_array.size();
	}

	/**
	 * Remove all the values.
	 *
	 * @pre must be an array or an object
	 */
	inline void clear() noexcept
	{
		assert(isArray() || isObject());

		if (m_type == Type::Array) {
			m_array.clear();
		} else {
			m_object.clear();
		}
	}

	/*
	 * Array functions
	 * ----------------------------------------------------------
	 */

	/**
	 * Get the value at the specified position or the defaultValue if position is out of bounds.
	 *
	 * @param position the position
	 * @param defaultValue the value replacement
	 * @return the value or defaultValue
	 */
	template <typename DefaultValue>
	inline Value valueOr(unsigned position, DefaultValue &&defaultValue) const
	{
		if (m_type != Type::Array || position >= m_array.size()) {
			return defaultValue;
		}

		return m_array[position];
	}

	/**
	 * Overloaded function with type check.
	 *
	 * @param position the position
	 * @param type the requested type
	 * @param defaultValue the value replacement
	 * @return the value or defaultValue
	 */
	template <typename DefaultValue>
	inline Value valueOr(unsigned position, Type type, DefaultValue &&defaultValue) const
	{
		if (m_type != Type::Array || position >= m_array.size() || m_array[position].typeOf() != type) {
			return defaultValue;
		}

		return m_array[position];
	}

	/**
	 * Get a value at the specified index.
	 *
	 * @pre must be an array
	 * @param position the position
	 * @return the value
	 * @throw std::out_of_range if out of bounds
	 */
	inline const Value &at(unsigned position) const
	{
		assert(isArray());

		return m_array.at(position);
	}

	/**
	 * Overloaded function.
	 *
	 * @pre must be an array
	 * @param position the position
	 * @return the value
	 * @throw std::out_of_range if out of bounds
	 */
	inline Value &at(unsigned position)
	{
		assert(isArray());

		return m_array.at(position);
	}

	/**
	 * Get a value at the specified index.
	 *
	 * @pre must be an array
	 * @pre position must be valid
	 * @param position the position
	 * @return the value
	 */
	inline const Value &operator[](unsigned position) const
	{
		assert(isArray());
		assert(position < m_array.size());

		return m_array[position];
	}

	/**
	 * Overloaded function.
	 *
	 * @pre must be an array
	 * @pre position must be valid
	 * @param position the position
	 * @return the value
	 */
	inline Value &operator[](unsigned position)
	{
		assert(isArray());
		assert(position < m_array.size());

		return m_array[position];
	}

	/**
	 * Push a value to the beginning of the array.
	 *
	 * @pre must be an array
	 * @param value the value to push
	 */
	inline void push(const Value &value)
	{
		assert(isArray());

		m_array.insert(m_array.begin(), value);
	}

	/**
	 * Overloaded function.
	 *
	 * @pre must be an array
	 * @param value the value to push
	 */
	inline void push(Value &&value)
	{
		assert(isArray());

		m_array.insert(m_array.begin(), std::move(value));
	}

	/**
	 * Insert a value at the specified position.
	 *
	 * @pre must be an array
	 * @pre position must be valid
	 * @param position the position
	 * @param value the value to push
	 */
	inline void insert(unsigned position, const Value &value)
	{
		assert(isArray());
		assert(position <= m_array.size());

		m_array.insert(m_array.begin() + position, value);
	}

	/**
	 * Overloaded function.
	 *
	 * @pre must be an array
	 * @pre position must be valid
	 * @param position the position
	 * @param value the value to push
	 */
	inline void insert(unsigned position, Value &&value)
	{
		assert(isArray());
		assert(position <= m_array.size());

		m_array.insert(m_array.begin() + position, std::move(value));
	}

	/**
	 * Add a new value to the end.
	 *
	 * @pre must be an array
	 * @param value the value to append
	 */
	inline void append(const Value &value)
	{
		assert(isArray());

		m_array.push_back(value);
	}

	/**
	 * Overloaded function.
	 *
	 * @pre must be an array
	 * @param value the value to append
	 */
	inline void append(Value &&value)
	{
		assert(isArray());

		m_array.push_back(std::move(value));
	}

	/**
	 * Remove a value at the specified position.
	 *
	 * @pre must be an array
	 * @pre position must be valid
	 * @param position the position
	 */
	inline void erase(unsigned position)
	{
		assert(isArray());
		assert(position < m_array.size());

		m_array.erase(m_array.begin() + position);
	}

	/*
	 * Object functions
	 * ----------------------------------------------------------
	 */

	/**
	 * Get the value at the specified key or the defaultValue if key is absent.
	 *
	 * @param name the name
	 * @param defaultValue the value replacement
	 * @return the value or defaultValue
	 */
	template <typename DefaultValue>
	Value valueOr(const std::string &name, DefaultValue &&defaultValue) const
	{
		if (m_type != Type::Object) {
			return defaultValue;
		}

		auto it = m_object.find(name);

		if (it == m_object.end()) {
			return defaultValue;
		}

		return it->second;
	}

	/**
	 * Overloaded function with type check.
	 *
	 * @param name the name
	 * @param type the requested type
	 * @param defaultValue the value replacement
	 * @return the value or defaultValue
	 */
	template <typename DefaultValue>
	Value valueOr(const std::string &name, Type type, DefaultValue &&defaultValue) const
	{
		if (m_type != Type::Object) {
			return defaultValue;
		}

		auto it = m_object.find(name);

		if (it == m_object.end() || it->second.typeOf() != type) {
			return defaultValue;
		}

		return it->second;
	}

	/**
	 * Get a value from the object.
	 *
	 * @pre must be an object
	 * @param name the value key
	 * @return the value
	 * @throw std::out_of_range if not found
	 */
	inline const Value &at(const std::string &name) const
	{
		assert(isObject());

		return m_object.at(name);
	}

	/**
	 * Overloaded function.
	 *
	 * @pre must be an object
	 * @param name the value key
	 * @return the value
	 * @throw std::out_of_range if not found
	 */
	inline Value &at(const std::string &name)
	{
		assert(isObject());

		return m_object.at(name);
	}

	/**
	 * Get a value from the object.
	 *
	 * @pre must be an object
	 * @param name the value key
	 * @return the value
	 */
	inline Value &operator[](const std::string &name)
	{
		assert(isObject());

		return m_object[name];
	}

	/**
	 * Find a value by key.
	 *
	 * @pre must be an object
	 * @param key the property key
	 * @return the iterator or past the end if not found
	 */
	inline iterator find(const std::string &key)
	{
		assert(isObject());

		return iterator(*this, m_object.find(key));
	}

	/**
	 * Overloaded function.
	 *
	 * @pre must be an object
	 * @param key the property key
	 * @return the iterator or past the end if not found
	 */
	inline const_iterator find(const std::string &key) const
	{
		assert(isObject());

		return const_iterator(*this, m_object.find(key));
	}

	/**
	 * Insert a new value.
	 *
	 * @pre must be an object
	 * @param name the key
	 * @param value the value
	 */
	inline void insert(std::string name, const Value &value)
	{
		assert(isObject());

		m_object.insert({std::move(name), value});
	}

	/**
	 * Overloaded function.
	 *
	 * @pre must be an object
	 * @param name the key
	 * @param value the value
	 */
	inline void insert(std::string name, Value &&value)
	{
		assert(isObject());

		m_object.insert({std::move(name), std::move(value)});
	}

	/**
	 * Check if a value exists.
	 *
	 * @pre must be an object
	 * @param key the key value
	 * @return true if exists
	 */
	inline bool contains(const std::string &key) const noexcept
	{
		assert(isObject());

		return m_object.find(key) != m_object.end();
	}

	/**
	 * Remove a value of the specified key.
	 *
	 * @pre must be an object
	 * @param key the value key
	 */
	inline void erase(const std::string &key)
	{
		assert(isObject());

		m_object.erase(key);
	}

	/**
	 * Return this value as JSon representation.
	 *
	 * @param indent, the indentation to use (0 == compact, < 0 == tabs, > 0 == number of spaces)
	 * @param tabs, use tabs or not
	 * @return the string
	 */
	inline std::string toJson(int indent = 2) const
	{
		return toJson(indent, 0);
	}
};

/**
 * Escape the input.
 *
 * @param input the input
 * @return the escaped string
 */
std::string escape(const std::string &input);

/**
 * Convenient function for creating array from initializer list.
 *
 * @param values the values
 * @return the array
 */
inline Value array(std::initializer_list<Value> values)
{
	return Value(std::vector<Value>(values.begin(), values.end()));
}

/**
 * Convenient function for creating object from initializer list.
 *
 * @param values the values
 * @return the object
 */
inline Value object(std::initializer_list<std::pair<std::string, Value>> values)
{
	return Value(std::map<std::string, Value>(values.begin(), values.end()));
}

} // !json

#endif // !_JSON_H_