json: store numbers natively as uint/sint/real

allows more accurate representations and better error checking.
This commit is contained in:
Danny Robson 2016-05-12 17:41:31 +10:00
parent 1f432c13b7
commit 8142944139
5 changed files with 313 additions and 106 deletions

View File

@ -183,22 +183,23 @@ json::flat::parse (const boost::filesystem::path &path)
std::ostream&
json::flat::operator<< (std::ostream &os, json::flat::type t)
{
using T = json::flat::type;
switch (t) {
case json::flat::type::STRING: os << "STRING"; break;
case json::flat::type::NUL: os << "NUL"; break;
case json::flat::type::BOOLEAN: os << "BOOLEAN"; break;
case json::flat::type::INTEGER: os << "INTEGER"; break;
case json::flat::type::REAL: os << "REAL"; break;
case T::STRING: return os << "STRING";
case T::NUL: return os << "NUL";
case T::BOOLEAN: return os << "BOOLEAN";
case T::INTEGER: return os << "INTEGER";
case T::REAL: return os << "REAL";
case json::flat::type::OBJECT_BEGIN: os << "OBJECT_BEGIN"; break;
case json::flat::type::OBJECT_END: os << "OBJECT_END"; break;
case json::flat::type::ARRAY_BEGIN: os << "ARRAY_BEGIN"; break;
case json::flat::type::ARRAY_END: os << "ARRAY_END"; break;
case T::OBJECT_BEGIN: return os << "OBJECT_BEGIN";
case T::OBJECT_END: return os << "OBJECT_END";
case T::ARRAY_BEGIN: return os << "ARRAY_BEGIN";
case T::ARRAY_END: return os << "ARRAY_END";
default:
unreachable ();
case T::UNKNOWN: ;
// fall out
}
return os;
unreachable ();
}

View File

