-
-
Notifications
You must be signed in to change notification settings - Fork 6.9k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Feature/user defined types #338
Changes from 7 commits
c136f67
2ee7399
0001032
63241db
f805dfc
733e95f
06d60de
2407833
db4806b
9d290c6
5451aa5
15507d1
56492ce
b81c2a2
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -91,13 +91,34 @@ SOFTWARE. | |
*/ | ||
namespace nlohmann | ||
{ | ||
// TODO add real documentation before PR | ||
|
||
// Traits structure declaration, users can specialize it for their own types | ||
// | ||
// constructing a json object from a user-defined type will call the | ||
// 'json to_json(T)' function | ||
// | ||
// whereas calling json::get<T> will call 'T from_json(json const&)' | ||
template <typename T, typename = void> | ||
struct json_traits; | ||
|
||
// alias templates to reduce boilerplate | ||
template <bool B, typename T = void> | ||
using enable_if_t = typename std::enable_if<B, T>::type; | ||
|
||
template <typename T> | ||
using remove_cv_t = typename std::remove_cv<T>::type; | ||
|
||
template <typename T> | ||
using remove_reference_t = typename std::remove_reference<T>::type; | ||
|
||
// TODO update this doc | ||
/*! | ||
@brief unnamed namespace with internal helper functions | ||
@since version 1.0.0 | ||
*/ | ||
namespace | ||
|
||
namespace detail | ||
{ | ||
/*! | ||
@brief Helper to determine whether there's a key_type for T. | ||
|
@@ -122,6 +143,44 @@ struct has_mapped_type | |
std::is_integral<decltype(detect(std::declval<T>()))>::value; | ||
}; | ||
|
||
// taken from http://stackoverflow.com/questions/10711952/how-to-detect-existence-of-a-class-using-sfinae | ||
// used to determine if json_traits is defined for a given type T | ||
template <typename T> | ||
struct has_destructor | ||
{ | ||
template <typename U> | ||
static std::true_type detect(decltype(std::declval<U>().~U())*); | ||
|
||
template <typename> | ||
static std::false_type detect(...); | ||
|
||
static constexpr bool value = decltype(detect<T>(0))::value; | ||
}; | ||
|
||
template<typename T> | ||
struct has_json_traits | ||
{ | ||
static constexpr bool value = has_destructor<json_traits<T>>::value; | ||
}; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Do we need all this? I think we can have some member function like this in // Sketch
template <typename T>
auto from_impl(T const& t, long) -> decltype(do_something_without_trait(t));
template <typename T>
auto from_impl(T const& t, int) -> decltype(json_trait<T>::from_json(t, *this));
template <typename T>
auto from(T const& t) -> decltype(from_impl(t, 0)); Notice how So what that does is "try to call the int overload, and if that fails, fall back to the long overload". You can probably use this "idiom" to easily detect whether (If you want to check some examples that actually work, a good example is how There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. If I understand correctly, the |
||
|
||
struct to_json_fn | ||
{ | ||
template <typename T> | ||
constexpr auto operator()(T&& val) const -> decltype(to_json(std::forward<T>(val))) | ||
{ | ||
return to_json(std::forward<T>(val)); | ||
} | ||
}; | ||
|
||
struct from_json_fn | ||
{ | ||
template <typename Json, typename T> | ||
constexpr auto operator()(Json const& from, T& to) const -> decltype(from_json(from, to)) | ||
{ | ||
return from_json(from, to); | ||
} | ||
}; | ||
|
||
/*! | ||
@brief helper class to create locales with decimal point | ||
|
||
|
@@ -144,6 +203,23 @@ struct DecimalSeparator : std::numpunct<char> | |
|
||
} | ||
|
||
// taken from ranges-v3 | ||
// TODO add doc | ||
template <typename T> | ||
struct __static_const | ||
{ | ||
static constexpr T value{}; | ||
}; | ||
|
||
template <typename T> | ||
constexpr T __static_const<T>::value; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I remember that identifiers starting with an underscore followed by adjacent underscores are reserved for the implementation in any scope (not only the global one), that is, one cannot use them. But the rules for reserved identifiers were more complex than that (this is C++ after all), so might want to check if that is actually allowed in a non-global scope (we are inside a namespace here). There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. According to cppreference : "the identifiers with a double underscore anywhere are reserved" I'll rename it |
||
|
||
inline namespace | ||
{ | ||
constexpr auto const& to_json = __static_const<detail::to_json_fn>::value; | ||
constexpr auto const& from_json = __static_const<detail::from_json_fn>::value; | ||
} | ||
|
||
/*! | ||
@brief a class to store JSON values | ||
|
||
|
@@ -1225,6 +1301,25 @@ class basic_json | |
assert_invariant(); | ||
} | ||
|
||
// constructor chosen if json_traits is specialized for type T | ||
// note: constructor is marked explicit to avoid the following issue: | ||
// | ||
// struct not_equality_comparable{}; | ||
// | ||
// not_equality_comparable{} == not_equality_comparable{}; | ||
// | ||
// this will construct implicitely 2 json objects and call operator== on them | ||
// which can cause nasty bugs on the user's in json-unrelated code | ||
// | ||
// the trade-off is expressivety in initializer-lists | ||
// auto j = json{{"a", json(not_equality_comparable{})}}; | ||
// | ||
// we can remove this constraint though, since lots of ctor are not explicit already | ||
template <typename T, typename = enable_if_t<detail::has_json_traits< | ||
remove_cv_t<remove_reference_t<T>>>::value>> | ||
explicit basic_json(T &&val) | ||
: basic_json(json_traits<remove_cv_t<remove_reference_t<T>>>::to_json( | ||
std::forward<T>(val))) {} | ||
/*! | ||
@brief create a string (explicit) | ||
|
||
|
@@ -1241,15 +1336,14 @@ class basic_json | |
|
||
@sa @ref basic_json(const typename string_t::value_type*) -- create a | ||
string value from a character pointer | ||
@sa @ref basic_json(const CompatibleStringType&) -- create a string value | ||
@sa @ref basic_json(const CompatibleStringType&) -- create a string | ||
value | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. nitpick: you should probably revert all unnecessary formatting changes, since they make going through the relevant parts of the diffs harder There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Indeed, forgot to check that, my bad |
||
from a compatible string container | ||
|
||
@since version 1.0.0 | ||
*/ | ||
basic_json(const string_t& val) | ||
: m_type(value_t::string), m_value(val) | ||
{ | ||
assert_invariant(); | ||
basic_json(const string_t &val) : m_type(value_t::string), m_value(val) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. My intuition says that we would like to use the trait system for constructing There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I agree, although this would be a huge change to the current implementation, it might be out of the scope of this PR. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. While I think we probably don't want to do this for string and int, and other primitive types yet, I recall that there was a constructor for "ranges" (types with There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Are you talking about the constructor taking |
||
assert_invariant(); | ||
} | ||
|
||
/*! | ||
|
@@ -2201,7 +2295,7 @@ class basic_json | |
{ | ||
std::stringstream ss; | ||
// fix locale problems | ||
const static std::locale loc(std::locale(), new DecimalSeparator); | ||
const static std::locale loc(std::locale(), new detail::DecimalSeparator); | ||
ss.imbue(loc); | ||
|
||
// 6, 15 or 16 digits of precision allows round-trip IEEE 754 | ||
|
@@ -2584,20 +2678,52 @@ class basic_json | |
// value access // | ||
////////////////// | ||
|
||
// get_impl overload chosen if json_traits struct is specialized for type T | ||
// simply returns json_traits<T>::from_json(*this); | ||
template <typename T, typename = enable_if_t<detail::has_json_traits< | ||
remove_cv_t<remove_reference_t<T>>>::value>> | ||
auto get_impl(T *) const | ||
-> decltype(json_traits<remove_cv_t<remove_reference_t<T>>>::from_json( | ||
std::declval<basic_json>())) { | ||
return json_traits<remove_cv_t<remove_reference_t<T>>>::from_json(*this); | ||
} | ||
|
||
// this one is quite atrocious | ||
// this overload is chosen ONLY if json_traits struct is not specialized, and if the expression nlohmann::from_json(*this, T&) is valid | ||
// I chose to prefer the json_traits specialization if it exists, since it's a more advanced use. | ||
// But we can of course change this behaviour | ||
template <typename T> | ||
auto get_impl(T *) const | ||
-> enable_if_t<not detail::has_json_traits<remove_cv_t<T>>::value, | ||
remove_cv_t<remove_reference_t<decltype( | ||
::nlohmann::from_json(std::declval<basic_json>(), | ||
std::declval<T &>()), | ||
std::declval<T>())>>> | ||
{ | ||
remove_cv_t<T> ret; | ||
// I guess this output parameter is the only way to get ADL | ||
// Even if users can use the get<T> method to have a more 'functional' behaviour | ||
// i.e. having a return type, could there be a way to have the same behaviour with from_json? | ||
// e.g. auto t = nlohmann::from_json<T>(json{}); | ||
// this seems to require variable templates though... (at least it did when I tried to implement it) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'll think about this, you make good points here. |
||
::nlohmann::from_json(*this, ret); | ||
return ret; | ||
} | ||
|
||
/// get an object (explicit) | ||
template<class T, typename std::enable_if< | ||
std::is_convertible<typename object_t::key_type, typename T::key_type>::value and | ||
std::is_convertible<basic_json_t, typename T::mapped_type>::value, int>::type = 0> | ||
T get_impl(T*) const | ||
{ | ||
if (is_object()) | ||
{ | ||
return T(m_value.object->begin(), m_value.object->end()); | ||
} | ||
else | ||
{ | ||
throw std::domain_error("type must be object, but is " + type_name()); | ||
} | ||
template <class T, | ||
typename std::enable_if< | ||
std::is_convertible<typename object_t::key_type, | ||
typename T::key_type>::value and | ||
std::is_convertible<basic_json_t, | ||
typename T::mapped_type>::value, | ||
int>::type = 0> | ||
T get_impl(T *) const { | ||
if (is_object()) { | ||
return T(m_value.object->begin(), m_value.object->end()); | ||
} else { | ||
throw std::domain_error("type must be object, but is " + type_name()); | ||
} | ||
} | ||
|
||
/// get an object (explicit) | ||
|
@@ -2619,7 +2745,7 @@ class basic_json | |
not std::is_same<basic_json_t, typename T::value_type>::value and | ||
not std::is_arithmetic<T>::value and | ||
not std::is_convertible<std::string, T>::value and | ||
not has_mapped_type<T>::value, int>::type = 0> | ||
not detail::has_mapped_type<T>::value, int>::type = 0> | ||
T get_impl(T*) const | ||
{ | ||
if (is_array()) | ||
|
@@ -2664,7 +2790,7 @@ class basic_json | |
/// get an array (explicit) | ||
template<class T, typename std::enable_if< | ||
std::is_same<basic_json, typename T::value_type>::value and | ||
not has_mapped_type<T>::value, int>::type = 0> | ||
not detail::has_mapped_type<T>::value, int>::type = 0> | ||
T get_impl(T*) const | ||
{ | ||
if (is_array()) | ||
|
@@ -5829,7 +5955,7 @@ class basic_json | |
o.width(0); | ||
|
||
// fix locale problems | ||
const auto old_locale = o.imbue(std::locale(std::locale(), new DecimalSeparator)); | ||
const auto old_locale = o.imbue(std::locale(std::locale(), new detail::DecimalSeparator)); | ||
// set precision | ||
|
||
// 6, 15 or 16 digits of precision allows round-trip IEEE 754 | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
you probably want
uncvref_t
here as well.