I am playing around with JsonCpp and have noticed weird behavior when getting to the end of uint64 range.
Numbers in range works properly.
Numbers above range but between [2**64,2**64+2**11]
return 0.
Numbers above 2**64+2**11
throws an exception.
Is that expected? Can I configure JsonCpp to do those validations for me?
Thanks in advance!
Below is a code snippet that demonstrate the issue.
#include <iostream>
#include <jsoncpp/json/json.h>
#include <jsoncpp/json/reader.h>
#include <jsoncpp/json/value.h>
int main() {
//std::string uint64Str = "1234"; // any number in range [0, 2**64-1] works correctly (prints uint64Str)
//std::string uint64Str = "18446744073709551615"; // any number in range [0, 2**64-1] works correctly, including 2**64-1 (prints uint64Str)
//std::string uint64Str = "18446744073709551616"; //max uint64 + 1 print 0
//std::string uint64Str = "18446744073709551617"; //max uint64 + 2 print 0
// ...
//std::string uint64Str = "18446744073709553663"; //max uint64 + 2048 print 0
//std::string uint64Str = "18446744073709553664"; //max uint64 + 2049 print 0
std::string uint64Str = "18446744073709553665"; //max uint64 + 2050 (or more) throws instance of 'Json::LogicError' what(): double out of UInt64 range
Json::Value root;
Json::Reader reader;
bool parsingResult = reader.parse("{\"key\":"+uint64Str+"}", root);
std::cout<<root["key"].asUInt64()<<std::endl;
return 0;
}
Thanks to TedLyngmo for the guidance.
After his answer and additional research. Numbers that are bigger than max are stored in a real (double).
And then this code runs:
case realValue:
JSON_ASSERT_MESSAGE(InRange(value_.real_, 0, maxUInt64),
"double out of UInt64 range");
return UInt64( value_.real_ );
I would expect the function InRange
to fail on numbers out of range.
However its implementation is:
template <typename T, typename U>
static inline bool InRange(double d, T min, U max) {
// The casts can lose precision, but we are looking only for
// an approximate range. Might fail on edge cases though. ~cdunn
return d >= static_cast<double>(min) && d <= static_cast<double>(max);
}
It converts max to double and it rounds max up (to 2**64+2
) and if d is between max<uint64> to 2**62+2**11
is is also rounded (to 2**64+2
).
So it thinks it is in range.
When converting d to uint64_t you get max+1 which is 0.
The function InRange
do mention that is in not exact... yet, I consider that to be a bug in the library.
The solution that I am going to use is:
uint64_t getUint64(const Json::Value& shouldBeUInt64) {
uint64_t res = shouldBeUInt64.asUInt64();
if (res == 0 and shouldBeUInt64.asDouble()>0){
throw Json::LogicError("double out of UInt64 range");
}
return res;
}
Update (25 Nov 2023): See suggested fix here: https://github.com/open-source-parsers/jsoncpp/pull/1519