@ -174,7 +174,7 @@ validate (json::tree::string &node,
// check length is less than a maximum
auto maxLength = schema.find ("maxLength");
if (maxLength != schema.cend ()) {
auto cmp = maxLength->second->as_number ().native ();
auto cmp = maxLength->second->as_number ().uint ();
if (!util::is_integer (cmp))
throw length_error ("maxLength");
@ -185,7 +185,7 @@ validate (json::tree::string &node,
// check length is greater than a maximum
auto minLength = schema.find ("minLength");
if (minLength != schema.cend ()) {
auto cmp = minLength->second->as_number ().native ();
auto cmp = minLength->second->as_number ().uint ();
if (!util::is_integer (cmp))
throw length_error ("minLength");
@ -206,31 +206,42 @@ validate (json::tree::string &node,
//-----------------------------------------------------------------------------
template <typename T>
static void
validate (json::tree::number &node,
const json::tree::object &schema)
{
const auto &val = node.native ();
validate_number (T val, const json::tree::object &schema) {
using R = json::tree::number::repr_t;
// check strictly positive integer multiple
auto mult = schema.find ("multipleOf");
if (mult != schema.cend ()) {
auto div = mult->second->as_number ().native ();
const auto &div = mult->second->as_number ();
if (val <= 0 || util::almost_equal (val, div))
throw json::schema_error ("multipleOf");
switch (div.repr ()) {
case R::REAL: if (util::exactly_zero (std::fmod (val, div.real ()))) throw json::schema_error ("multipleOf"); break;
case R::SINT: if (util::exactly_zero (std::fmod (val, div.sint ()))) throw json::schema_error ("multipleOf"); break;
case R::UINT: if (util::exactly_zero (std::fmod (val, div.uint ()))) throw json::schema_error ("multipleOf"); break;
}
}
// check maximum holds. exclusive requires max condition.
auto max = schema.find ("maximum");
auto exclusiveMax = schema.find ("exclusiveMaximum");
if (max != schema.end ()) {
auto cmp = max->second->as_number ().native ();
const auto &cmp = max->second->as_number ();
if (exclusiveMax != schema.end () && exclusiveMax->second->as_boolean () && val >= cmp)
throw json::schema_error ("exclusiveMax");
else if (val > cmp)
throw json::schema_error ("maximum");
if (exclusiveMax != schema.end () && exclusiveMax->second->as_boolean ()) {
switch (cmp.repr ()) {
case R::REAL: if (val >= T(cmp.real ())) throw json::schema_error ("exclusiveMax"); break;
case R::SINT: if (val >= T(cmp.uint ())) throw json::schema_error ("exclusiveMax"); break;
case R::UINT: if (val >= T(cmp.sint ())) throw json::schema_error ("exclusiveMax"); break;
}
} else {
switch (cmp.repr ()) {
case R::REAL: if (val > T(cmp.real ())) throw json::schema_error ("maximum"); break;
case R::SINT: if (val > T(cmp.sint ())) throw json::schema_error ("maximum"); break;
case R::UINT: if (val > T(cmp.uint ())) throw json::schema_error ("maximum"); break;
}
}
} else {
if (exclusiveMax != schema.cend ())
throw json::schema_error ("exclusiveMax");
@ -240,16 +251,42 @@ validate (json::tree::number &node,
auto min = schema.find ("minimum");
auto exclusiveMin = schema.find ("exclusiveMinimum");
if (min != schema.end ()) {
auto cmp = min->second->as_number ().native ();
const auto &cmp = min->second->as_number ();
if (exclusiveMin != schema.end () && exclusiveMin->second->as_boolean () && val <= cmp)
throw json::schema_error ("exclusiveMin");
else if (val < cmp)
throw json::schema_error ("minimum");
if (exclusiveMin != schema.end () && exclusiveMin->second->as_boolean ()) {
switch (cmp.repr ()) {
case R::REAL: if (val <= T(cmp.real ())) throw json::schema_error ("exclusiveMin"); break;
case R::SINT: if (val <= T(cmp.sint ())) throw json::schema_error ("exclusiveMin"); break;
case R::UINT: if (val <= T(cmp.uint ())) throw json::schema_error ("exclusiveMin"); break;
}
} else {
switch (cmp.repr ()) {
case R::REAL: if (val < T(cmp.real ())) throw json::schema_error ("minimum"); break;
case R::SINT: if (val < T(cmp.sint ())) throw json::schema_error ("minimum"); break;
case R::UINT: if (val < T(cmp.uint ())) throw json::schema_error ("minimum"); break;
}
}
} else {
if (exclusiveMin != schema.cend ())
throw json::schema_error ("exclusiveMin");
}
}
//-----------------------------------------------------------------------------
static void
validate (json::tree::number &node,
const json::tree::object &schema)
{
using N = json::tree::number;
using R = N::repr_t;
switch (node.repr ()) {
case R::REAL: validate_number<N::real_t> (node.real (), schema); break;
case R::SINT: validate_number<N::sint_t> (node.sint (), schema); break;
case R::UINT: validate_number<N::uint_t> (node.uint (), schema); break;
}
}
@ -304,7 +341,7 @@ validate (json::tree::node &node,
if (type != schema.cend ()) {
// check against a single named type
if (type->second->is_string ()) {
auto a = type->second->as_string ();
const auto &a = type->second->as_string ();
auto b = to_string (node.type ());
if (a != b)

View File

@ -49,21 +49,29 @@ using json::tree::null;
///////////////////////////////////////////////////////////////////////////////
namespace util {
template <>
bool
is_integer (const json::tree::number &node)
{
return is_integer (node.native ());
using R = json::tree::number::repr_t;
switch (node.repr ()) {
case R::REAL:
return is_integer (node.real ());
case R::SINT:
case R::UINT:
return true;
}
unreachable ();
}
//-----------------------------------------------------------------------------
template <>
bool
is_integer (const json::tree::node &node)
{
return node.is_number () &&
is_integer (node.as_number ());
return node.is_number () && is_integer (node.as_number ());
}
}
@ -127,43 +135,67 @@ parse (std::vector<json::flat::item>::const_iterator first,
CHECK (first != last);
CHECK (output.get () == nullptr);
using T = json::flat::type;
switch (first->tag) {
case json::flat::type::NUL:
case T::NUL:
output.reset (new json::tree::null ());
return first + 1;
case json::flat::type::BOOLEAN:
case T::BOOLEAN:
CHECK (*first->first == 't' || *first->first == 'f');
output.reset (new json::tree::boolean (*first->first == 't'));
return first + 1;
case json::flat::type::STRING:
case T::STRING:
CHECK_NEQ (first->first, first->last);
output.reset (new json::tree::string (first->first + 1, first->last - 1));
return first + 1;
case json::flat::type::INTEGER:
case json::flat::type::REAL:
case T::INTEGER:
if (first->first[0] == '-') {
char *end;
intmax_t v = strtoll (first->first, &end, 10);
if (end == first->first || end > first->last)
throw json::parse_error ("invalid signed integer");
output.reset (new json::tree::number (v));
} else {
char *end;
uintmax_t v = strtoull (first->first, &end, 10);
if (end == first->first || end > first->last)
throw json::parse_error ("invalid unsigned integer");
output.reset (new json::tree::number (v));
}
return first + 1;
case T::REAL:
output.reset (new json::tree::number (std::atof (first->first)));
return first + 1;
case json::flat::type::ARRAY_BEGIN: {
case T::ARRAY_BEGIN: {
auto value = std::make_unique<json::tree::array> ();
auto cursor = ::parse (first + 1, last, *value);
output = std::move (value);
return cursor;
}
case json::flat::type::OBJECT_BEGIN: {
case T::OBJECT_BEGIN: {
auto value = std::make_unique<json::tree::object> ();
auto cursor = ::parse (first + 1, last, *value);
output = std::move (value);
return cursor;
}
default:
case T::UNKNOWN:
case T::OBJECT_END:
case T::ARRAY_END:
unreachable ();
}
unreachable ();
}
@ -304,7 +336,7 @@ json::tree::node::as_bool (void) const
float
json::tree::node::as_float (void) const
{
return static_cast<float> (as_number ().native ());
return static_cast<float> (as_number ().real ());
}
@ -312,20 +344,23 @@ json::tree::node::as_float (void) const
double
json::tree::node::as_double (void) const
{
return as_number ().native ();
return as_number ().real ();
}
//-----------------------------------------------------------------------------
size_t
uintmax_t
json::tree::node::as_uint (void) const
{
auto val = as_number ().native ();
if (!util::is_integer (val))
throw json::type_error ("cast fractional value to uint");
return as_number ().uint ();
}
// TODO: use trunc_cast
return static_cast<size_t> (val);
//-----------------------------------------------------------------------------
intmax_t
json::tree::node::as_sint (void) const
{
return as_number ().sint ();
}
@ -344,45 +379,60 @@ namespace json { namespace tree {
{
return as_bool ();
}
} }
//-----------------------------------------------------------------------------
namespace json { namespace tree {
template <>
float json::tree::node::as (void) const
{
return as_float ();
}
} }
//-----------------------------------------------------------------------------
namespace json { namespace tree {
template <>
double
json::tree::node::as (void) const
{
return as_double ();
}
} }
//-----------------------------------------------------------------------------
#define AS_INTEGRAL(T) \
namespace json { namespace tree { \
template <> \
T \
node::as (void) const \
{ \
return static_cast<T> (as_double ()); \
} \
} }
template <>
uint32_t
json::tree::node::as (void) const
{
return static_cast<uint32_t> (as_uint ());
}
AS_INTEGRAL(uint8_t)
AS_INTEGRAL(uint16_t)
AS_INTEGRAL(uint32_t)
AS_INTEGRAL(uint64_t)
//-----------------------------------------------------------------------------
template <>
uint64_t
json::tree::node::as (void) const
{
return static_cast<uint64_t> (as_uint ());
}
//-----------------------------------------------------------------------------
template <>
int32_t
json::tree::node::as (void) const
{
return static_cast<int32_t> (as_sint ());
}
//-----------------------------------------------------------------------------
template <>
int64_t
json::tree::node::as (void) const
{
return static_cast<int64_t> (as_sint ());
}
} }
///////////////////////////////////////////////////////////////////////////////
@ -411,7 +461,7 @@ json::tree::node::operator[] (const std::string &key)&
//-----------------------------------------------------------------------------
json::tree::node&
json::tree::node::operator[] (unsigned int idx)&
json::tree::node::operator[] (size_t idx)&
{ return as_array()[idx]; }
@ -423,7 +473,7 @@ json::tree::node::operator[] (const std::string &key) const&
//-----------------------------------------------------------------------------
const json::tree::node&
json::tree::node::operator[] (unsigned int idx) const&
json::tree::node::operator[] (size_t idx) const&
{ return as_array()[idx]; }
@ -634,7 +684,7 @@ json::tree::array::size (void) const
//-----------------------------------------------------------------------------
json::tree::node&
json::tree::array::operator[] (unsigned int idx)&
json::tree::array::operator[] (size_t idx)&
{
return *m_values[idx];
}
@ -642,7 +692,7 @@ json::tree::array::operator[] (unsigned int idx)&
//-----------------------------------------------------------------------------
const json::tree::node&
json::tree::array::operator[] (unsigned int idx) const&
json::tree::array::operator[] (size_t idx) const&
{
return *m_values[idx];
}
@ -761,7 +811,13 @@ json::tree::string::operator== (const std::string &rhs) const
std::unique_ptr<json::tree::node>
json::tree::number::clone (void) const
{
return std::make_unique<json::tree::number> (m_value);
switch (m_repr) {
case REAL: return std::make_unique<json::tree::number> (m_value.r);
case SINT: return std::make_unique<json::tree::number> (m_value.s);
case UINT: return std::make_unique<json::tree::number> (m_value.u);
}
unreachable ();
}
@ -769,15 +825,86 @@ json::tree::number::clone (void) const
std::ostream&
json::tree::number::write (std::ostream &os) const
{
os << std::setprecision (std::numeric_limits<double>::digits10) << m_value;
return os;
auto old = int (os.precision ());
switch (m_repr) {
case REAL: return os << std::numeric_limits<real_t>::digits10 << m_value.r << std::setprecision (old);
case SINT: return os << std::numeric_limits<sint_t>::digits10 << m_value.s << std::setprecision (old);
case UINT: return os << std::numeric_limits<uint_t>::digits10 << m_value.u << std::setprecision (old);
}
unreachable ();
}
//-----------------------------------------------------------------------------
bool
json::tree::number::operator ==(const json::tree::number &rhs) const
{ return util::almost_equal (rhs.m_value, m_value); }
json::tree::number::operator ==(const json::tree::number &rhs) const {
if (repr () != rhs.repr ())
return false;
switch (repr ()) {
case REAL: return util::almost_equal (real (), rhs.real ());
case SINT: return util::almost_equal (sint (), rhs.sint ());
case UINT: return util::almost_equal (uint (), rhs.uint ());
}
unreachable ();
}
//-----------------------------------------------------------------------------
json::tree::number::real_t
json::tree::number::real (void) const
{
if (m_repr != REAL)
throw json::type_error ("number is not a real");
return m_value.r;
}
//-----------------------------------------------------------------------------
json::tree::number::sint_t
json::tree::number::sint (void) const
{
if (m_repr != SINT)
throw json::type_error ("number is not a sint");
return m_value.s;
}
//-----------------------------------------------------------------------------
json::tree::number::uint_t
json::tree::number::uint (void) const
{
if (m_repr != UINT)
throw json::type_error ("number is not a uint");
return m_value.u;
}
//-----------------------------------------------------------------------------
json::tree::number::operator json::tree::number::real_t (void) const
{
return real ();
}
//-----------------------------------------------------------------------------
json::tree::number::operator json::tree::number::sint_t (void) const
{
return sint ();
}
//-----------------------------------------------------------------------------
json::tree::number::operator json::tree::number::uint_t (void) const
{
return uint ();
}
///////////////////////////////////////////////////////////////////////////////
@ -854,14 +981,26 @@ namespace json { namespace tree {
template <>
std::unique_ptr<node>
io<int>::serialise (const int &i) {
return std::unique_ptr<node> (new number (i));
io<int32_t>::serialise (const int32_t &i) {
return std::unique_ptr<node> (new number (intmax_t {i}));
}
template <>
std::unique_ptr<node>
io<size_t>::serialise (const size_t &i) {
return std::unique_ptr<node> (new number (i));
io<int64_t>::serialise (const int64_t &i) {
return std::unique_ptr<node> (new number (intmax_t {i}));
}
template <>
std::unique_ptr<node>
io<uint32_t>::serialise (const uint32_t &i) {
return std::unique_ptr<node> (new number (uintmax_t {i}));
}
template <>
std::unique_ptr<node>
io<uint64_t>::serialise (const uint64_t &i) {
return std::unique_ptr<node> (new number (uintmax_t {i}));
}
template <>

View File

@ -52,6 +52,7 @@ namespace json { namespace tree {
/// Abstract base for all JSON values
class node {
public:
node (const node&) = delete;
virtual ~node () { ; }
virtual std::unique_ptr<node> clone (void) const = 0;
@ -75,7 +76,8 @@ namespace json { namespace tree {
virtual bool as_bool (void) const;
virtual float as_float (void) const;
virtual double as_double (void) const;
virtual size_t as_uint (void) const;
virtual intmax_t as_sint (void) const;
virtual uintmax_t as_uint (void) const;
virtual const char* as_chars (void) const&;
template <typename T>
@ -103,24 +105,26 @@ namespace json { namespace tree {
virtual bool operator!=(const char *rhs) const { return !(*this == rhs); }
virtual node& operator[] (const std::string&)&;
virtual node& operator[] (unsigned int)&;
virtual node& operator[] (size_t)&;
virtual const node& operator[] (const std::string&) const&;
virtual const node& operator[] (unsigned int) const&;
virtual const node& operator[] (size_t) const&;
virtual std::ostream& write (std::ostream &os) const = 0;
protected:
node () = default;
};
/// Represents a JSON object, and contains its children.
class object final : public node {
protected:
typedef std::map<std::string, std::unique_ptr<node>> value_store;
private:
using value_store = std::map<std::string, std::unique_ptr<node>>;
public:
typedef value_store::iterator iterator;
typedef value_store::const_iterator const_iterator;
protected:
value_store m_values;
public:
virtual ~object ();
@ -136,8 +140,8 @@ namespace json { namespace tree {
{ return rhs == *this; }
virtual void insert (const std::string &key, std::unique_ptr<node>&& value);
virtual const node& operator[](const std::string &key) const& override;
virtual node& operator[](const std::string &key)& override;
virtual const node& operator[] (const std::string &key) const& override;
virtual node& operator[] (const std::string &key)& override;
virtual bool has (const std::string&) const;
virtual const_iterator find (const std::string&) const;
@ -153,6 +157,9 @@ namespace json { namespace tree {
virtual void erase (const std::string &key);
virtual std::ostream& write (std::ostream &os) const override;
private:
value_store m_values;
};
@ -182,8 +189,8 @@ namespace json { namespace tree {
virtual bool operator==(const node &rhs) const override;
virtual size_t size (void) const;
virtual node& operator [](unsigned int idx)& override;
virtual const node& operator [](unsigned int idx) const& override;
virtual node& operator[] (size_t idx)& override;
virtual const node& operator[] (size_t idx) const& override;
virtual iterator begin (void);
virtual iterator end (void);
@ -234,13 +241,21 @@ namespace json { namespace tree {
/// Represents a JSON integer/float literal.
class number final : public node {
protected:
double m_value;
public:
explicit number (double _value): m_value (_value) { ; }
explicit number (int _value): m_value (_value) { ; }
explicit number (size_t _value): m_value (_value) { ; }
enum repr_t {
REAL,
SINT,
UINT
};
using real_t = double;
using sint_t = intmax_t;
using uint_t = uintmax_t;
explicit number (real_t _value): m_repr (REAL) { m_value.r = _value; }
explicit number (sint_t _value): m_repr (SINT) { m_value.s = _value; }
explicit number (uint_t _value): m_repr (UINT) { m_value.u = _value; }
virtual std::unique_ptr<node> clone (void) const override;
virtual const number& as_number (void) const& override { return *this; }
@ -248,15 +263,30 @@ namespace json { namespace tree {
virtual bool is_number (void) const override { return true; }
virtual type_t type (void) const override { return NUMBER; }
virtual repr_t repr (void) const { return m_repr; }
virtual bool operator==(const number &rhs) const override;
virtual bool operator==(const node &rhs) const override
{ return rhs == *this; }
operator double(void) const { return m_value; }
double native (void) const { return m_value; }
operator real_t (void) const;
operator sint_t (void) const;
operator uint_t (void) const;
real_t real (void) const;
sint_t sint (void) const;
uint_t uint (void) const;
virtual std::ostream& write (std::ostream &os) const override;
private:
union {
real_t r;
sint_t s;
uint_t u;
} m_value;
repr_t m_repr;
};

View File

@ -54,7 +54,7 @@ main (void)
tap.expect (!ref["integer"].is_string (), "integer not is_string");
tap.expect (
util::exactly_equal (
(unsigned)ref["integer"].as_number ().native (),
(unsigned)ref["integer"].as_number ().as_uint (),
1u
),
"integer value equality"
@ -102,7 +102,7 @@ main (void)
tap.expect (!ref["double"].is_string (), "double not is_string");
tap.expect (
util::exactly_equal (
ref["double"].as_number ().native (),
ref["double"].as_number ().as<double> (),
3.14
),
"double value equality"