-
-
Notifications
You must be signed in to change notification settings - Fork 6.7k
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
Do not throw exception when default_value's type does not match the actual type #278
Comments
Thanks for reporting. I'll have a look. |
This has been causing me issues too, this addition would be a great help. |
I understand your use case, but it is different to the described behavior of the
(Emphasis added). In your example, there exists an entry with key I am not sure whether your proposed "catch all and return Consider for instance an object that stores numbers and strings. If you ask |
@nlohmann That's true if the user cares about error handling. For many real cases, this is just inconvenient. If we really care about whether there's an error, there're plenty of other methods for us to find that out, and providing a default value means we really don't want any exceptions. |
I'm not sure I agree with that last statement. Providing a different value that what's stored because the caller asked for the wrong type doesn't seem right. |
What about only if the value is null? To me it makes sense for .value to return the default value if the value is null. For my usage, there is an API that returns JSON, but a certain string is set to null instead of not being present if it is not relevant, and I want to use the default value for this. |
what about having a nothrow version of |
We could have a nothrow version |
This bugs me as well, I would like to see much more permissives functions to avoid code like this: auto it = object.find("property");
if (it != object.end() && it->is_string())
myvalue = *it; I would like to see much more permissives implicit conversions functions that also allow casting to default values if not. json j; // null
int i = j; // i = 0 This will help us deserialize untrusted input much easier! Like this: auto port = object["network"].value("port", 6667); What do you think? |
In that case, untrusted input, would you want that to do a string to number conversion if port were a string?
It could be as, cast, value_as, ... |
With this post, I try to summarize the discussion. Please correct me if I misunderstood. Using the example of @markand, the current auto port = object["network"].value("port", 6667);
The current discussion shows that case 3 makes people unhappy. Right now, it seems we have three possibilities:
Apparently, “solution” 1 leaves people unhappy… However, it also has the most unsurprising behavior. For solution 2, I already expressed my concerns that it may be confusing if the default value is returned even if the requested value Solution 3 seems to be an implicit conversion from the stored value to the type of the default value. Here, I see an issue: Such a conversion only makes sense for few types. While a conversion from number types is straightforward, it may already be confusing to assume an implicit conversion from booleans or I added Solution 4 after the comment of @gregmarr. [edit] Did I miss anything? |
4 Leave the It's roughly equivalent to the differences between Int.Parse and Int.TryParse in C#. |
@gregmarr What do you mean with "tries to convert to the type of the default value"? Apart from "add another function", isn't this what I described with possibility 3 (note: I edited the original comment, because the sentence stopped in the middle). |
It's very similar, I just made it explicit by using a different function instead of implicit with the existing function. |
What about an overload of
Alternatively, we can just add a function that takes a json object as default value and compare the types directly from the json default value argument. auto v = object.value("port", 123); // 123 is a json object.
// v = 123 if port is not present or not of type int. |
@markand I like this approach. I'll have a look. |
I implemented the proposal of @markand with a slight change: instead of having the user provide a type, the type is derived from the default value. There are two cases:
An exception is only thrown if the function is called on a JSON value which is not an object. There are still some issues with respect to number types: currently, The function (ignore the name for now) looks like this: template <class ValueType, typename
std::enable_if<
std::is_convertible<basic_json_t, ValueType>::value
, int>::type = 0>
ValueType value_with_type(const typename object_t::key_type& key,
ValueType default_value) const
{
// derive JSON value type from given default value
const value_t return_value_type = basic_json(default_value).type();
// at only works for objects
if (is_object())
{
// if key is found and stored value has the same type as the
// default value, return value and given default value otherwise
const auto it = find(key);
if (it != end() and it->type() == return_value_type)
{
return *it;
}
else
{
return default_value;
}
}
else
{
throw std::domain_error("cannot use value() with " + type_name());
}
} Here are some test cases: json j =
{
{"port", 1234}, {"proxy", true}
};
int port1 = j.value_with_type("port", 8080);
int port2 = j.value_with_type("the_port", 8080);
int port3 = j.value_with_type("proxy", 8080);
bool proxy1 = j.value_with_type("proxy", false);
bool proxy2 = j.value_with_type("the_proxy", false);
bool proxy3 = j.value_with_type("port", false);
CHECK(port1 == 1234);
CHECK(port2 == 8080);
CHECK(port3 == 8080);
CHECK(proxy1 == true);
CHECK(proxy2 == false);
CHECK(proxy3 == false); What do you think? |
This solves the issue for me. To me it doesn't matter whether it's a new function (and I don't really know whether it should be 😕) |
It is very nice. @jackb-p a behaviour change in a public function is considered as breaking change and thus should require a major version upgrade (if following semantic versioning). For me there is no problem if it is a new function, it's much more explicit. |
I wouldn't mind putting a changed function into the 3.0.0 release. As #244 would also introduce changes to the public API, a major release should be not too far away. |
Any further opinion on this?
I currently prefer the first option, because the change affects a corner case that no-one really thought about in the first place. |
Do you mean replacing |
While I appreciate the semantic consistency you're stressing, I think the meaning of For example, I would argue that To that end, I think the only time I don't know if this is hard to implement, but my thinking is at the higher level about what the point of |
@clwill Let's not thing about how hard implementation is - it's more important to come up with semantics that do surprise the caller. |
Then in that case, I think throwing errors should be reserved for coding issues, not data issues. That is, missing/wrong data (as in the original form of this issue -- the data type of the found object) shouldn't throw, it should return the default. As the original poster put it:
I think that's pretty much where I am too. |
@clwill If the data is there but the wrong type, how do you tell whether it's a coding issue or a data issue? If the data is known to be correct, then this is a coding issue, because the caller is using the wrong type to look it up. |
I'm confused, if the data is known to be correct, why are you using |
It can be known to be correct and still have optional entries. |
in reply to @nlohmann comment: #278 (comment) Please, don't forget about a sub-case of 3: |
I'm sorry we discussed thus issue for so long and still did not find a solution. I just read this thread again and it seems to me that we spent a lot of time discussing the case what should happen if the type of the default value does not match the stored value if it is present. The thing is: the stored value is a JSON value, so why shouldn't the default value be a JSON value itself. The resulting function would have the same semantics as Python's
The name basic_json get_value(const typename object_t::key_type& key,
basic_json default_value = nullptr) const
{
// get_value only works for objects
if (is_object())
{
// if key is found return stored value, or default value otherwise
const auto it = find(key);
if (it != end())
{
return *it;
}
return default_value;
}
else
{
JSON_THROW(type_error::create(306, "cannot use get_value() with " + type_name()));
}
} Any opinions on this? |
I'm not sure if a default parameter is great here. Also, do we really want to return a // omitting boilerplate: U must be convertible to T
template <typename T = basic_json, typename U = void>
T get_value(const typename object_t::key_type& key, U&& default_value) const { /* ... */ } This looks a bit like EDIT: Instead of adding a |
Thanks for the quick reply!
|
To be honest I did not read the whole thread with scrutiny (I scrolled quite fast) Here are some calling code I had in mind while writing this reply: j.get_value("key", nullptr); // T is defaulted to basic_json, will return a basic_json
j.get_value<vector<string>>("key", {"default", "value"}); Since So the logical conclusion (for me, at least) would be to However, this will simply not work for users using |
Now I see what you are up to with the template parameters ;) The second example is exactly the issue I wanted to avoid in the first place. If Another question: do you think whether it is (a) possible and (b) a good idea to implement And: I still like the idea of returning |
Hmm, I gave it some thought, and I think you're right about the second example, we should throw if there is a type mismatch. I do still believe we could use the prototype I wrote earlier, instead of restricting it to About implementing However, a |
As long as the version that returns |
I have changed to limp along with a "test first" strategy. Not "what's the value, apply default in case" approach but "is there value? No value? Fine, apply default. Is value? Use it."
I'm building code on an embedded device and am doing my damnedest to avoid having exceptions thrown for anything other than real hard core exceptions -- such as hardware failures. This code is simply for device parameter settings, and it seems to me that throwing exceptions when a default value is missing is awfully dramatic.
So, if there is any option I can get where throwing exceptions is a truly last resort kind of thing (e.g. a completely malformed request), then I'm good.
… On Jul 9, 2017, at 12:00 PM, Niels Lohmann ***@***.***> wrote:
As long as the version that returns basic_json does not throw, I am happy. I wonder what the others from this thread think.
—
You are receiving this because you were mentioned.
Reply to this email directly, view it on GitHub <#278 (comment)>, or mute the thread <https://github.com/notifications/unsubscribe-auth/ABROJVcqK1C2I31TZa0V7qFmn7JvQigNks5sMSNbgaJpZM4JF_GV>.
|
This issue has been automatically marked as stale because it has not had recent activity. It will be closed if no further activity occurs. Thank you for your contributions. |
This issue has been automatically marked as stale because it has not had recent activity. It will be closed if no further activity occurs. Thank you for your contributions. |
Hello, I totally agree with @nlohmann in one point: Sorry for heating up this thread, and even more if there is already a solution, but I am currently facing exactly this dilemma. Thanks for the great library & cheers! |
I currently have the issue, that I have to parse a lot of json from various sources. Sometimes there is a mistake in one of the sending clients and they send an integer as a string or null instead of an integer, etc. I would prefer it if there were some functionality to give me a value, if the json value is of the expected type and exists and in any error case returns me a default value. Currently there seems to be no functionality like that and I need to write my own function, that combines |
What would be the signature of that function? |
The signature would basically be the same as
|
It could be possible to rely on overload with a extra type as parameter. struct NoTypeError {};
template <typename T>
T value(std::string, T default, NoTypeError); |
Thanks for this great library. I have been quite happy about almost every aspect of the library, except one point about the
value
method on object.Consider this sample code.
When I'm using
value
to specify a default value of a key, I pretty much don't care about whether it exists or not, whether it is a valid value or not. It's pretty much like, if it failed to fetch a value of a specific type, then return the default_value I specified.So I'm thinking about changing the following method:
https://github.com/nlohmann/json/blob/develop/src/json.hpp#L3653
to something like this:
Which I think will make the
value
method a lot easier to use. What do you think? @nlohmannThe text was updated successfully, but these errors were encountered: