view C++/modules/Json/Json.h @ 445:f5e62f6c1475

Json: add escape free function
author David Demelier <markand@malikania.fr>
date Mon, 26 Oct 2015 19:29:34 +0100
parents ee155fc84c56
children 902b034df6e3
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 <exception>
#include <initializer_list>
#include <map>
#include <string>
#include <utility>
#include <deque>

/**
 * 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 ptext the text message
	 * @param psource the source (e.g. file name)
	 * @param pline the line number
	 * @param pcolumn the column number
	 * @param pposition 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}
	{
	}

	inline const std::string &text() const noexcept
	{
		return m_text;
	}

	inline const std::string &source() const noexcept
	{
		return m_source;
	}

	inline int line() const noexcept
	{
		return m_line;
	}

	inline int column() const noexcept
	{
		return m_column;
	}

	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 Array;
class Object;

/**
 * @class Value
 * @brief Generic JSON value wrapper.
 */
class Value {
protected:
	/**
	 * Type of value.
	 */
	Type m_type;

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

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

	/**
	 * 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};
	}

	/**
	 * 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)};
	}

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

		switch (m_type) {
		case Type::String:
			new (&m_string) std::string();
			m_string = std::move(other.m_string);
			break;
		case Type::Real:
			m_number = other.m_number;
			break;
		case Type::Int:
			m_integer = other.m_integer;
			break;
		case Type::Boolean:
			m_boolean = other.m_boolean;
			break;
		case Type::Object:
			new (&m_map) std::map<std::string, Value>();
			m_map = std::move(other.m_map);
			break;
		case Type::Array:
			new (&m_array) std::deque<Value>();
			m_array = std::move(other.m_array);
			break;
		default:
			break;
		}
	}

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

		switch (m_type) {
		case Type::String:
			new (&m_string) std::string();
			m_string = other.m_string;
			break;
		case Type::Real:
			m_number = other.m_number;
			break;
		case Type::Int:
			m_integer = other.m_integer;
			break;
		case Type::Boolean:
			m_boolean = other.m_boolean;
			break;
		case Type::Object:
			new (&m_map) std::map<std::string, Value>();
			m_map = other.m_map;
			break;
		case Type::Array:
			new (&m_array) std::deque<Value>();
			m_array = other.m_array;
			break;
		default:
			break;
		}
	}

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

		switch (m_type) {
		case Type::String:
			new (&m_string) std::string();
			m_string = other.m_string;
			break;
		case Type::Real:
			m_number = other.m_number;
			break;
		case Type::Int:
			m_integer = other.m_integer;
			break;
		case Type::Boolean:
			m_boolean = other.m_boolean;
			break;
		case Type::Object:
			new (&m_map) std::map<std::string, Value>();
			m_map = other.m_map;
			break;
		case Type::Array:
			new (&m_array) std::deque<Value>();
			m_array = other.m_array;
			break;
		default:
			break;
		}

		return *this;
	}

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

		switch (m_type) {
		case Type::String:
			new (&m_string) std::string();
			m_string = std::move(other.m_string);
			break;
		case Type::Real:
			m_number = other.m_number;
			break;
		case Type::Int:
			m_integer = other.m_integer;
			break;
		case Type::Boolean:
			m_boolean = other.m_boolean;
			break;
		case Type::Object:
			new (&m_map) std::map<std::string, Value>();
			m_map = std::move(other.m_map);
			break;
		case Type::Array:
			new (&m_array) std::deque<Value>();
			m_array = std::move(other.m_array);
			break;
		default:
			break;
		}

		return *this;
	}

	/**
	 * Destructor.
	 */
	inline ~Value()
	{
		switch (m_type) {
		case Type::String:
			m_string.~basic_string();
			break;
		case Type::Object:
			m_map.~map<std::string, Value>();
			break;
		case Type::Array:
			m_array.~deque<Value>();
			break;
		default:
			break;
		}
	}

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

	/**
	 * Convert the value to object.
	 *
	 * @return an object or empty if not an object
	 * @note the returned object is a copy, modifying it won't modify this value
	 */
	Object toObject() const noexcept;

	/**
	 * Convert the value to array.
	 *
	 * @return an array or empty if not an array
	 * @note the returned array is a copy, modifying it won't modify this value
	 */
	Array toArray() 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;
	}
};

/**
 * @class Array
 * @brief Array definition.
 */
