From ca07788451c4d4f6512bc44ade7a30d5f40fc93e Mon Sep 17 00:00:00 2001 From: Andrey Tolstoy Date: Wed, 20 Sep 2023 04:27:10 +0700 Subject: [PATCH] Merge pull request #2695 from particle-iot/feature/wiring-json-64-bit-int [wiring] json: 64-bit support --- test/unit_tests/wiring/json.cpp | 159 ++++++++++++++++++++++++++++++- wiring/inc/spark_wiring_json.h | 5 + wiring/src/spark_wiring_json.cpp | 68 +++++++++++++ 3 files changed, 230 insertions(+), 2 deletions(-) diff --git a/test/unit_tests/wiring/json.cpp b/test/unit_tests/wiring/json.cpp index 59b47682da..127822d54b 100644 --- a/test/unit_tests/wiring/json.cpp +++ b/test/unit_tests/wiring/json.cpp @@ -54,6 +54,30 @@ class Checker { return *this; } + Checker& number64(long long val) { + const JSONValue v = value(); + REQUIRE(v.type() == JSON_TYPE_NUMBER); + CHECK(v.isNumber()); + CHECK(v.toInt64() == val); + return *this; + } + + Checker& number(unsigned val) { + const JSONValue v = value(); + REQUIRE(v.type() == JSON_TYPE_NUMBER); + CHECK(v.isNumber()); + CHECK(v.toUInt() == val); + return *this; + } + + Checker& number64(unsigned long long val) { + const JSONValue v = value(); + REQUIRE(v.type() == JSON_TYPE_NUMBER); + CHECK(v.isNumber()); + CHECK(v.toUInt64() == val); + return *this; + } + Checker& number(double val) { const JSONValue v = value(); REQUIRE(v.type() == JSON_TYPE_NUMBER); @@ -208,6 +232,29 @@ TEST_CASE("Parsing JSON") { check("-2147483648").number((int)-2147483648); // INT_MIN check("2147483647").number((int)2147483647); // INT_MAX } + SECTION("int64") { + check("0").number64((long long)0); + check("1").number64((long long)1); + check("-1").number64((long long)-1); + check("12345").number64((long long)12345); + check("-12345").number64((long long)-12345); + check("4294967295").number64((long long)UINT32_MAX); // UINT32_MAX + check("-9223372036854775808").number64((long long)INT64_MIN); // INT64_MIN + check("9223372036854775807").number64((long long)INT64_MAX); // INT64_MAX + } + SECTION("uint") { + check("0").number((unsigned)0); + check("1").number((unsigned)1); + check("12345").number((unsigned)12345); + check("4294967295").number((unsigned)UINT32_MAX); // UINT32_MAX + } + SECTION("uint64") { + check("0").number64((unsigned long long)0); + check("1").number64((unsigned long long)1); + check("12345").number64((unsigned long long)12345); + check("4294967295").number64((unsigned long long)UINT32_MAX); // UINT32_MAX + check("18446744073709551615").number64((unsigned long long)UINT64_MAX); // UINT64_MAX + } SECTION("float") { check("0.0").number(0.0); check("1.0").number(1.0); @@ -413,6 +460,96 @@ TEST_CASE("Writing JSON") { check(data).equals("2147483647"); } } + SECTION("int64") { + SECTION("0") { + json.value((long long)0); + check(data).equals("0"); + } + SECTION("1") { + json.value((long long)1); + check(data).equals("1"); + } + SECTION("-1") { + json.value((long long)-1); + check(data).equals("-1"); + } + SECTION("12345") { + json.value((long long)12345); + check(data).equals("12345"); + } + SECTION("-12345") { + json.value((long long)-12345); + check(data).equals("-12345"); + } + SECTION("-2147483648") { + json.value((long long)-2147483648); // INT_MIN + check(data).equals("-2147483648"); + } + SECTION("2147483647") { + json.value((long long)2147483647); // INT_MAX + check(data).equals("2147483647"); + } + SECTION("-9223372036854775808") { + json.value((long long)INT64_MIN); // INT64_MIN + check(data).equals("-9223372036854775808"); + } + SECTION("9223372036854775807") { + json.value((long long)INT64_MAX); // INT64_MAX + check(data).equals("9223372036854775807"); + } + } + SECTION("uint") { + SECTION("0") { + json.value((unsigned)0); + check(data).equals("0"); + } + SECTION("1") { + json.value((unsigned)1); + check(data).equals("1"); + } + SECTION("12345") { + json.value((unsigned)12345); + check(data).equals("12345"); + } + SECTION("2147483647") { + json.value((unsigned)2147483647); // INT_MAX + check(data).equals("2147483647"); + } + SECTION("4294967295") { + json.value((unsigned)UINT32_MAX); // UINT32_MAX + check(data).equals("4294967295"); + } + } + SECTION("uint64") { + SECTION("0") { + json.value((unsigned long long)0); + check(data).equals("0"); + } + SECTION("1") { + json.value((unsigned long long)1); + check(data).equals("1"); + } + SECTION("12345") { + json.value((unsigned long long)12345); + check(data).equals("12345"); + } + SECTION("2147483647") { + json.value((unsigned long long)2147483647); // INT_MAX + check(data).equals("2147483647"); + } + SECTION("4294967295") { + json.value((unsigned long long)UINT32_MAX); // UINT32_MAX + check(data).equals("4294967295"); + } + SECTION("9223372036854775807") { + json.value((unsigned long long)INT64_MAX); // INT64_MAX + check(data).equals("9223372036854775807"); + } + SECTION("18446744073709551615") { + json.value((unsigned long long)UINT64_MAX); // UINT64_MAX + check(data).equals("18446744073709551615"); + } + } SECTION("float") { SECTION("0.0") { json.value(0.0); @@ -864,6 +1001,9 @@ TEST_CASE("JSONValue") { check(v).invalid(); CHECK(v.toBool() == false); CHECK(v.toInt() == 0); + CHECK(v.toInt64() == 0); + CHECK(v.toUInt() == 0); + CHECK(v.toUInt64() == 0); CHECK(v.toDouble() == 0.0); CHECK(v.toString() == ""); } @@ -872,6 +1012,9 @@ TEST_CASE("JSONValue") { check(v).null(); CHECK(v.toBool() == false); CHECK(v.toInt() == 0); + CHECK(v.toInt64() == 0); + CHECK(v.toUInt() == 0); + CHECK(v.toUInt64() == 0); CHECK(v.toDouble() == 0.0); CHECK(v.toString() == ""); } @@ -880,6 +1023,9 @@ TEST_CASE("JSONValue") { const JSONValue v = parse("true"); check(v).boolean(true); CHECK(v.toInt() == 1); + CHECK(v.toInt64() == 1); + CHECK(v.toUInt() == 1); + CHECK(v.toUInt64() == 1); CHECK(v.toDouble() == 1.0); CHECK(v.toString() == "true"); } @@ -887,6 +1033,9 @@ TEST_CASE("JSONValue") { const JSONValue v = parse("false"); check(v).boolean(false); CHECK(v.toInt() == 0); + CHECK(v.toInt64() == 0); + CHECK(v.toUInt() == 0); + CHECK(v.toUInt64() == 0); CHECK(v.toDouble() == 0.0); CHECK(v.toString() == "false"); } @@ -895,14 +1044,20 @@ TEST_CASE("JSONValue") { SECTION("int") { SECTION("0") { const JSONValue v = parse("0"); - check(v).number(0); + check(v).number((int)0); + check(v).number64((long long)0); + check(v).number((unsigned)0); + check(v).number64((unsigned long long)0); CHECK(v.toBool() == false); CHECK(v.toDouble() == 0.0); CHECK(v.toString() == "0"); } SECTION("12345") { const JSONValue v = parse("12345"); - check(v).number(12345); + check(v).number((int)12345); + check(v).number64((long long)12345); + check(v).number((unsigned)12345); + check(v).number64((unsigned long long)12345); CHECK(v.toBool() == true); CHECK(v.toDouble() == 12345.0); CHECK(v.toString() == "12345"); diff --git a/wiring/inc/spark_wiring_json.h b/wiring/inc/spark_wiring_json.h index 9d63857ddd..bf8c32466c 100644 --- a/wiring/inc/spark_wiring_json.h +++ b/wiring/inc/spark_wiring_json.h @@ -56,6 +56,9 @@ class JSONValue { bool toBool() const; int toInt() const; + unsigned toUInt() const; + long long toInt64() const; + unsigned long long toUInt64() const; double toDouble() const; JSONString toString() const; @@ -176,7 +179,9 @@ class JSONWriter { JSONWriter& value(int val); JSONWriter& value(unsigned val); JSONWriter& value(long val); + JSONWriter& value(long long val); JSONWriter& value(unsigned long val); + JSONWriter& value(unsigned long long val); JSONWriter& value(double val, int precision); JSONWriter& value(double val); JSONWriter& value(const char *val); diff --git a/wiring/src/spark_wiring_json.cpp b/wiring/src/spark_wiring_json.cpp index b167268a8f..17dc898640 100644 --- a/wiring/src/spark_wiring_json.cpp +++ b/wiring/src/spark_wiring_json.cpp @@ -134,6 +134,60 @@ int spark::JSONValue::toInt() const { } } +unsigned spark::JSONValue::toUInt() const { + switch (type()) { + case JSON_TYPE_BOOL: { + const char* const s = d_->json + t_->start; + return *s == 't'; + } + case JSON_TYPE_NUMBER: + case JSON_TYPE_STRING: { + // toInt() may produce incorrect results for floating point numbers, since we want to keep + // compile-time dependency on strtod() optional + const char* const s = d_->json + t_->start; + return strtoul(s, nullptr, 10); + } + default: + return 0; + } +} + +long long spark::JSONValue::toInt64() const { + switch (type()) { + case JSON_TYPE_BOOL: { + const char* const s = d_->json + t_->start; + return *s == 't'; + } + case JSON_TYPE_NUMBER: + case JSON_TYPE_STRING: { + // toInt() may produce incorrect results for floating point numbers, since we want to keep + // compile-time dependency on strtod() optional + const char* const s = d_->json + t_->start; + return strtoll(s, nullptr, 10); + } + default: + return 0; + } +} + +unsigned long long spark::JSONValue::toUInt64() const { + switch (type()) { + case JSON_TYPE_BOOL: { + const char* const s = d_->json + t_->start; + return *s == 't'; + } + case JSON_TYPE_NUMBER: + case JSON_TYPE_STRING: { + // toInt() may produce incorrect results for floating point numbers, since we want to keep + // compile-time dependency on strtod() optional + const char* const s = d_->json + t_->start; + return strtoull(s, nullptr, 10); + } + default: + return 0; + } +} + double spark::JSONValue::toDouble() const { switch (type()) { case JSON_TYPE_BOOL: { @@ -477,6 +531,20 @@ spark::JSONWriter& spark::JSONWriter::value(unsigned long val) { return *this; } +spark::JSONWriter& spark::JSONWriter::value(long long val) { + writeSeparator(); + printf("%lld", val); + state_ = NEXT; + return *this; +} + +spark::JSONWriter& spark::JSONWriter::value(unsigned long long val) { + writeSeparator(); + printf("%llu", val); + state_ = NEXT; + return *this; +} + spark::JSONWriter& spark::JSONWriter::value(double val, int precision) { writeSeparator(); printf("%.*lf", precision, val);