///////////////////////////////////////////////////////////////////////////////
//
// Copyright (C) 2008-2012 Artyom Beilis (Tonkikh) <artyomtnk@yahoo.com>
//
// See accompanying file COPYING.TXT file for licensing details.
//
///////////////////////////////////////////////////////////////////////////////
#ifndef CPPCMS_JSON_H
#define CPPCMS_JSON_H
#include <cppcms/defs.h>
#include <cppcms/string_key.h>
#include <booster/copy_ptr.h>
#include <booster/backtrace.h>
#include <vector>
#include <map>
#include <string>
#include <iostream>
#include <limits>
namespace cppcms {
///
/// \brief This namespace includes all JSON parsing and formatting related classes and functions
///
namespace json {
class value;
///
/// \brief Special object that is convertible to null json value
///
struct null {};
///
/// \brief Special object that is convertible to undefined json value
///
struct undefined {};
inline bool operator==(undefined const &/*l*/,undefined const &/*r*/) {return true;}
inline bool operator!=(undefined const &/*l*/,undefined const &/*r*/) {return false;}
inline bool operator==(null const &/*l*/,null const &/*r*/) {return true;}
inline bool operator!=(null const &/*l*/,null const &/*r*/) {return false;}
///
/// \brief The json::array - std::vector of json::value's
///
typedef std::vector<value> array;
///
/// \brief The json::object - std::map of json::value's
///
typedef std::map<string_key,value> object;
#ifdef CPPCMS_DOXYGEN_DOCS
///
/// \brief The type traits schema for converting json values to/from orinary objects
/// i.e. serialization from JSON to C++ object
///
template<typename T>
struct traits {
///
/// Convert json value \a v to an object of type T and return it
///
static T get(value const &v);
///
/// Convert an object \a in to a json::value \a v
///
static void set(value &v,T const &in);
};
#else
template<typename T>
struct traits;
#endif
///
/// The type of json value
///
typedef enum {
is_undefined, ///< Undefined value
is_null, ///< null value
is_boolean, ///< boolean value
is_number, ///< numeric value
is_string, ///< string value
is_object, ///< object value
is_array ///< array value
} json_type;
enum {
compact = 0, ///< Print JSON values in most compact format
readable = 1 ///< Print JSON values in human readable format (with identention)
};
///
/// \brief The error that is thrown in case of bad conversion of json::value to ordinary value
///
/// When implementing json::traits for complex classes you are expected to throw this exception
/// in case of invalid formatting
///
class CPPCMS_API bad_value_cast : public booster::bad_cast {
public:
bad_value_cast();
bad_value_cast(std::string const &s);
bad_value_cast(std::string const &s,json_type actual);
bad_value_cast(std::string const &s,json_type expected, json_type actual);
virtual ~bad_value_cast() throw();
virtual const char* what() const throw();
private:
std::string msg_;
};
class value;
///
/// Read json object from input stream
///
std::istream CPPCMS_API &operator>>(std::istream &in,value &v);
///
/// Write json object to output stream
///
std::ostream CPPCMS_API &operator<<(std::ostream &out,value const &v);
///
/// Write human readable representation of json_type
///
std::ostream CPPCMS_API &operator<<(std::ostream &out,json_type);
///
/// \brief This class is central representation of json objects.
///
/// It can be a value from any type
/// including scalar, object, array and undefined
///
class CPPCMS_API value {
public:
///
/// Get the type of the value
///
json_type type() const;
///
/// Returns true if type()==json::is_undefined
///
bool is_undefined() const;
///
/// Returns true if type()==json::is_null
///
bool is_null() const;
///
/// Convert value to boolean, throws bad_value_cast if value's type is not boolean
///
bool const &boolean() const;
///
/// Convert value to double, throws bad_value_cast if value's type is not number
///
double const &number() const;
///
/// Convert value to strng, throws bad_value_cast if value's type is not string
///
std::string const &str() const;
///
/// Convert value to json::object, throws bad_value_cast if value's type is not object
///
json::object const &object() const;
///
/// Convert value to json::array, throws bad_value_cast if value's type is not array
///
json::array const &array() const;
///
/// Get reference to bool variable that represents this value, throws bad_value_cast if type is invalid
///
bool &boolean();
///
/// Get reference to double variable that represents this value, throws bad_value_cast if type is invalid
///
double &number();
///
/// Get reference to string variable that represents this value, throws bad_value_cast if type is invalid
///
std::string &str();
///
/// Get reference to object variable that represents this value, throws bad_value_cast if type is invalid
///
json::object &object();
///
/// Get reference to array variable that represents this value, throws bad_value_cast if type is invalid
///
json::array &array();
///
/// Set value to undefined type
///
void undefined();
///
/// Set value to null type
///
void null();
///
/// Set value to boolean type and assign it
///
void boolean(bool);
///
/// Set value to numeric type and assign it
///
void number(double );
///
/// Set value to string type and assign it
///
void str(std::string const &);
///
/// Set value to object type and assign it
///
void object(json::object const &);
///
/// Set value to array type and assign it
///
void array(json::array const &);
///
/// Convert the value to type T, using json::traits, throws bad_value_cast if conversion is not possible
///
template<typename T>
T get_value() const
{
return traits<T>::get(*this);
}
///
/// Convert the object \a v of type T to the value
///
template<typename T>
void set_value(T const &v)
{
traits<T>::set(*this,v);
}
///
/// Searches a value in the path \a path
///
/// For example if the json::value represents { "x" : { "y" : 10 } }, then find("x.y") would return
/// a reference to value that hold a number 10, find("x") returns a reference to a value
/// that holds an object { "y" : 10 } and find("foo") would return value of undefined type.
///
value const &find(std::string const &path) const;
///
/// Searches a value in the path \a path
///
/// For example if the json::value represents { "x" : { "y" : 10 } }, then find("x.y") would return
/// a reference to value that hold a number 10, find("x") returns a reference to a value
/// that holds an object { "y" : 10 } and find("foo") would return value of undefined type.
///
value const &find(char const *path) const;
///
/// Searches a value in the path \a path, if not found throw bad_value_cast.
///
/// For example if the json::value represents { "x" : { "y" : 10 } }, then find("x.y") would return
/// a reference to value that hold a number 10, find("x") returns a reference to a value
/// that holds an object { "y" : 10 } and find("foo") throws
///
value const &at(std::string const &path) const;
///
/// Searches a value in the path \a path, if not found throw bad_value_cast.
///
/// For example if the json::value represents { "x" : { "y" : 10 } }, then find("x.y") would return
/// a reference to value that hold a number 10, find("x") returns a reference to a value
/// that holds an object { "y" : 10 } and find("foo") throws
///
value const &at(char const *path) const;
///
/// Searches a value in the path \a path, if not found throw bad_value_cast.
///
/// For example if the json::value represents { "x" : { "y" : 10 } }, then find("x.y") would return
/// a reference to value that hold a number 10, find("x") returns a reference to a value
/// that holds an object { "y" : 10 } and find("foo") throws
///
value &at(std::string const &path);
///
/// Searches a value in the path \a path, if not found throw bad_value_cast.
///
/// For example if the json::value represents { "x" : { "y" : 10 } }, then find("x.y") would return
/// a reference to value that hold a number 10, find("x") returns a reference to a value
/// that holds an object { "y" : 10 } and find("foo") throws
///
value &at(char const *path);
///
/// Sets the value \a v at the path \a path, if the path invalid, creates it.
///
void at(std::string const &path,value const &v);
///
/// Sets the value \a v at the path \a path, if the path invalid, creates it.
///
void at(char const *path,value const &v);
///
/// Creates a value from and object \a v of type T
///
template<typename T>
value(T const &v)
{
set_value(v);
}
///
/// Returns the type of variable in path, if not found returns undefined
///
/// Same as find(path).type()
///
json_type type(std::string const &path) const
{
return find(path).type();
}
///
/// Returns the type of variable in path, if not found returns undefined
///
/// Same as find(path).type()
///
json_type type(char const *path) const
{
return find(path).type();
}
///
/// Convert an object \a v of type T to a value at specific path, same as at(path,value(v))
///
template<typename T>
void set(std::string const &path,T const &v)
{
at(path,value(v));
}
///
/// Convert an object \a v of type T to a value at specific path, same as at(path,value(v))
///
template<typename T>
void set(char const *path,T const &v)
{
at(path,value(v));
}
///
/// Get a string value from a path \a path. If the path is not invalid or the object
/// is not of type string at this path, returns \a def instead
///
std::string get(std::string const &path,char const *def) const
{
value const &v=find(path);
if(v.is_undefined())
return def;
try {
return v.get_value<std::string>();
}
catch(std::bad_cast const &e) {
return def;
}
}
///
/// Get a string value from a path \a path. If the path is not invalid or the object
/// is not of type string at this path, returns \a def instead
///
std::string get(char const *path,char const *def) const
{
value const &v=find(path);
if(v.is_undefined())
return def;
try {
return v.get_value<std::string>();
}
catch(std::bad_cast const &e) {
return def;
}
}
///
/// Get an object of type T from the path \a path. Throws bad_value_cast if such path does not
/// exists of conversion can't be done
///
template<typename T>
T get(std::string const &path) const
{
return at(path).get_value<T>();
}
///
/// Get an object of type T from the path \a path. Throws bad_value_cast if such path does not
/// exists of conversion can't be done
///
template<typename T>
T get(char const *path) const
{
return at(path).get_value<T>();
}
///
/// Get an object of type T from the path \a path. Returns \a def if such path does not
/// exists of conversion can't be done
///
template<typename T>
T get(char const *path,T const &def) const
{
value const &v=find(path);
if(v.is_undefined())
return def;
try {
return v.get_value<T>();
}
catch(std::bad_cast const &e) {
return def;
}
}
///
/// Get an object of type T from the path \a path. Returns \a def if such path does not
/// exists of conversion can't be done
///
template<typename T>
T get(std::string const &path,T const &def) const
{
value const &v=find(path);
if(v.is_undefined())
return def;
try {
return v.get_value<T>();
}
catch(std::bad_cast const &e) {
return def;
}
}
///
/// Returns a reference to the node \a name of the value.
/// For value = {"x",10} and name == "x" return a value that holds 10.
///
/// If value is not object it's type set to object.
/// If such node does not exits, it is created with undefined value
///
value &operator[](std::string const &name);
///
/// Returns reference to the node \a name of the value.
/// For value = {"x",10} and name == "x" return a value that holds 10.
///
/// If value is not object or such node does not exits, throws bad_value_cast
///
value const &operator[](std::string const &name) const;
///
/// Returns a reference to \a n 'th entry of the array. If the value is not an array it is reset to array,
/// of the array is too small it is resized to size of at least n+1
///
value &operator[](size_t n);
///
/// Returns a reference to \a n 'th entry of array, if the value is not array or n is too big, throws
/// bad_value_cast
///
value const &operator[](size_t n) const;
///
/// Convert a value to std::string, if \a how has value \a readable it is converted with indentation
///
std::string save(int how=compact) const;
///
/// Write a value to std::ostream, if \a how has value \a readable it is converted with indentation
///
void save(std::ostream &out,int how=compact) const;
///
/// Read a value from std::istream.
///
/// Note: only JSON object and JSON array are considered valid values
///
/// \param in the std::istream used to read the data
/// \param full require EOF once the object is read, otherwise consider it as syntax error
/// \param line_number return a number of the line where syntax error occurred
/// \result returns true if the value was read successfully, otherwise returns false to indicate a syntax error.
///
bool load(std::istream &in,bool full,int *line_number=0);
///
/// Read a value from character range
///
/// Note: only JSON object and JSON array are considered valid values
///
/// \param begin - begin of the text range, at the end points to the end of parsed range
/// \param end - end of the text range
/// \param full require EOF once the object is read, otherwise consider it as syntax error
/// \param line_number return a number of the line where syntax error occurred
/// \result returns true if the value was read successfully, otherwise returns false to indicate a syntax error.
///
/// \ver{v1_2}
bool load(char const *&begin,char const *end,bool full,int *line_number=0);
///
/// Compare two values objects, return true if they are same
///
bool operator==(value const &other) const;
///
/// Compare two values objects, return false if they are same
///
bool operator!=(value const &other) const;
///
/// Copy constructor
///
value(value const &other) :
d(other.d)
{
}
///
/// Assignment operator
///
value const &operator=(value const &other)
{
d=other.d;
return *this;
}
///
/// Default value - creates a value of undefined type
///
value()
{
}
///
/// Destructor
///
~value()
{
}
///
/// Swaps two values, does not throw.
///
void swap(value &other)
{
d.swap(other.d);
}
private:
void write(std::ostream &out,int tabs) const;
void write_value(std::ostream &out,int tabs) const;
struct _data;
struct CPPCMS_API copyable {
_data *operator->() { return &*d; }
_data &operator*() { return *d; }
_data const *operator->() const { return &*d; }
_data const &operator*() const { return *d; }
copyable();
copyable(copyable const &r);
copyable const &operator=(copyable const &r);
~copyable();
void swap(copyable &other)
{
d.swap(other.d);
}
private:
booster::copy_ptr<_data> d;
} d;
friend struct copyable;
};
///
/// Convert UTF-8 string to JSON string, i.e. a sring foo is converted to "foo",
/// a string bar"baz is converted to "bar\"baz"
///
/// \ver{v1_2}
std::string CPPCMS_API to_json(std::string const &utf);
///
/// Convert UTF-8 string to JSON string, i.e. a sring foo is converted to "foo",
/// a string bar"baz is converted to "bar\"baz"
///
/// \ver{v1_2}
std::string CPPCMS_API to_json(char const *begin,char const *end);
///
/// Convert UTF-8 string to JSON string, i.e. a sring foo is converted to "foo",
/// a string bar"baz is converted to "bar\"baz"
///
/// \ver{v1_2}
void CPPCMS_API to_json(char const *begin,char const *end,std::ostream &out);
//
/// Convert UTF-8 string to JSON string, i.e. a sring foo is converted to "foo",
/// a string bar"baz is converted to "bar\"baz"
///
/// \ver{v1_2}
void CPPCMS_API to_json(std::string const &str,std::ostream &out);
/// \cond INTERNAL
template<typename T1,typename T2>
struct traits<std::pair<T1,T2> > {
static std::pair<T1,T2> get(value const &v)
{
if(v.object().size()!=2)
throw bad_value_cast("Object with two members expected");
std::pair<T1,T2> pair(v.get_value<T1>("first"),v.get_value<T2>("second"));
return pair;
}
static void set(value &v,std::pair<T1,T2> const &in)
{
v=json::object();
v.set_value("first",in.first);
v.set_value("second",in.second);
}
};
template<typename T>
struct traits<std::vector<T> > {
static std::vector<T> get(value const &v)
{
std::vector<T> result;
json::array const &a=v.array();
result.resize(a.size());
for(unsigned i=0;i<a.size();i++)
result[i]=a[i].get_value<T>();
return result;
}
static void set(value &v,std::vector<T> const &in)
{
v=json::array();
json::array &a=v.array();
a.resize(in.size());
for(unsigned i=0;i<in.size();i++)
a[i].set_value(in[i]);
}
};
#define CPPCMS_JSON_SPECIALIZE(type,method) \
template<> \
struct traits<type> { \
static type get(value const &v) \
{ \
return v.method(); \
} \
static void set(value &v,type const &in)\
{ \
v.method(in); \
} \
};
CPPCMS_JSON_SPECIALIZE(bool,boolean);
CPPCMS_JSON_SPECIALIZE(double,number);
CPPCMS_JSON_SPECIALIZE(std::string,str);
CPPCMS_JSON_SPECIALIZE(json::object,object);
CPPCMS_JSON_SPECIALIZE(json::array,array);
#undef CPPCMS_JSON_SPECIALIZE
#define CPPCMS_JSON_SPECIALIZE_INT(type) \
template<> \
struct traits<type> { \
static type get(value const &v) \
{ \
type res=static_cast<type>(v.number()); \
if(res!=v.number()) \
throw bad_value_cast(); \
return res; \
} \
static void set(value &v,type const &in) \
{ \
if(std::numeric_limits<type>::digits > \
std::numeric_limits<double>::digits \
&& static_cast<double>(in)!=in) \
{ \
throw bad_value_cast(); \
} \
v.number(static_cast<double>(in)); \
} \
};
CPPCMS_JSON_SPECIALIZE_INT(char)
CPPCMS_JSON_SPECIALIZE_INT(unsigned char)
CPPCMS_JSON_SPECIALIZE_INT(signed char)
CPPCMS_JSON_SPECIALIZE_INT(wchar_t)
CPPCMS_JSON_SPECIALIZE_INT(short)
CPPCMS_JSON_SPECIALIZE_INT(unsigned short)
CPPCMS_JSON_SPECIALIZE_INT(int)
CPPCMS_JSON_SPECIALIZE_INT(unsigned int)
CPPCMS_JSON_SPECIALIZE_INT(long)
CPPCMS_JSON_SPECIALIZE_INT(unsigned long)
CPPCMS_JSON_SPECIALIZE_INT(long long)
CPPCMS_JSON_SPECIALIZE_INT(unsigned long long)
#undef CPPCMS_JSON_SPECIALIZE_INT
template<>
struct traits<float> {
static float get(value const &v)
{
double r=v.number();
if( r < (-std::numeric_limits<float>::max()) // actually should be C++11 lowest, but it should be under IEEE float lowest()=-max()
|| std::numeric_limits<float>::max() < r )
{
throw bad_value_cast();
}
return static_cast<float>(r);
}
static void set(value &v,float const &in)
{
v.number(in);
}
};
template<>
struct traits<long double> {
static long double get(value const &v)
{
return v.number();
}
static void set(value &v,long double const &in)
{
if( in < -std::numeric_limits<double>::max() // should actually be std::numeric_limits<float>::lowest() but it is ==-max()
|| std::numeric_limits<double>::max() < in )
{
throw bad_value_cast();
}
v.number(static_cast<double>(in));
}
};
template<>
struct traits<json::null> {
static void set(value &v,json::null const &/*in*/)
{
v.null();
}
};
template<int n>
struct traits<char[n]> {
typedef char vtype[n];
static void set(value &v,vtype const &in)
{
v.str(in);
}
};
template<int n>
struct traits<char const [n]> {
typedef char const vtype[n];
static void set(value &v,vtype const &in)
{
v.str(in);
}
};
template<>
struct traits<char const *> {
static void set(value &v,char const * const &in)
{
v.str(in);
}
};
/// \endcond
} // json
} // cppcms
#endif