class Array : public Value {
public:
	/**
	 * Construct an empty array.
	 */
	inline Array()
	{
		m_type = Type::Array;
		new (&m_array) std::deque<Value>();
	}

	/**
	 * Copy constructor from value for safe casts.
	 *
	 * @param v the value to copy from
	 */
	inline Array(const Value &v)
		: Value{v}
	{
	}

	/**
	 * Move constructor from value for safe casts.
	 *
	 * @param v the value to move from
	 */
	inline Array(Value &&v)
		: Value{std::move(v)}
	{
	}

	/**
	 * Construct an array from a std::initializer_list.
	 *
	 * @param values the list of values
	 */
	inline Array(std::initializer_list<Value> values)
	{
		m_type = Type::Array;
		new (&m_array) std::deque<Value>(values.begin(), values.end());
	}

	/**
	 * Get non-const iterator to the beginning.
	 *
	 * @return the iterator
	 */
	inline auto begin() noexcept
	{
		return m_array.begin();
	}

	/**
	 * Get a const iterator to the beginning.
	 *
	 * @return the iterator
	 */
	inline auto begin() const noexcept
	{
		return m_array.begin();
	}

	/**
	 * Get a const iterator to the beginning.
	 *
	 * @return the iterator
	 */
	inline auto cbegin() const noexcept
	{
		return m_array.cbegin();
	}

	/**
	 * Get a non-const iterator to the end.
	 *
	 * @return the iterator
	 */
	inline auto end() noexcept
	{
		return m_array.end();
	}

	/**
	 * Get a const iterator to the end.
	 *
	 * @return the iterator
	 */
	inline auto end() const noexcept
	{
		return m_array.end();
	}

	/**
	 * Get a const iterator to the end.
	 *
	 * @return the iterator
	 */
	inline auto cend() const noexcept
	{
		return m_array.cend();
	}

	/**
	 * 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 V>
	inline Value valueOr(unsigned position, V &&defaultValue) const
	{
		if (position >= m_array.size()) {
			return defaultValue;
		}

		return m_array[position];
	}

	/**
	 * Get a value at the specified index.
	 *
	 * @param position the position
	 * @return the value
	 * @throw std::out_of_range if out of bounds
	 */
	inline const Value &at(unsigned position) const
	{
		return m_array.at(position);
	}

	/**
	 * Get a value at the specified index.
	 *
	 * @param position the position
	 * @return the value
	 * @throw std::out_of_range if out of bounds
	 */
	inline Value &at(unsigned position)
	{
		return m_array.at(position);
	}

	/**
	 * Get a value at the specified index.
	 *
	 * @param position the position
	 * @return the value
	 */
	inline const Value &operator[](unsigned position) const
	{
		return m_array[position];
	}

	/**
	 * Overloaded function.
	 *
	 * @param position the position
	 * @return the value
	 */
	inline Value &operator[](unsigned position)
	{
		return m_array[position];
	}

	/**
	 * Push a value to the beginning of the array.
	 *
	 * @param value the value to push
	 */
	template <typename T>
	inline void push(T &&value)
	{
		m_array.push_front(std::forward<T>(value));
	}

	/**
	 * Insert a value at the specified position.
	 *
	 * @param position the position
	 * @param value the value to push
	 */
	template <typename T>
	inline void insert(unsigned position, T &&value)
	{
		m_array.insert(m_array.begin() + position, std::forward<T>(value));
	}

	/**
	 * Add a new value.
	 *
	 * @param value the value to append
	 */
	template <typename T>
	inline void append(T &&value)
	{
		m_array.push_back(std::forward<T>(value));
	}

	/**
	 * Get the array size.
	 *
	 * @return the size
	 */
	inline unsigned size() const noexcept
	{
		return m_array.size();
	}

	/**
	 * Remove all the values.
	 */
	inline void clear()
	{
		m_array.clear();
	}

	/**
	 * Remove a value at the specified position.
	 *
	 * @param position the position
	 */
	inline void erase(unsigned position)
	{
		m_array.erase(m_array.begin() + position);
	}
};

/**
 * @class Object
 * @brief Object definition.
 */
class Object : public Value {
public:
	/**
	 * Construct an empty object.
	 */
	Object()
	{
		m_type = Type::Object;
		new (&m_map) std::map<std::string, Value>();
	}

	/**
	 * Copy constructor from value for safe casts.
	 *
	 * @param v the value to copy from
	 */
	Object(const Value &v)
		: Value{v}
	{
	}

