Skip to content

Commit

Permalink
Statically verify input types and permit nested JSON types
Browse files Browse the repository at this point in the history
  • Loading branch information
DeadMG committed Dec 28, 2015
1 parent e73bccc commit db6e50e
Show file tree
Hide file tree
Showing 3 changed files with 152 additions and 87 deletions.
220 changes: 139 additions & 81 deletions jsonpp/value.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -35,11 +35,39 @@
#include <iosfwd>

namespace json {
class value;

template<typename T, typename = void> struct is_valid_json_type {
static const bool result = false;
};
template<> struct is_valid_json_type<bool> {
static const bool result = true;
};
template<> struct is_valid_json_type<json::value> {
static const bool result = true;
};
template<> struct is_valid_json_type<json::null> {
static const bool result = true;
};
template<typename T> struct is_valid_json_type<T, std::enable_if_t<json::is_string<T>::value>> {
static const bool result = true;
};
template<typename T> struct is_valid_json_type<T, std::enable_if_t<json::is_number<T>::value>> {
static const bool result = true;
};
template<typename T> struct is_valid_json_type<std::vector<T>> {
static const bool result = is_valid_json_type<T>::result;
};
template<typename T> struct is_valid_json_type<std::map<std::string, T>> {
static const bool result = is_valid_json_type<T>::result;
};

class value {
public:
using object = std::map<std::string, value>;
using array = std::vector<value>;
private:
template<typename T> struct type_tag {};
union storage_t {
double number;
bool boolean;
Expand Down Expand Up @@ -72,9 +100,110 @@ class value {
storage_type = other.storage_type;
}

bool is_impl(type_tag<std::string>) const JSONPP_NOEXCEPT {
return storage_type == type::string;
}

bool is_impl(type_tag<const char*>) const JSONPP_NOEXCEPT {
return storage_type == type::string;
}

bool is_impl(type_tag<json::null>) const JSONPP_NOEXCEPT {
return storage_type == type::null;
}

template<typename T> std::enable_if_t<json::is_number<T>::value, bool>
is_impl(type_tag<T>) const JSONPP_NOEXCEPT {
return storage_type == type::number;
}

bool is_impl(type_tag<bool>) const JSONPP_NOEXCEPT {
return storage_type == type::boolean;
}

bool is_impl(type_tag<json::value>) const JSONPP_NOEXCEPT {
return true;
}


template<typename T>
struct is_generic : And<Not<is_string<T>>, Not<is_bool<T>>, Not<is_number<T>>,
Not<is_null<T>>, Not<std::is_same<T, object>>, Not<std::is_same<T, array>>> {};
bool is_impl(type_tag<std::map<std::string, T>>) const JSONPP_NOEXCEPT {
if (storage_type != type::object)
return false;
for (auto&& pair : *storage.obj) {
if (!pair.second.is<T>())
return false;
}
return true;
}

template<typename T>
bool is_impl(type_tag<std::vector<T>>) const JSONPP_NOEXCEPT {
if (storage_type != type::array)
return false;
for (auto&& obj : *storage.arr) {
if (!obj.is<T>())
return false;
}
return true;
}

const char* as_impl(type_tag<const char*>) const {
if (!is<const char*>())
throw std::runtime_error("Invalid JSON type.");
return storage.str->c_str();
}

std::string as_impl(type_tag<std::string>) const {
if (!is<std::string>())
throw std::runtime_error("Invalid JSON type.");
return *(storage.str);
}

json::null as_impl(type_tag<json::null>) const {
if (!is<json::null>())
throw std::runtime_error("Invalid JSON type.");
return{};
}

bool as_impl(type_tag<bool>) const {
if (!is<bool>())
throw std::runtime_error("Invalid JSON type.");
return storage.boolean;
}

template<typename T> std::enable_if_t<json::is_number<T>::value, T> as_impl(type_tag<T>) const {
if (!is<double>())
throw std::runtime_error("Invalid JSON type.");
return storage.number;
}

json::value as_impl(type_tag<json::value>) const {
return *this;
}

template<typename T>
std::map<std::string, T> as_impl(type_tag<std::map<std::string, T>>) const {
if (!is<std::map<std::string, T>>())
throw std::runtime_error("Invalid JSON type.");
auto contents = *storage.obj;
std::map<std::string, T> result;
for (auto&& pair : contents)
result[pair.first] = pair.second.as<T>();
return result;
}

template<typename T>
std::vector<T> as_impl(type_tag<std::vector<T>>) const {
if (!is<std::vector<T>>())
throw std::runtime_error("Invalid JSON type.");
auto contents = *(storage.arr);
auto results = std::vector<T>();
for (auto&& obj : contents)
results.push_back(obj.as<T>());
return results;
}

public:
value() JSONPP_NOEXCEPT: storage_type(type::null) {}
value(null) JSONPP_NOEXCEPT: storage_type(type::null) {}
Expand Down Expand Up @@ -223,91 +352,20 @@ class value {
storage_type = type::null;
}

template<typename T, EnableIf<is_string<T>> = 0>
bool is() const JSONPP_NOEXCEPT {
return storage_type == type::string;
}

template<typename T, EnableIf<is_null<T>> = 0>
bool is() const JSONPP_NOEXCEPT {
return storage_type == type::null;
}

template<typename T, EnableIf<is_number<T>> = 0>
bool is() const JSONPP_NOEXCEPT {
return storage_type == type::number;
}

template<typename T, EnableIf<is_bool<T>> = 0>
bool is() const JSONPP_NOEXCEPT {
return storage_type == type::boolean;
}

template<typename T, EnableIf<std::is_same<T, object>> = 0>
bool is() const JSONPP_NOEXCEPT {
return storage_type == type::object;
}

template<typename T, EnableIf<std::is_same<T, array>> = 0>
bool is() const JSONPP_NOEXCEPT {
return storage_type == type::array;
}

template<typename T, EnableIf<is_generic<T>> = 0>
template<typename T>
bool is() const JSONPP_NOEXCEPT {
return false;
}

template<typename T, EnableIf<std::is_same<T, const char*>> = 0>
T as() const {
assert(is<T>());
return storage.str->c_str();
static_assert(is_valid_json_type<T>::result, "Passed a type argument to is() which is not valid JSON");
return is_impl(type_tag<T>());
}

template<typename T, EnableIf<std::is_same<T, std::string>> = 0>
T as() const {
assert(is<T>());
return *(storage.str);
}

template<typename T, EnableIf<is_null<T>> = 0>
T as() const {
assert(is<T>());
return {};
}

template<typename T, EnableIf<is_bool<T>> = 0>
T as() const {
assert(is<T>());
return storage.boolean;
}

template<typename T, EnableIf<is_number<T>> = 0>
T as() const {
assert(is<T>());
return storage.number;
}

template<typename T, EnableIf<std::is_same<T, object>> = 0>
T as() const {
assert(is<T>());
return *(storage.obj);
}

template<typename T, EnableIf<std::is_same<T, array>> = 0>
T as() const {
assert(is<T>());
return *(storage.arr);
}

template<typename T, EnableIf<is_generic<T>> = 0>
template<typename T>
T as() const {
throw std::runtime_error("calling value::as<T>() on an invalid type (use a json type instead)");
static_assert(is_valid_json_type<T>::result, "Passed a type argument to as() which is not valid JSON");
return as_impl(type_tag<T>());
}

template<typename T>
T as(Identity<T>&& def) const {
return is<T>() ? as<T>() : std::forward<T>(def);
std::decay_t<T> as(T&& def) const {
return is<std::decay_t<T>>() ? as<std::decay_t<T>>() : std::forward<T>(def);
}

template<typename T, EnableIf<is_string<T>> = 0>
Expand Down
3 changes: 3 additions & 0 deletions tests/accessors.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,8 @@
#include <catch.hpp>
#include <jsonpp/value.hpp>

#define COMMA ,

TEST_CASE("accessors", "[subscripts-accessors]") {
json::value v = { json::object{ {"test", 1} }, nullptr, "my_string", 1.0 };
json::value o = json::object{ {"key", "value"}, {"int", 1} };
Expand All @@ -43,6 +45,7 @@ TEST_CASE("accessors", "[subscripts-accessors]") {
};

SECTION("get from complex structure") {
REQUIRE(v[0].is<std::map<std::string COMMA int>>());
REQUIRE(v[0]["test"].is<int>());
REQUIRE(v[0]["test"].as<int>() == 1);
};
Expand Down
16 changes: 10 additions & 6 deletions tests/basic.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,10 @@
#include <catch.hpp>
#include <jsonpp/parser.hpp>

template<> std::string Catch::toString<std::nullptr_t>(const std::nullptr_t& ptr) {
return "null";
}

TEST_CASE("numbers", "[basic-numbers]") {
json::value v;
json::format_options minify;
Expand Down Expand Up @@ -125,21 +129,21 @@ TEST_CASE("strings", "[basic-strings]") {
}

SECTION("escaped strings") {
REQUIRE_NOTHROW(json::parse(R"("\"")", v));
REQUIRE_NOTHROW(json::parse("\"\\\"\"", v));
REQUIRE(!v.is<json::array>());
REQUIRE(!v.is<double>());
REQUIRE(v.is<std::string>());
REQUIRE(v.is<const char*>());
REQUIRE(!v.is<bool>());
REQUIRE(!v.is<json::null>());
REQUIRE(!v.is<json::object>());
REQUIRE(json::dump_string(v, minify) == R"("\"")");
REQUIRE(json::dump_string(v, minify) == "\"\\\"\"");

auto&& str1 = v.as<std::string>();
REQUIRE(str1.size() == 1);
REQUIRE(str1.back() == '"');

REQUIRE_NOTHROW(json::parse(R"("\t\n\b\"\u2000\u1234")", v));
REQUIRE_NOTHROW(json::parse("\"\\t\\n\\b\\\"\\u2000\\u1234\"", v));
REQUIRE(!v.is<json::array>());
REQUIRE(!v.is<double>());
REQUIRE(v.is<std::string>());
Expand Down Expand Up @@ -217,17 +221,17 @@ TEST_CASE("arrays", "[basic-arrays]") {
SECTION("single element array") {
REQUIRE_NOTHROW(json::parse("[10]\n\t\n", v));
REQUIRE(v.is<json::array>());
REQUIRE(v.is<std::vector<int>>());
REQUIRE(!v.is<double>());
REQUIRE(!v.is<bool>());
REQUIRE(!v.is<std::string>());
REQUIRE(!v.is<json::null>());
REQUIRE(!v.is<json::object>());
REQUIRE(json::dump_string(v, minify) == "[10]");
auto&& arr = v.as<json::array>();
auto&& arr = v.as<std::vector<int>>();
REQUIRE(!arr.empty());
REQUIRE(arr.size() == 1);
REQUIRE(arr.back().is<int>());
REQUIRE(arr.back().as<int>() == 10);
REQUIRE(arr.back() == 10);
}

SECTION("regular array") {
Expand Down

0 comments on commit db6e50e

Please sign in to comment.