JsonDeserializer: use float when the value has few digits

This commit is contained in:
Benoit Blanchon
2024-09-04 14:33:14 +02:00
parent fd6314e132
commit 1f7a3f3174
8 changed files with 163 additions and 28 deletions

View File

@ -69,14 +69,32 @@ TEST_CASE("deserialize JSON array") {
REQUIRE(arr[1] == 84); REQUIRE(arr[1] == 84);
} }
SECTION("Double") { SECTION("Float") {
DeserializationError err = deserializeJson(doc, "[4.2,1e2]"); DeserializationError err = deserializeJson(doc, "[4.2,1e2]");
JsonArray arr = doc.as<JsonArray>(); JsonArray arr = doc.as<JsonArray>();
REQUIRE(err == DeserializationError::Ok); REQUIRE(err == DeserializationError::Ok);
REQUIRE(2 == arr.size()); REQUIRE(2 == arr.size());
REQUIRE(arr[0] == 4.2); REQUIRE(arr[0].as<float>() == Approx(4.2f));
REQUIRE(arr[1] == 1e2); REQUIRE(arr[1] == 1e2f);
REQUIRE(spy.log() == AllocatorLog{
Allocate(sizeofPool()),
Reallocate(sizeofPool(), sizeofPool(2)),
});
}
SECTION("Double") {
DeserializationError err = deserializeJson(doc, "[4.2123456,-7E89]");
JsonArray arr = doc.as<JsonArray>();
REQUIRE(err == DeserializationError::Ok);
REQUIRE(2 == arr.size());
REQUIRE(arr[0].as<double>() == Approx(4.2123456));
REQUIRE(arr[1] == -7E89);
REQUIRE(spy.log() == AllocatorLog{
Allocate(sizeofPool()),
Reallocate(sizeofPool(), sizeofPool(4)),
});
} }
SECTION("Unsigned long") { SECTION("Unsigned long") {

View File

@ -6,6 +6,8 @@
#include <ArduinoJson.h> #include <ArduinoJson.h>
#include <catch.hpp> #include <catch.hpp>
#include "Allocators.hpp"
TEST_CASE("deserializeJson() returns IncompleteInput") { TEST_CASE("deserializeJson() returns IncompleteInput") {
const char* testCases[] = { const char* testCases[] = {
// strings // strings
@ -118,3 +120,43 @@ TEST_CASE("deserializeJson() returns NoMemory if string length overflows") {
REQUIRE(err == DeserializationError::NoMemory); REQUIRE(err == DeserializationError::NoMemory);
} }
} }
TEST_CASE("deserializeJson() returns NoMemory if extension allocation fails") {
JsonDocument doc(FailingAllocator::instance());
SECTION("uint32_t should pass") {
auto err = deserializeJson(doc, "4294967295");
REQUIRE(err == DeserializationError::Ok);
}
SECTION("uint64_t should fail") {
auto err = deserializeJson(doc, "18446744073709551615");
REQUIRE(err == DeserializationError::NoMemory);
}
SECTION("int32_t should pass") {
auto err = deserializeJson(doc, "-2147483648");
REQUIRE(err == DeserializationError::Ok);
}
SECTION("int64_t should fail") {
auto err = deserializeJson(doc, "-9223372036854775808");
REQUIRE(err == DeserializationError::NoMemory);
}
SECTION("float should pass") {
auto err = deserializeJson(doc, "3.402823e38");
REQUIRE(err == DeserializationError::Ok);
}
SECTION("double should fail") {
auto err = deserializeJson(doc, "1.7976931348623157e308");
REQUIRE(err == DeserializationError::NoMemory);
}
}

View File

@ -155,15 +155,27 @@ TEST_CASE("deserialize JSON object") {
REQUIRE(obj["key2"] == -42); REQUIRE(obj["key2"] == -42);
} }
SECTION("Double") { SECTION("Float") {
DeserializationError err = DeserializationError err =
deserializeJson(doc, "{\"key1\":12.345,\"key2\":-7E89}"); deserializeJson(doc, "{\"key1\":12.345,\"key2\":-7E3}");
JsonObject obj = doc.as<JsonObject>(); JsonObject obj = doc.as<JsonObject>();
REQUIRE(err == DeserializationError::Ok); REQUIRE(err == DeserializationError::Ok);
REQUIRE(doc.is<JsonObject>()); REQUIRE(doc.is<JsonObject>());
REQUIRE(obj.size() == 2); REQUIRE(obj.size() == 2);
REQUIRE(obj["key1"] == 12.345); REQUIRE(obj["key1"].as<float>() == Approx(12.345f));
REQUIRE(obj["key2"] == -7E3f);
}
SECTION("Double") {
DeserializationError err =
deserializeJson(doc, "{\"key1\":12.3456789,\"key2\":-7E89}");
JsonObject obj = doc.as<JsonObject>();
REQUIRE(err == DeserializationError::Ok);
REQUIRE(doc.is<JsonObject>());
REQUIRE(obj.size() == 2);
REQUIRE(obj["key1"].as<double>() == Approx(12.3456789));
REQUIRE(obj["key2"] == -7E89); REQUIRE(obj["key2"] == -7E89);
} }

View File

@ -203,7 +203,7 @@ TEST_CASE("JsonVariant::as()") {
REQUIRE(variant.as<bool>() == true); REQUIRE(variant.as<bool>() == true);
REQUIRE(variant.as<long>() == 4L); REQUIRE(variant.as<long>() == 4L);
REQUIRE(variant.as<double>() == 4.2); REQUIRE(variant.as<double>() == Approx(4.2));
REQUIRE(variant.as<const char*>() == "4.2"_s); REQUIRE(variant.as<const char*>() == "4.2"_s);
REQUIRE(variant.as<std::string>() == "4.2"_s); REQUIRE(variant.as<std::string>() == "4.2"_s);
REQUIRE(variant.as<JsonString>() == "4.2"); REQUIRE(variant.as<JsonString>() == "4.2");

View File

@ -60,10 +60,15 @@ TEST_CASE("parseNumber<float>()") {
SECTION("VeryLong") { SECTION("VeryLong") {
checkFloat("0.00000000000000000000000000000001", 1e-32f); checkFloat("0.00000000000000000000000000000001", 1e-32f);
checkFloat("100000000000000000000000000000000.0", 1e+32f);
checkFloat( // The following don't work because they have many digits so parseNumber()
"100000000000000000000000000000000.00000000000000000000000000000", // treats them as double. But it's not an issue because JsonVariant will use
1e+32f); // a float to store them.
//
// checkFloat("100000000000000000000000000000000.0", 1e+32f);
// checkFloat(
// "100000000000000000000000000000000.00000000000000000000000000000",
// 1e+32f);
} }
SECTION("NaN") { SECTION("NaN") {

View File

@ -23,7 +23,7 @@ TEST_CASE("Test unsigned integer overflow") {
} }
REQUIRE(first.type() == NumberType::UnsignedInteger); REQUIRE(first.type() == NumberType::UnsignedInteger);
REQUIRE(second.type() == NumberType::Float); REQUIRE(second.type() == NumberType::Double);
} }
TEST_CASE("Test signed integer overflow") { TEST_CASE("Test signed integer overflow") {
@ -41,7 +41,7 @@ TEST_CASE("Test signed integer overflow") {
} }
REQUIRE(first.type() == NumberType::SignedInteger); REQUIRE(first.type() == NumberType::SignedInteger);
REQUIRE(second.type() == NumberType::Float); REQUIRE(second.type() == NumberType::Double);
} }
TEST_CASE("Invalid value") { TEST_CASE("Invalid value") {
@ -49,3 +49,15 @@ TEST_CASE("Invalid value") {
REQUIRE(result.type() == NumberType::Invalid); REQUIRE(result.type() == NumberType::Invalid);
} }
TEST_CASE("float") {
auto result = parseNumber("3.402823e38");
REQUIRE(result.type() == NumberType::Float);
}
TEST_CASE("double") {
auto result = parseNumber("1.7976931348623157e308");
REQUIRE(result.type() == NumberType::Double);
}

View File

@ -520,16 +520,30 @@ class JsonDeserializer {
auto number = parseNumber(buffer_); auto number = parseNumber(buffer_);
switch (number.type()) { switch (number.type()) {
case NumberType::UnsignedInteger: case NumberType::UnsignedInteger:
result.setInteger(number.asUnsignedInteger(), resources_); if (result.setInteger(number.asUnsignedInteger(), resources_))
return DeserializationError::Ok; return DeserializationError::Ok;
else
return DeserializationError::NoMemory;
case NumberType::SignedInteger: case NumberType::SignedInteger:
result.setInteger(number.asSignedInteger(), resources_); if (result.setInteger(number.asSignedInteger(), resources_))
return DeserializationError::Ok; return DeserializationError::Ok;
else
return DeserializationError::NoMemory;
case NumberType::Float: case NumberType::Float:
result.setFloat(number.asFloat(), resources_); if (result.setFloat(number.asFloat(), resources_))
return DeserializationError::Ok; return DeserializationError::Ok;
else
return DeserializationError::NoMemory;
#if ARDUINOJSON_USE_DOUBLE
case NumberType::Double:
if (result.setFloat(number.asDouble(), resources_))
return DeserializationError::Ok;
else
return DeserializationError::NoMemory;
#endif
default: default:
return DeserializationError::InvalidInput; return DeserializationError::InvalidInput;

View File

@ -21,18 +21,27 @@ enum class NumberType : uint8_t {
Invalid, Invalid,
Float, Float,
SignedInteger, SignedInteger,
UnsignedInteger UnsignedInteger,
#if ARDUINOJSON_USE_DOUBLE
Double,
#endif
}; };
union NumberValue { union NumberValue {
NumberValue() {} NumberValue() {}
NumberValue(JsonFloat x) : asFloat(x) {} NumberValue(float x) : asFloat(x) {}
NumberValue(JsonInteger x) : asSignedInteger(x) {} NumberValue(JsonInteger x) : asSignedInteger(x) {}
NumberValue(JsonUInt x) : asUnsignedInteger(x) {} NumberValue(JsonUInt x) : asUnsignedInteger(x) {}
#if ARDUINOJSON_USE_DOUBLE
NumberValue(double x) : asDouble(x) {}
#endif
JsonInteger asSignedInteger; JsonInteger asSignedInteger;
JsonUInt asUnsignedInteger; JsonUInt asUnsignedInteger;
JsonFloat asFloat; float asFloat;
#if ARDUINOJSON_USE_DOUBLE
double asDouble;
#endif
}; };
class Number { class Number {
@ -41,9 +50,12 @@ class Number {
public: public:
Number() : type_(NumberType::Invalid) {} Number() : type_(NumberType::Invalid) {}
Number(JsonFloat value) : type_(NumberType::Float), value_(value) {} Number(float value) : type_(NumberType::Float), value_(value) {}
Number(JsonInteger value) : type_(NumberType::SignedInteger), value_(value) {} Number(JsonInteger value) : type_(NumberType::SignedInteger), value_(value) {}
Number(JsonUInt value) : type_(NumberType::UnsignedInteger), value_(value) {} Number(JsonUInt value) : type_(NumberType::UnsignedInteger), value_(value) {}
#if ARDUINOJSON_USE_DOUBLE
Number(double value) : type_(NumberType::Double), value_(value) {}
#endif
template <typename T> template <typename T>
T convertTo() const { T convertTo() const {
@ -54,6 +66,10 @@ class Number {
return convertNumber<T>(value_.asSignedInteger); return convertNumber<T>(value_.asSignedInteger);
case NumberType::UnsignedInteger: case NumberType::UnsignedInteger:
return convertNumber<T>(value_.asUnsignedInteger); return convertNumber<T>(value_.asUnsignedInteger);
#if ARDUINOJSON_USE_DOUBLE
case NumberType::Double:
return convertNumber<T>(value_.asDouble);
#endif
default: default:
return T(); return T();
} }
@ -73,10 +89,17 @@ class Number {
return value_.asUnsignedInteger; return value_.asUnsignedInteger;
} }
JsonFloat asFloat() const { float asFloat() const {
ARDUINOJSON_ASSERT(type_ == NumberType::Float); ARDUINOJSON_ASSERT(type_ == NumberType::Float);
return value_.asFloat; return value_.asFloat;
} }
#if ARDUINOJSON_USE_DOUBLE
double asDouble() const {
ARDUINOJSON_ASSERT(type_ == NumberType::Double);
return value_.asDouble;
}
#endif
}; };
inline Number parseNumber(const char* s) { inline Number parseNumber(const char* s) {
@ -192,10 +215,19 @@ inline Number parseNumber(const char* s) {
if (*s != '\0') if (*s != '\0')
return Number(); return Number();
JsonFloat final_result = #if ARDUINOJSON_USE_DOUBLE
make_float(static_cast<JsonFloat>(mantissa), exponent); bool isDouble = exponent < -FloatTraits<float>::exponent_max ||
exponent > FloatTraits<float>::exponent_max ||
return Number(is_negative ? -final_result : final_result); mantissa > FloatTraits<float>::mantissa_max;
if (isDouble) {
auto final_result = make_float(double(mantissa), exponent);
return Number(is_negative ? -final_result : final_result);
} else
#endif
{
auto final_result = make_float(float(mantissa), exponent);
return Number(is_negative ? -final_result : final_result);
}
} }
template <typename T> template <typename T>