	/**
	 * Move constructor from value for safe casts.
	 *
	 * @param v the value to move from
	 */
	Object(Value &&v)
		: Value{std::move(v)}
	{
	}

	/**
	 * Create an object from the initializer_list of key-value pairs.
	 *
	 * @param values the values
	 */
	Object(std::initializer_list<std::pair<std::string, Value>> values)
	{
		m_type = Type::Object;
		new (&m_map) std::map<std::string, Value>(values.begin(), values.end());
	}

	/**
	 * Get non-const iterator to the beginning.
	 *
	 * @return the iterator
	 */
	inline auto begin() noexcept
	{
		return m_map.begin();
	}

	/**
	 * Get a const iterator to the beginning.
	 *
	 * @return the iterator
	 */
	inline auto begin() const noexcept
	{
		return m_map.begin();
	}

	/**
	 * Get a const iterator to the beginning.
	 *
	 * @return the iterator
	 */
	inline auto cbegin() const noexcept
	{
		return m_map.cbegin();
	}

	/**
	 * Get a non-const iterator to the end.
	 *
	 * @return the iterator
	 */
	inline auto end() noexcept
	{
		return m_map.end();
	}

	/**
	 * Get a const iterator to the end.
	 *
	 * @return the iterator
	 */
	inline auto end() const noexcept
	{
		return m_map.end();
	}

	/**
	 * Get a const iterator to the end.
	 *
	 * @return the iterator
	 */
	inline auto cend() const noexcept
	{
		return m_map.cend();
	}

	/**
	 * 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 V>
	inline Value valueOr(const std::string &name, V &&defaultValue) const
	{
		auto it = m_map.find(name);

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

		return it->second;
	}

	/**
	 * Get a value from the 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
	{
		return m_map.at(name);
	}

	/**
	 * Overloaded function.
	 *
	 * @param name the value key
	 * @return the value
	 * @throw std::out_of_range if not found
	 */
	inline Value &at(const std::string &name)
	{
		return m_map.at(name);
	}

	/**
	 * Get a value from the object.
	 *
	 * @param name the value key
	 * @return the value
	 */
	inline Value &operator[](const std::string &name)
	{
		return m_map.at(name);
	}

	/**
	 * Insert a new value.
	 *
	 * @param name the key
	 * @param value the value
	 */
	inline void insert(std::string name, Value value)
	{
		m_map.insert({std::move(name), std::move(value)});
	}

	/**
	 * Remove all the values from the object.
	 */
	inline void clear()
	{
		m_map.clear();
	}

	/**
	 * Get the number of entries in the object.
	 *
	 * @return the size
	 */
	inline unsigned size() const noexcept
	{
		return m_map.size();
	}

	/**
	 * Check if a value exists.
	 *
	 * @param key the key value
	 * @return true if exists
	 */
	inline bool contains(const std::string &key) const noexcept
	{
		return m_map.find(key) != m_map.end();
	}

	/**
	 * Remove a value of the specified key.
	 *
	 * @param key the value key
	 */
	inline void erase(const std::string &key)
	{
		m_map.erase(key);
	}
};

/**
 * @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 Document
 * @brief Construct a JSON document from a source
 * @see Buffer
 * @see File
 */
class Document {
private:
	Value m_value;

public:
	/**
	 * Construct a document from a buffer.
	 *
	 * @param buffer the text
	 * @throw Error on errors
	 */
	Document(Buffer buffer);

	/**
	 * Construct a document from a file.
	 *
	 * @param file the file
	 * @throw Error on errors
	 */
	Document(File file);

	/**
	 * Check if the opened document is an object.
	 *
	 * @return true if object
	 */
	inline bool isObject() const noexcept
	{
		return m_value.typeOf() == Type::Object;
	}

	/**
	 * Check if the opened document is an array.
	 *
	 * @return true if array
	 */
	inline bool isArray() const noexcept
	{
		return m_value.typeOf() == Type::Array;
	}

	/**
	 * Get the object.
	 *
	 * @return the object or empty object if not an object
	 */
	inline Object toObject() const noexcept
	{
		if (m_value.typeOf() != Type::Object) {
			return Object{};
		}

		return Object(m_value);
	}

	/**
	 * Get the array.
	 *
	 * @return the array or empty object if not an array
	 */
	inline Array toArray() const noexcept
	{
		if (m_value.typeOf() != Type::Array) {
			return Array{};
		}

		return Array(m_value);
	}
};

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

} // !json

#endif // !_JSON_H_