-
-
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
Feature/user defined types #338
Conversation
|
||
template <typename T> | ||
using remove_reference_t = typename std::remove_reference<T>::type; | ||
|
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.
}; | ||
|
||
template <typename T> | ||
constexpr T __static_const<T>::value; |
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.
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 comment
The 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
: 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 comment
The 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 basic_json
objects from all types that are not basic_json
, since in case somebody has a different kind of representation in a string (e.g. XML), they could still define their own trait class to construct basic_json
objects from that.
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.
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 comment
The 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 value_type
, begin
and end
). I think it would be still a good idea to at least move this constructor out to check that ADL works.
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.
Are you talking about the constructor taking CompatibleArrayType&
as a parameter?
Could you post a short pseudo-code about what you have in mind?
@@ -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 comment
The 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 comment
The reason will be displayed to describe this comment to others. Learn more.
Indeed, forgot to check that, my bad
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 comment
The 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 basic_json
:
// 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 from
calls from_impl(t, (int)0)
, which will try to call the overload from_impl(T, int)
(which is a better match that the overload with long). If that works, then that is the one that is called. But if in that overload json_trait<T>::from
fails, e.g., because there is no specialization for T
, that overload will be silently removed, and then the second overload from_impl(T, long)
will kick in, because there is an implicit conversion from int
to long
.
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 json_traits
implement something that you need, and fall back to some other solution if it doesn't.
(If you want to check some examples that actually work, a good example is how begin
and end
are implemented in range-v3, or in STL2).
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.
If I understand correctly, the from_impl
method aims to replace the two get_impl
overloads that I added? I agree this is a lot more readable than what I have now.
// 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 comment
The reason will be displayed to describe this comment to others. Learn more.
I'll think about this, you make good points here.
Thanks for working on this. I will try to take a more detailed look tomorrow at it, but the tests look good :) |
I'm also still asking myself whether I prefer the It would be nice to have a discussion about the pros and cons of both methods. |
I think @d-frey wanted to be able to serialize/deserialize the same type in different ways to json objects. I agree with him in that to do that the best thing to do is to have a template parameter that controls this in We could also, inside It takes a lot of time and effort (and rewrites) to experiment with these things, but I guess once we get a bit of experience with it the best solution would become more clear. |
Good point, I shall experiment with the current way first then. Thanks! |
5544568
to
9d290c6
Compare
Hmpf, it seems VS2015 struggles with ADL... It compiles after specifying |
I'm really amazed about the effort and the discussion. I won't find the time to have a deeper look at this in the next days, but I am looking forward to! Keep up the great work! |
Like all compiler workarounds, this one is a bit surprising... |
I tried to provide some overloads for commonly used types, however it needs to rewrite a lot of code so I discarded that. |
Also, before trying to implement the This allows to specialize on multiple One of the cons is readability though: template <>
template <>
struct basic_json<>::json_traits<MyType>
{}; Is it worth going so far now? Should we support this after a first release? |
I don't know if I understood you correctly, but if what you mean is that one user might want to provide different behavior in the template <typename T> struct json_traits {};
template <>
struct json_traits<MyType> {
template <typename JSON>
static void from(JSON const& j, MyType& m) {
if constexpr (Same<string_type_t<JSON>, std::wstring>) {
// do something if the string type is std::wstring
} else {
// do something else otherwise
}
}
template <typename JSON>
static void to(MyType const& m, JSON& j) { ... }
}; I just used a concept ( Commodity template aliases like |
Thanks for your reply. Sorry I didn't have time to check the PR last week, I will try to work on it this week. I will start another PR for the other implementation. |
I didn't see in the documentation...Is there functionality to convert libconfig to json? Any desire to add that functionality? |
Continued in #355. |
WIP: This PR is NOT ready to be merged, its purpose is to facilitate review/discussions.
Files to change
There are currently two files which need to be edited:
src/json.hpp.re2c
(note the.re2c
suffix) - This file contains a comment section which describes the JSON lexic. This section is translated byre2c
into filesrc/json.hpp
which is plain "vanilla" C++11 code. (In fact, the generated lexer consists of some hundred lines ofgoto
s, which is a hint you never want to edit this file...).If you only edit file
src/json.hpp
(without the.re2c
suffix), your changes will be overwritten as soon as the lexer is touched again. To generate thesrc/json.hpp
file which is actually used during compilation of the tests and all other code, please executeTo run
re2c
and generate/overwrite filesrc/json.hpp
with your changes in filesrc/json.hpp.re2c
.test/src/unit.cpp
- This contains the Catch unit tests which currently cover 100 % of the library's code.If you add or change a feature, please also add a unit test to this file. The unit tests can be compiled with
and can be executed with
The test cases are also executed with several different compilers on Travis once you open a pull request.
Please understand that I cannot accept pull requests changing only file
src/json.hpp
.Note
Please don't
src/json.hpp
-- please read the paragraph above and understand whysrc/json.hpp.re2c
exists.#ifdef
s or other means.