-
Notifications
You must be signed in to change notification settings - Fork 2.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
isDouble() returns true for integer number. #1361
Comments
Here is some further testing: for(Json::Value::const_iterator it = root.begin(); it != root.end(); ++it) (gdb) p key |
I believe this is working as intended. The |
@BillyDonahue thank you for the reply and the explanation regarding the JSON format description. I am by no means an expert on it so I appreciate the information you shared. As I understand it now, given that JSON is text based, it would not be incumbent on the JSON standard to identify value types since it is carrying strings. However, it seems to me that a library implementation, such as jsoncpp, with some work can identify data types and tag them accordingly when it holds them in memory. Obviously going from string to primitive and identifying a type can be tricky, but in going from primitive to JsonValue the work is handled by the language/compiler using the overloaded methods/constructors: Value::Value(Int value) {
initBasic(intValue);
value_.int_ = value;
}
Value::Value(double value) {
initBasic(realValue);
value_.real_ = value;
} This code can set two different values into the value_ union, but the type is correctly set for each and stored with the value. The problem is that later, calling isDouble() on both values would return true because the method tests both types: bool Value::isDouble() const {
return type() == intValue || type() == uintValue || type() == realValue;
} Would you mind explaining why this method cannot be reworked to checking the expected type: bool Value::isDouble() const {
return type() == realValue;
} Thank you again for your patience and time :). |
I got tripped up by this today, but only because the first documentation page I found when googling was https://jsoncpp.sourceforge.net/class_json_1_1_value.html#ec4f74ef7b776b1d9c8a10fc3bb4add5 , and that's very outdated, it's from jsoncpp 0.5.0 if I can believe the sourceforge download package. For reference, this commit from (cough ) 13 years ago (json 0.7.0) changed the behavior |
I think I will chime in here and say this behavior is incorrect. If JSON only has a concept of "numeric", then that check is filled by the OR The |
I think that is sort of my point. If isDouble() is not covered by the standard it shouldn't matter as long as the standard is being met by isNumeric(). In this case isDouble() is a non standard extension available from this library. In this case it should provide the expected behavior given its name. If it can't or won't be made to provide the expected behavior then it should not be available, although in my opinion this would be an unnecessary loss of functionality. |
Sounds like we are all saying the same thing. However, the code for isDouble() returns true for any numeric, and isNumeric() defers to isDouble(). |
|
That's an interesting way to think about it. Then should the semantics of the name be addressed to resolve the issue? I mean in this case it should be removed because that is the same behavior as isNumeric() but with ambiguity given its name. |
Well maybe but we don't really want an API break over it. Better to just improve the docs if necessary. |
Seems like there are a few things going on here. First, there are the types as defined in JSON: integer and number. Second, there are types as understood by computers: int, uint, float, double, etc. Having "is" functions for integer and number makes sense with respect to Json::Value objects. Having "as" functions to get the Json::Value as a specific C++ data type also makes sense. The complications arise when trying to test if a Json::Value "is" a C++ data type. I sort of see the rational of taking the "isC++Type" pattern to mean "can it be a C++ type?", but it's not an obvious interpretation. Given that this is a C++ library with C++ types at its core, I think it makes more sense to let these functions reflect the implementation internals. Then I can make decisions about how to extract the information without loss of precision (with respect to whatever internal data type was used to store the original value) or make an informed decision that the extraction may incur loss of precision. |
I think that's a good point. At the core of the issue it seems to me the key is to determine if there will or will not be loss of precision when retrieving the JSON value into the C++ domain. The use case that ran into and triggered this issue involved converting JSON data into binary with the goal of optimizing memory usage so being able to take an integer from JSON and then pack it in the smallest integer unit was essential for what we needed to achieve. Maintain precision would fit the bill here. |
This is my primary concern with merging the linked pull request. |
Maybe save it for the next major release? I still stand by my assertion that the changes are overall beneficial and make the API more intuitive. |
Saving for major release makes sense if there is concern that the API change may break code 👍🏽. |
Frankly I would have assumed that JSON numbers are always doubles because that's what being a subset of JS would imply but indeed the specifications do not require this, see https://blog.trl.sn/blog/what-is-a-json-number/. I think Conversely, The current code does not quite do that: It will consider 64-bit integers which can't be exactly represented by doubles, doubles. With the proposed PR, we would get:
To wrap it up: if (x.isDouble()) {
double y = x.asDouble();
...
} should be fine, otherwise the API would be unintuitive; I'm not fond of the linked PR. Currently it's merely somewhat questionable w.r.t 64-bit integers which could lose precision; a major release would be a good opportunity to address that. I don't think the internal type tags are very useful for numbers since as I mentioned before, there is no real concept of a storage type in JSON, but if someone does want to query them, they can just use |
Describe the bug
When integer numbers are used to instantiate a Json::Value object the query for isDouble() on that object returns true.
To Reproduce
I modified a portion of the src/test_lib_json/main.cpp file to force the testing. But this can be easily reproduced with simple code such as:
int main(int argc, char *argv[]) { int v(1234); Json::Value jv(v); if(jv.isDouble() == true) { } }
JSONTEST_ASSERT_EQUAL(Json::Value(1234).isIntegral(), true); //<--- PASSES
JSONTEST_ASSERT_EQUAL(Json::Value(1234).isDouble(), false); //<--- FAILS (320)
JSONTEST_ASSERT_EQUAL(Json::Value(1234).isIntegral(), false); //<--- FAILS (321)
JSONTEST_ASSERT_EQUAL(Json::Value(1234).isDouble(), true); //<--- PASSES
int v(1234);
JSONTEST_ASSERT_EQUAL(Json::Value(v).isIntegral(), true); //<--- PASSES
JSONTEST_ASSERT_EQUAL(Json::Value(v).isDouble(), false); //<--- FAILS (326)
JSONTEST_ASSERT_EQUAL(Json::Value(v).isIntegral(), false); //<--- FAILS (327)
JSONTEST_ASSERT_EQUAL(Json::Value(v).isDouble(), true); //<--- PASSES
std::cout << "With Double" << std::endl;
JSONTEST_ASSERT_EQUAL(Json::Value(1234.56).isIntegral(), false);//<--- PASSES
JSONTEST_ASSERT_EQUAL(Json::Value(1234.56).isDouble(), true); //<--- PASSES
JSONTEST_ASSERT_EQUAL(Json::Value(1234.56).isIntegral(), true); //<--- FAILS (333)
JSONTEST_ASSERT_EQUAL(Json::Value(1234.56).isDouble(), true); //<--- PASSES
Json::Value array1_;
array1_.append(1234);
JSONTEST_ASSERT_EQUAL(array1_[0].isIntegral(), true); //<--- PASSES
JSONTEST_ASSERT_EQUAL(array1_[0].isDouble(), false); //<--- FAILS (337)
Results
src/test_lib_json/main.cpp(320): Json::Value(1234).isDouble() == false
Expected: true
Actual : false
src/test_lib_json/main.cpp(321): Json::Value(1234).isIntegral() == false
Expected: true
Actual : false
src/test_lib_json/main.cpp(326): Json::Value(v).isDouble() == false
Expected: true
Actual : false
src/test_lib_json/main.cpp(327): Json::Value(v).isIntegral() == false
Expected: true
Actual : false
src/test_lib_json/main.cpp(333): Json::Value(1234.56).isIntegral() == true
Expected: false
Actual : true
src/test_lib_json/main.cpp(337): array1_[0].isDouble() == false
Expected: true
Actual : false
Expected behavior
If the number added is an integer then identifying it as a double should fail.
Desktop (please complete the following information):
OS - Rocky Linux
Version - 1.9.5 built with CMake.
Additional context
I am using the jsoncpp library to encode and decode information. When the data is decoded it is imperative that we know the type since our use case has specific logic that runs for different data types. As such, our tests fail when an integer value is encoded with jsoncpp and returns true when queried with isDouble() on the receiving end.
The text was updated successfully, but these errors were encountered: