Skip to content

Commit

Permalink
JsonDeserializer: use float when the value has few digits
Browse files Browse the repository at this point in the history
  • Loading branch information
bblanchon committed Sep 4, 2024
1 parent fd6314e commit 1f7a3f3
Show file tree
Hide file tree
Showing 8 changed files with 162 additions and 27 deletions.
24 changes: 21 additions & 3 deletions extras/tests/JsonDeserializer/array.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -69,14 +69,32 @@ TEST_CASE("deserialize JSON array") {
REQUIRE(arr[1] == 84);
}

SECTION("Double") {
SECTION("Float") {
DeserializationError err = deserializeJson(doc, "[4.2,1e2]");
JsonArray arr = doc.as<JsonArray>();

REQUIRE(err == DeserializationError::Ok);
REQUIRE(2 == arr.size());
REQUIRE(arr[0] == 4.2);
REQUIRE(arr[1] == 1e2);
REQUIRE(arr[0].as<float>() == Approx(4.2f));
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") {
Expand Down
42 changes: 42 additions & 0 deletions extras/tests/JsonDeserializer/errors.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@
#include <ArduinoJson.h>
#include <catch.hpp>

#include "Allocators.hpp"

TEST_CASE("deserializeJson() returns IncompleteInput") {
const char* testCases[] = {
// strings
Expand Down Expand Up @@ -118,3 +120,43 @@ TEST_CASE("deserializeJson() returns NoMemory if string length overflows") {
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);
}
}
16 changes: 14 additions & 2 deletions extras/tests/JsonDeserializer/object.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -155,15 +155,27 @@ TEST_CASE("deserialize JSON object") {
REQUIRE(obj["key2"] == -42);
}

SECTION("Float") {
DeserializationError err =
deserializeJson(doc, "{\"key1\":12.345,\"key2\":-7E3}");
JsonObject obj = doc.as<JsonObject>();

REQUIRE(err == DeserializationError::Ok);
REQUIRE(doc.is<JsonObject>());
REQUIRE(obj.size() == 2);
REQUIRE(obj["key1"].as<float>() == Approx(12.345f));
REQUIRE(obj["key2"] == -7E3f);
}

SECTION("Double") {
DeserializationError err =
deserializeJson(doc, "{\"key1\":12.345,\"key2\":-7E89}");
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"] == 12.345);
REQUIRE(obj["key1"].as<double>() == Approx(12.3456789));
REQUIRE(obj["key2"] == -7E89);
}

Expand Down
2 changes: 1 addition & 1 deletion extras/tests/JsonVariant/as.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -203,7 +203,7 @@ TEST_CASE("JsonVariant::as()") {

REQUIRE(variant.as<bool>() == true);
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<std::string>() == "4.2"_s);
REQUIRE(variant.as<JsonString>() == "4.2");
Expand Down
13 changes: 9 additions & 4 deletions extras/tests/Numbers/parseFloat.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -60,10 +60,15 @@ TEST_CASE("parseNumber<float>()") {

SECTION("VeryLong") {
checkFloat("0.00000000000000000000000000000001", 1e-32f);
checkFloat("100000000000000000000000000000000.0", 1e+32f);
checkFloat(
"100000000000000000000000000000000.00000000000000000000000000000",
1e+32f);

// The following don't work because they have many digits so parseNumber()
// treats them as double. But it's not an issue because JsonVariant will use
// a float to store them.
//
// checkFloat("100000000000000000000000000000000.0", 1e+32f);
// checkFloat(
// "100000000000000000000000000000000.00000000000000000000000000000",
// 1e+32f);
}

SECTION("NaN") {
Expand Down
16 changes: 14 additions & 2 deletions extras/tests/Numbers/parseNumber.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ TEST_CASE("Test unsigned integer overflow") {
}

REQUIRE(first.type() == NumberType::UnsignedInteger);
REQUIRE(second.type() == NumberType::Float);
REQUIRE(second.type() == NumberType::Double);
}

TEST_CASE("Test signed integer overflow") {
Expand All @@ -41,11 +41,23 @@ TEST_CASE("Test signed integer overflow") {
}

REQUIRE(first.type() == NumberType::SignedInteger);
REQUIRE(second.type() == NumberType::Float);
REQUIRE(second.type() == NumberType::Double);
}

TEST_CASE("Invalid value") {
auto result = parseNumber("6a3");

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);
}
26 changes: 20 additions & 6 deletions src/ArduinoJson/Json/JsonDeserializer.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -520,16 +520,30 @@ class JsonDeserializer {
auto number = parseNumber(buffer_);
switch (number.type()) {
case NumberType::UnsignedInteger:
result.setInteger(number.asUnsignedInteger(), resources_);
return DeserializationError::Ok;
if (result.setInteger(number.asUnsignedInteger(), resources_))
return DeserializationError::Ok;
else
return DeserializationError::NoMemory;

case NumberType::SignedInteger:
result.setInteger(number.asSignedInteger(), resources_);
return DeserializationError::Ok;
if (result.setInteger(number.asSignedInteger(), resources_))
return DeserializationError::Ok;
else
return DeserializationError::NoMemory;

case NumberType::Float:
result.setFloat(number.asFloat(), resources_);
return DeserializationError::Ok;
if (result.setFloat(number.asFloat(), resources_))
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:
return DeserializationError::InvalidInput;
Expand Down
50 changes: 41 additions & 9 deletions src/ArduinoJson/Numbers/parseNumber.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -21,18 +21,27 @@ enum class NumberType : uint8_t {
Invalid,
Float,
SignedInteger,
UnsignedInteger
UnsignedInteger,
#if ARDUINOJSON_USE_DOUBLE
Double,
#endif
};

union NumberValue {
NumberValue() {}
NumberValue(JsonFloat x) : asFloat(x) {}
NumberValue(float x) : asFloat(x) {}
NumberValue(JsonInteger x) : asSignedInteger(x) {}
NumberValue(JsonUInt x) : asUnsignedInteger(x) {}
#if ARDUINOJSON_USE_DOUBLE
NumberValue(double x) : asDouble(x) {}
#endif

JsonInteger asSignedInteger;
JsonUInt asUnsignedInteger;
JsonFloat asFloat;
float asFloat;
#if ARDUINOJSON_USE_DOUBLE
double asDouble;
#endif
};

class Number {
Expand All @@ -41,9 +50,12 @@ class Number {

public:
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(JsonUInt value) : type_(NumberType::UnsignedInteger), value_(value) {}
#if ARDUINOJSON_USE_DOUBLE
Number(double value) : type_(NumberType::Double), value_(value) {}
#endif

template <typename T>
T convertTo() const {
Expand All @@ -54,6 +66,10 @@ class Number {
return convertNumber<T>(value_.asSignedInteger);
case NumberType::UnsignedInteger:
return convertNumber<T>(value_.asUnsignedInteger);
#if ARDUINOJSON_USE_DOUBLE
case NumberType::Double:
return convertNumber<T>(value_.asDouble);
#endif
default:
return T();
}
Expand All @@ -73,10 +89,17 @@ class Number {
return value_.asUnsignedInteger;
}

JsonFloat asFloat() const {
float asFloat() const {
ARDUINOJSON_ASSERT(type_ == NumberType::Float);
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) {
Expand Down Expand Up @@ -192,10 +215,19 @@ inline Number parseNumber(const char* s) {
if (*s != '\0')
return Number();

JsonFloat final_result =
make_float(static_cast<JsonFloat>(mantissa), exponent);

return Number(is_negative ? -final_result : final_result);
#if ARDUINOJSON_USE_DOUBLE
bool isDouble = exponent < -FloatTraits<float>::exponent_max ||
exponent > FloatTraits<float>::exponent_max ||
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>
Expand Down

0 comments on commit 1f7a3f3

Please sign in to comment.