From 920786a35680194fc41a20da6069081b39aba2a0 Mon Sep 17 00:00:00 2001 From: Brett Christensen Date: Tue, 13 Feb 2024 18:40:53 +1100 Subject: [PATCH 1/6] add hostname to mqtt ids. Use arduinojson library --- Software/Software.ino | 1 + Software/src/devboard/mqtt/mqtt.cpp | 180 +- Software/src/devboard/mqtt/mqtt.h | 2 +- .../lib/bblanchon-ArduinoJson/ArduinoJson.h | 7578 +++++++++++++++++ 4 files changed, 7657 insertions(+), 104 deletions(-) create mode 100644 Software/src/lib/bblanchon-ArduinoJson/ArduinoJson.h diff --git a/Software/Software.ino b/Software/Software.ino index df20927a..9302c355 100644 --- a/Software/Software.ino +++ b/Software/Software.ino @@ -11,6 +11,7 @@ #include "src/devboard/utils/events.h" #include "src/inverter/INVERTERS.h" #include "src/lib/adafruit-Adafruit_NeoPixel/Adafruit_NeoPixel.h" +#include "src/lib/bblanchon-ArduinoJson/ArduinoJson.h" #include "src/lib/eModbus-eModbus/Logging.h" #include "src/lib/eModbus-eModbus/ModbusServerRTU.h" #include "src/lib/eModbus-eModbus/scripts/mbServerFCs.h" diff --git a/Software/src/devboard/mqtt/mqtt.cpp b/Software/src/devboard/mqtt/mqtt.cpp index 2e7cb9c3..c632f584 100644 --- a/Software/src/devboard/mqtt/mqtt.cpp +++ b/Software/src/devboard/mqtt/mqtt.cpp @@ -4,6 +4,7 @@ #include #include "../../../USER_SETTINGS.h" #include "../../battery/BATTERIES.h" +#include "../../lib/bblanchon-ArduinoJson/ArduinoJson.h" #include "../../lib/knolleary-pubsubclient/PubSubClient.h" #include "../utils/timer.h" @@ -28,76 +29,59 @@ static void publish_values(void) { static void publish_cell_voltages(void) { static bool mqtt_first_transmission = true; - - // If the cell voltage number isn't initialized... + static JsonDocument doc; + static const char* hostname = WiFi.getHostname(); if (nof_cellvoltages == 0u) { return; } - // At startup, re-post the discovery message for home assistant + if (mqtt_first_transmission == true) { mqtt_first_transmission = false; - - // Base topic for any cell voltage "sensor" String topic = "homeassistant/sensor/battery-emulator/cell_voltage"; + for (int i = 0; i < nof_cellvoltages; i++) { - // Build JSON message with device configuration for each cell voltage - // Probably shouldn't be BatteryEmulator here, instead "LeafBattery" - // or similar but hey, it works. - // mqtt_msg is a global buffer, should be fine since we run too much - // in a single thread :) - snprintf(mqtt_msg, sizeof(mqtt_msg), - "{" - "\"device\": {" - "\"identifiers\": [" - "\"battery-emulator\"" - "]," - "\"manufacturer\": \"DalaTech\"," - "\"model\": \"BatteryEmulator\"," - "\"name\": \"BatteryEmulator\"" - "}," - "\"device_class\": \"voltage\"," - "\"enabled_by_default\": true," - "\"object_id\": \"sensor_battery_voltage_cell%d\"," - "\"origin\": {" - "\"name\": \"BatteryEmulator\"," - "\"sw\": \"%s-mqtt\"," - "\"url\": \"https://github.com/dalathegreat/Battery-Emulator\"" - "}," - "\"state_class\": \"measurement\"," - "\"name\": \"Battery Cell Voltage %d\"," - "\"state_topic\": \"battery-emulator/spec_data\"," - "\"unique_id\": \"battery-emulator_battery_voltage_cell%d\"," - "\"unit_of_measurement\": \"V\"," - "\"value_template\": \"{{ value_json.cell_voltages[%d] }}\"" - "}", - i + 1, version_number, i + 1, i + 1, i); - // End each discovery topic with cell number and '/config' + char cellNumber[4]; // Buffer to hold the formatted cell number + sprintf(cellNumber, "%03d", i + 1); // Format the cell number with leading zeros + + doc["name"] = "Battery Cell Voltage " + String(cellNumber); + doc["object_id"] = "battery_voltage_cell" + String(cellNumber); + doc["unique_id"] = "battery-emulator_battery_voltage_cell" + String(cellNumber); + doc["device_class"] = "voltage"; + doc["state_class"] = "measurement"; + doc["state_topic"] = "battery-emulator/spec_data"; + doc["unit_of_measurement"] = "V"; + doc["enabled_by_default"] = true; + doc["expire_after"] = 240; + doc["value_template"] = "{{ value_json.cell_voltages[" + String(i) + "] }}"; + doc["device"]["identifiers"][0] = "battery-emulator"; + doc["device"]["manufacturer"] = "DalaTech"; + doc["device"]["model"] = "BatteryEmulator"; + doc["device"]["name"] = "BatteryEmulator_" + String(hostname); + doc["origin"]["name"] = "BatteryEmulator"; + doc["origin"]["sw"] = String(version_number) + "-mqtt"; + doc["origin"]["url"] = "https://github.com/dalathegreat/Battery-Emulator"; + + serializeJson(doc, mqtt_msg, sizeof(mqtt_msg)); String cell_topic = topic + String(i + 1) + "/config"; - mqtt_publish_retain(cell_topic.c_str()); + mqtt_publish(cell_topic.c_str(), mqtt_msg, true); } + doc.clear(); // clear after sending autoconfig } else { - // Every 5-ish seconds, build the JSON payload for the state topic. This requires - // some annoying formatting due to C++ not having nice Python-like string formatting. - // msg_length is a cumulative variable to track start position (param 1) and for - // modifying the maxiumum amount of characters to write (param 2). The third parameter - // is the string content - - // If cell voltages haven't been populated... - if (cellvoltages[0] == 0u / 1000) { //cell voltage is in mV and homeassistant expects V + if (cellvoltages[0] == 0u) { return; } - size_t msg_length = snprintf(mqtt_msg, sizeof(mqtt_msg), "{\n\"cell_voltages\":["); + JsonArray cell_voltages = doc["cell_voltages"].to(); for (size_t i = 0; i < nof_cellvoltages; ++i) { - msg_length += - snprintf(mqtt_msg + msg_length, sizeof(mqtt_msg) - msg_length, "%s%d", (i == 0) ? "" : ", ", cellvoltages[i]); + cell_voltages.add(cellvoltages[i] / 1000.0); } - snprintf(mqtt_msg + msg_length, sizeof(mqtt_msg) - msg_length, "]\n}\n"); - // Publish and print error if not OK - if (mqtt_publish_retain("battery-emulator/spec_data") == false) { + serializeJson(doc, mqtt_msg, sizeof(mqtt_msg)); + + if (!mqtt_publish("battery-emulator/spec_data", mqtt_msg, false)) { Serial.println("Cell voltage MQTT msg could not be sent"); } + doc.clear(); } } @@ -132,59 +116,48 @@ SensorConfig sensorConfigs[] = { }; static void publish_common_info(void) { + static JsonDocument doc; static bool mqtt_first_transmission = true; static char* state_topic = "battery-emulator/info"; + static const char* hostname = WiFi.getHostname(); if (mqtt_first_transmission == true) { mqtt_first_transmission = false; for (int i = 0; i < sizeof(sensorConfigs) / sizeof(sensorConfigs[0]); i++) { SensorConfig& config = sensorConfigs[i]; - snprintf(mqtt_msg, sizeof(mqtt_msg), - "{" - "\"name\": \"%s\"," - "\"state_topic\": \"%s\"," - "\"unique_id\": \"battery-emulator_%s\"," - "\"object_id\": \"sensor_battery_%s\"," - "\"device\": {" - "\"identifiers\": [" - "\"battery-emulator\"" - "]," - "\"manufacturer\": \"DalaTech\"," - "\"model\": \"BatteryEmulator\"," - "\"name\": \"BatteryEmulator\"" - "}," - "\"origin\": {" - "\"name\": \"BatteryEmulator\"," - "\"sw\": \"%s-mqtt\"," - "\"url\": \"https://github.com/dalathegreat/Battery-Emulator\"" - "}," - "\"value_template\": \"%s\"," - "\"unit_of_measurement\": \"%s\"," - "\"device_class\": \"%s\"," - "\"enabled_by_default\": true," - "\"state_class\": \"measurement\"" - "}", - config.name, state_topic, config.object_id, config.object_id, version_number, config.value_template, - config.unit, config.device_class); - mqtt_publish_retain(config.topic); + doc["name"] = config.name; + doc["state_topic"] = state_topic; + doc["unique_id"] = "battery-emulator_" + String(hostname) + "_" + String(config.object_id); + doc["object_id"] = String(hostname) + "_" + String(config.object_id); + doc["value_template"] = config.value_template; + doc["unit_of_measurement"] = config.unit; + doc["device_class"] = config.device_class; + doc["enabled_by_default"] = true; + doc["state_class"] = "measurement"; + doc["expire_after"] = 240; + doc["device"]["identifiers"][0] = "battery-emulator"; + doc["device"]["manufacturer"] = "DalaTech"; + doc["device"]["model"] = "BatteryEmulator"; + doc["device"]["name"] = "BatteryEmulator_" + String(hostname); + doc["origin"]["name"] = "BatteryEmulator"; + doc["origin"]["sw"] = String(version_number) + "-mqtt"; + doc["origin"]["url"] = "https://github.com/dalathegreat/Battery-Emulator"; + serializeJson(doc, mqtt_msg); + mqtt_publish(config.topic, mqtt_msg, true); } + doc.clear(); } else { - snprintf(mqtt_msg, sizeof(mqtt_msg), - "{\n" - " \"SOC\": %.3f,\n" - " \"state_of_health\": %.3f,\n" - " \"temperature_min\": %.3f,\n" - " \"temperature_max\": %.3f,\n" - " \"stat_batt_power\": %.3f,\n" - " \"battery_current\": %.3f,\n" - " \"cell_max_voltage\": %d,\n" - " \"cell_min_voltage\": %d,\n" - " \"battery_voltage\": %d\n" - "}\n", - ((float)SOC) / 100.0, ((float)StateOfHealth) / 100.0, ((float)((int16_t)temperature_min)) / 10.0, - ((float)((int16_t)temperature_max)) / 10.0, ((float)((int16_t)stat_batt_power)), - ((float)((int16_t)battery_current)) / 10.0, cell_max_voltage / 1000, cell_min_voltage / 1000, - battery_voltage / 10.0); - bool result = client.publish(state_topic, mqtt_msg, true); + doc["SOC"] = ((float)SOC) / 100.0; + doc["state_of_health"] = ((float)StateOfHealth) / 100.0; + doc["temperature_min"] = ((float)((int16_t)temperature_min)) / 10.0; + doc["temperature_max"] = ((float)((int16_t)temperature_max)) / 10.0; + doc["stat_batt_power"] = ((float)((int16_t)stat_batt_power)); + doc["battery_current"] = ((float)((int16_t)battery_current)) / 10.0; + doc["cell_max_voltage"] = cell_max_voltage / 1000.0; + doc["cell_min_voltage"] = cell_min_voltage / 1000.0; + doc["battery_voltage"] = battery_voltage / 10.0; + + serializeJson(doc, mqtt_msg); + bool result = mqtt_publish(state_topic, mqtt_msg, false); } //Serial.println(mqtt_msg); // Uncomment to print the payload on serial @@ -206,9 +179,10 @@ static void reconnect() { // attempt one reconnection Serial.print("Attempting MQTT connection... "); const char* hostname = WiFi.getHostname(); - String clientId = "LilyGoClient-" + String(hostname); + char clientId[64]; // Adjust the size as needed + snprintf(clientId, sizeof(clientId), "LilyGoClient-%s", hostname); // Attempt to connect - if (client.connect(clientId.c_str(), mqtt_user, mqtt_password)) { + if (client.connect(clientId, mqtt_user, mqtt_password)) { Serial.println("connected"); for (int i = 0; i < mqtt_nof_subscriptions; i++) { @@ -238,20 +212,20 @@ void mqtt_loop(void) { client.loop(); if (publish_global_timer.elapsed() == true) // Every 5s { - publish_values(); // Update values heading towards inverter. Prepare for sending on CAN, or write directly to Modbus. + publish_values(); } } else { if (millis() - previousMillisUpdateVal >= 5000) // Every 5s { previousMillisUpdateVal = millis(); - reconnect(); // Update values heading towards inverter. Prepare for sending on CAN, or write directly to Modbus. + reconnect(); } } } -bool mqtt_publish_retain(const char* topic) { +bool mqtt_publish(const char* topic, const char* mqtt_msg, bool retain) { if (client.connected() == true) { - return client.publish(topic, mqtt_msg, true); + return client.publish(topic, mqtt_msg, retain); } return false; } diff --git a/Software/src/devboard/mqtt/mqtt.h b/Software/src/devboard/mqtt/mqtt.h index b7b58c8f..b9d26f4e 100644 --- a/Software/src/devboard/mqtt/mqtt.h +++ b/Software/src/devboard/mqtt/mqtt.h @@ -59,6 +59,6 @@ extern char mqtt_msg[MQTT_MSG_BUFFER_SIZE]; void init_mqtt(void); void mqtt_loop(void); -bool mqtt_publish_retain(const char* topic); +bool mqtt_publish(const char* topic, const char* mqtt_msg, bool retain); #endif diff --git a/Software/src/lib/bblanchon-ArduinoJson/ArduinoJson.h b/Software/src/lib/bblanchon-ArduinoJson/ArduinoJson.h new file mode 100644 index 00000000..27314aed --- /dev/null +++ b/Software/src/lib/bblanchon-ArduinoJson/ArduinoJson.h @@ -0,0 +1,7578 @@ +// ArduinoJson - https://arduinojson.org +// Copyright © 2014-2024, Benoit BLANCHON +// MIT License + +#pragma once + +#ifdef __cplusplus + +#if __cplusplus < 201103L && (!defined(_MSC_VER) || _MSC_VER < 1910) +# error ArduinoJson requires C++11 or newer. Configure your compiler for C++11 or downgrade ArduinoJson to 6.20. +#endif +#ifndef ARDUINOJSON_ENABLE_STD_STREAM +# ifdef __has_include +# if __has_include() && \ + __has_include() && \ + !defined(min) && \ + !defined(max) +# define ARDUINOJSON_ENABLE_STD_STREAM 1 +# else +# define ARDUINOJSON_ENABLE_STD_STREAM 0 +# endif +# else +# ifdef ARDUINO +# define ARDUINOJSON_ENABLE_STD_STREAM 0 +# else +# define ARDUINOJSON_ENABLE_STD_STREAM 1 +# endif +# endif +#endif +#ifndef ARDUINOJSON_ENABLE_STD_STRING +# ifdef __has_include +# if __has_include() && !defined(min) && !defined(max) +# define ARDUINOJSON_ENABLE_STD_STRING 1 +# else +# define ARDUINOJSON_ENABLE_STD_STRING 0 +# endif +# else +# ifdef ARDUINO +# define ARDUINOJSON_ENABLE_STD_STRING 0 +# else +# define ARDUINOJSON_ENABLE_STD_STRING 1 +# endif +# endif +#endif +#ifndef ARDUINOJSON_ENABLE_STRING_VIEW +# ifdef __has_include +# if __has_include() && __cplusplus >= 201703L +# define ARDUINOJSON_ENABLE_STRING_VIEW 1 +# else +# define ARDUINOJSON_ENABLE_STRING_VIEW 0 +# endif +# else +# define ARDUINOJSON_ENABLE_STRING_VIEW 0 +# endif +#endif +#ifndef ARDUINOJSON_USE_DOUBLE +# define ARDUINOJSON_USE_DOUBLE 1 +#endif +#ifndef ARDUINOJSON_SIZEOF_POINTER +# if defined(__SIZEOF_POINTER__) +# define ARDUINOJSON_SIZEOF_POINTER __SIZEOF_POINTER__ +# elif defined(_WIN64) && _WIN64 +# define ARDUINOJSON_SIZEOF_POINTER 8 // 64 bits +# else +# define ARDUINOJSON_SIZEOF_POINTER 4 // assume 32 bits otherwise +# endif +#endif +#ifndef ARDUINOJSON_USE_LONG_LONG +# if ARDUINOJSON_SIZEOF_POINTER >= 4 // 32 & 64 bits systems +# define ARDUINOJSON_USE_LONG_LONG 1 +# else +# define ARDUINOJSON_USE_LONG_LONG 0 +# endif +#endif +#ifndef ARDUINOJSON_DEFAULT_NESTING_LIMIT +# define ARDUINOJSON_DEFAULT_NESTING_LIMIT 10 +#endif +#ifndef ARDUINOJSON_SLOT_ID_SIZE +# if ARDUINOJSON_SIZEOF_POINTER <= 2 +# define ARDUINOJSON_SLOT_ID_SIZE 1 // up to 255 slots +# elif ARDUINOJSON_SIZEOF_POINTER == 4 +# define ARDUINOJSON_SLOT_ID_SIZE 2 // up to 65535 slots +# else +# define ARDUINOJSON_SLOT_ID_SIZE 4 // up to 4294967295 slots +# endif +#endif +#ifndef ARDUINOJSON_POOL_CAPACITY +# if ARDUINOJSON_SIZEOF_POINTER <= 2 +# define ARDUINOJSON_POOL_CAPACITY 16 // 128 bytes +# elif ARDUINOJSON_SIZEOF_POINTER == 4 +# define ARDUINOJSON_POOL_CAPACITY 64 // 1024 bytes +# else +# define ARDUINOJSON_POOL_CAPACITY 128 // 3072 bytes +# endif +#endif +#ifndef ARDUINOJSON_INITIAL_POOL_COUNT +# define ARDUINOJSON_INITIAL_POOL_COUNT 4 +#endif +#ifndef ARDUINOJSON_AUTO_SHRINK +# if ARDUINOJSON_SIZEOF_POINTER <= 2 +# define ARDUINOJSON_AUTO_SHRINK 0 +# else +# define ARDUINOJSON_AUTO_SHRINK 1 +# endif +#endif +#ifndef ARDUINOJSON_STRING_LENGTH_SIZE +# if ARDUINOJSON_SIZEOF_POINTER <= 2 +# define ARDUINOJSON_STRING_LENGTH_SIZE 1 // up to 255 characters +# else +# define ARDUINOJSON_STRING_LENGTH_SIZE 2 // up to 65535 characters +# endif +#endif +#ifdef ARDUINO +# ifndef ARDUINOJSON_ENABLE_ARDUINO_STRING +# define ARDUINOJSON_ENABLE_ARDUINO_STRING 1 +# endif +# ifndef ARDUINOJSON_ENABLE_ARDUINO_STREAM +# define ARDUINOJSON_ENABLE_ARDUINO_STREAM 1 +# endif +# ifndef ARDUINOJSON_ENABLE_ARDUINO_PRINT +# define ARDUINOJSON_ENABLE_ARDUINO_PRINT 1 +# endif +# ifndef ARDUINOJSON_ENABLE_PROGMEM +# define ARDUINOJSON_ENABLE_PROGMEM 1 +# endif +#else // ARDUINO +# ifndef ARDUINOJSON_ENABLE_ARDUINO_STRING +# define ARDUINOJSON_ENABLE_ARDUINO_STRING 0 +# endif +# ifndef ARDUINOJSON_ENABLE_ARDUINO_STREAM +# define ARDUINOJSON_ENABLE_ARDUINO_STREAM 0 +# endif +# ifndef ARDUINOJSON_ENABLE_ARDUINO_PRINT +# define ARDUINOJSON_ENABLE_ARDUINO_PRINT 0 +# endif +# ifndef ARDUINOJSON_ENABLE_PROGMEM +# ifdef __AVR__ +# define ARDUINOJSON_ENABLE_PROGMEM 1 +# else +# define ARDUINOJSON_ENABLE_PROGMEM 0 +# endif +# endif +#endif // ARDUINO +#ifndef ARDUINOJSON_DECODE_UNICODE +# define ARDUINOJSON_DECODE_UNICODE 1 +#endif +#ifndef ARDUINOJSON_ENABLE_COMMENTS +# define ARDUINOJSON_ENABLE_COMMENTS 0 +#endif +#ifndef ARDUINOJSON_ENABLE_NAN +# define ARDUINOJSON_ENABLE_NAN 0 +#endif +#ifndef ARDUINOJSON_ENABLE_INFINITY +# define ARDUINOJSON_ENABLE_INFINITY 0 +#endif +#ifndef ARDUINOJSON_POSITIVE_EXPONENTIATION_THRESHOLD +# define ARDUINOJSON_POSITIVE_EXPONENTIATION_THRESHOLD 1e7 +#endif +#ifndef ARDUINOJSON_NEGATIVE_EXPONENTIATION_THRESHOLD +# define ARDUINOJSON_NEGATIVE_EXPONENTIATION_THRESHOLD 1e-5 +#endif +#ifndef ARDUINOJSON_LITTLE_ENDIAN +# if defined(_MSC_VER) || \ + (defined(__BYTE_ORDER__) && \ + __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__) || \ + defined(__LITTLE_ENDIAN__) || defined(__i386) || defined(__x86_64) +# define ARDUINOJSON_LITTLE_ENDIAN 1 +# else +# define ARDUINOJSON_LITTLE_ENDIAN 0 +# endif +#endif +#ifndef ARDUINOJSON_ENABLE_ALIGNMENT +# if defined(__AVR) +# define ARDUINOJSON_ENABLE_ALIGNMENT 0 +# else +# define ARDUINOJSON_ENABLE_ALIGNMENT 1 +# endif +#endif +#ifndef ARDUINOJSON_TAB +# define ARDUINOJSON_TAB " " +#endif +#ifndef ARDUINOJSON_STRING_BUFFER_SIZE +# define ARDUINOJSON_STRING_BUFFER_SIZE 32 +#endif +#ifndef ARDUINOJSON_DEBUG +# ifdef __PLATFORMIO_BUILD_DEBUG__ +# define ARDUINOJSON_DEBUG 1 +# else +# define ARDUINOJSON_DEBUG 0 +# endif +#endif +#if defined(nullptr) +# error nullptr is defined as a macro. Remove the faulty #define or #undef nullptr +#endif +#if ARDUINOJSON_ENABLE_ARDUINO_STRING || ARDUINOJSON_ENABLE_ARDUINO_STREAM || \ + ARDUINOJSON_ENABLE_ARDUINO_PRINT || \ + (ARDUINOJSON_ENABLE_PROGMEM && defined(ARDUINO)) +#include +#endif +#if !ARDUINOJSON_DEBUG +# ifdef __clang__ +# pragma clang system_header +# elif defined __GNUC__ +# pragma GCC system_header +# endif +#endif +#define ARDUINOJSON_CONCAT_(A, B) A##B +#define ARDUINOJSON_CONCAT2(A, B) ARDUINOJSON_CONCAT_(A, B) +#define ARDUINOJSON_CONCAT4(A, B, C, D) \ + ARDUINOJSON_CONCAT2(ARDUINOJSON_CONCAT2(A, B), ARDUINOJSON_CONCAT2(C, D)) +#define ARDUINOJSON_BIN2ALPHA_0000() A +#define ARDUINOJSON_BIN2ALPHA_0001() B +#define ARDUINOJSON_BIN2ALPHA_0010() C +#define ARDUINOJSON_BIN2ALPHA_0011() D +#define ARDUINOJSON_BIN2ALPHA_0100() E +#define ARDUINOJSON_BIN2ALPHA_0101() F +#define ARDUINOJSON_BIN2ALPHA_0110() G +#define ARDUINOJSON_BIN2ALPHA_0111() H +#define ARDUINOJSON_BIN2ALPHA_1000() I +#define ARDUINOJSON_BIN2ALPHA_1001() J +#define ARDUINOJSON_BIN2ALPHA_1010() K +#define ARDUINOJSON_BIN2ALPHA_1011() L +#define ARDUINOJSON_BIN2ALPHA_1100() M +#define ARDUINOJSON_BIN2ALPHA_1101() N +#define ARDUINOJSON_BIN2ALPHA_1110() O +#define ARDUINOJSON_BIN2ALPHA_1111() P +#define ARDUINOJSON_BIN2ALPHA_(A, B, C, D) ARDUINOJSON_BIN2ALPHA_##A##B##C##D() +#define ARDUINOJSON_BIN2ALPHA(A, B, C, D) ARDUINOJSON_BIN2ALPHA_(A, B, C, D) +#define ARDUINOJSON_VERSION "7.0.3" +#define ARDUINOJSON_VERSION_MAJOR 7 +#define ARDUINOJSON_VERSION_MINOR 0 +#define ARDUINOJSON_VERSION_REVISION 3 +#define ARDUINOJSON_VERSION_MACRO V703 +#ifndef ARDUINOJSON_VERSION_NAMESPACE +# define ARDUINOJSON_VERSION_NAMESPACE \ + ARDUINOJSON_CONCAT4(ARDUINOJSON_VERSION_MACRO, \ + ARDUINOJSON_BIN2ALPHA(ARDUINOJSON_ENABLE_PROGMEM, \ + ARDUINOJSON_USE_LONG_LONG, \ + ARDUINOJSON_USE_DOUBLE, 1), \ + ARDUINOJSON_BIN2ALPHA(ARDUINOJSON_ENABLE_NAN, \ + ARDUINOJSON_ENABLE_INFINITY, \ + ARDUINOJSON_ENABLE_COMMENTS, \ + ARDUINOJSON_DECODE_UNICODE), \ + ARDUINOJSON_SLOT_ID_SIZE) +#endif +#define ARDUINOJSON_BEGIN_PUBLIC_NAMESPACE \ + namespace ArduinoJson { \ + inline namespace ARDUINOJSON_VERSION_NAMESPACE { +#define ARDUINOJSON_END_PUBLIC_NAMESPACE \ + } \ + } +#define ARDUINOJSON_BEGIN_PRIVATE_NAMESPACE \ + namespace ArduinoJson { \ + inline namespace ARDUINOJSON_VERSION_NAMESPACE { \ + namespace detail { +#define ARDUINOJSON_END_PRIVATE_NAMESPACE \ + } \ + } \ + } +ARDUINOJSON_BEGIN_PUBLIC_NAMESPACE +template +struct Converter; +ARDUINOJSON_END_PUBLIC_NAMESPACE +ARDUINOJSON_BEGIN_PRIVATE_NAMESPACE +template +class InvalidConversion; // Error here? See https://arduinojson.org/v7/invalid-conversion/ +template +struct ConverterNeedsWriteableRef; +ARDUINOJSON_END_PRIVATE_NAMESPACE +#include +#include +#include +ARDUINOJSON_BEGIN_PUBLIC_NAMESPACE +class Allocator { + public: + virtual void* allocate(size_t size) = 0; + virtual void deallocate(void* ptr) = 0; + virtual void* reallocate(void* ptr, size_t new_size) = 0; + protected: + ~Allocator() = default; +}; +namespace detail { +class DefaultAllocator : public Allocator { + public: + void* allocate(size_t size) override { + return malloc(size); + } + void deallocate(void* ptr) override { + free(ptr); + } + void* reallocate(void* ptr, size_t new_size) override { + return realloc(ptr, new_size); + } + static Allocator* instance() { + static DefaultAllocator allocator; + return &allocator; + } + private: + DefaultAllocator() = default; + ~DefaultAllocator() = default; +}; +} // namespace detail +ARDUINOJSON_END_PUBLIC_NAMESPACE +#if ARDUINOJSON_DEBUG +#include +# define ARDUINOJSON_ASSERT(X) assert(X) +#else +# define ARDUINOJSON_ASSERT(X) ((void)0) +#endif +ARDUINOJSON_BEGIN_PRIVATE_NAMESPACE +template +struct uint_t; +template <> +struct uint_t<8> { + typedef uint8_t type; +}; +template <> +struct uint_t<16> { + typedef uint16_t type; +}; +template <> +struct uint_t<32> { + typedef uint32_t type; +}; +template +struct conditional { + typedef TrueType type; +}; +template +struct conditional { + typedef FalseType type; +}; +template +struct enable_if {}; +template +struct enable_if { + typedef T type; +}; +template +struct integral_constant { + static const T value = v; +}; +typedef integral_constant true_type; +typedef integral_constant false_type; +template +struct is_array : false_type {}; +template +struct is_array : true_type {}; +template +struct is_array : true_type {}; +template +struct remove_reference { + typedef T type; +}; +template +struct remove_reference { + typedef T type; +}; +template +class is_base_of { + protected: // <- to avoid GCC's "all member functions in class are private" + static int probe(const TBase*); + static char probe(...); + public: + static const bool value = + sizeof(probe(reinterpret_cast::type*>( + 0))) == sizeof(int); +}; +template +T&& declval(); +template +struct is_class { + protected: // <- to avoid GCC's "all member functions in class are private" + template + static int probe(void (U::*)(void)); + template + static char probe(...); + public: + static const bool value = sizeof(probe(0)) == sizeof(int); +}; +template +struct is_const : false_type {}; +template +struct is_const : true_type {}; +ARDUINOJSON_END_PRIVATE_NAMESPACE +#ifdef _MSC_VER +# pragma warning(push) +# pragma warning(disable : 4244) +#endif +#ifdef __ICCARM__ +#pragma diag_suppress=Pa093 +#endif +ARDUINOJSON_BEGIN_PRIVATE_NAMESPACE +template +struct is_convertible { + protected: // <- to avoid GCC's "all member functions in class are private" + static int probe(To); + static char probe(...); + static From& from_; + public: + static const bool value = sizeof(probe(from_)) == sizeof(int); +}; +ARDUINOJSON_END_PRIVATE_NAMESPACE +#ifdef _MSC_VER +# pragma warning(pop) +#endif +#ifdef __ICCARM__ +#pragma diag_default=Pa093 +#endif +ARDUINOJSON_BEGIN_PRIVATE_NAMESPACE +template +struct is_same : false_type {}; +template +struct is_same : true_type {}; +template +struct remove_cv { + typedef T type; +}; +template +struct remove_cv { + typedef T type; +}; +template +struct remove_cv { + typedef T type; +}; +template +struct remove_cv { + typedef T type; +}; +template +struct is_floating_point + : integral_constant< + bool, // + is_same::type>::value || + is_same::type>::value> {}; +template +struct is_integral : integral_constant::type, signed char>::value || + is_same::type, unsigned char>::value || + is_same::type, signed short>::value || + is_same::type, unsigned short>::value || + is_same::type, signed int>::value || + is_same::type, unsigned int>::value || + is_same::type, signed long>::value || + is_same::type, unsigned long>::value || + is_same::type, signed long long>::value || + is_same::type, unsigned long long>::value || + is_same::type, char>::value || + is_same::type, bool>::value> {}; +template +struct is_enum { + static const bool value = is_convertible::value && + !is_class::value && !is_integral::value && + !is_floating_point::value; +}; +template +struct is_pointer : false_type {}; +template +struct is_pointer : true_type {}; +template +struct is_signed : integral_constant::type, char>::value || + is_same::type, signed char>::value || + is_same::type, signed short>::value || + is_same::type, signed int>::value || + is_same::type, signed long>::value || + is_same::type, signed long long>::value || + is_same::type, float>::value || + is_same::type, double>::value> {}; +template +struct is_unsigned : integral_constant::type, unsigned char>::value || + is_same::type, unsigned short>::value || + is_same::type, unsigned int>::value || + is_same::type, unsigned long>::value || + is_same::type, unsigned long long>::value || + is_same::type, bool>::value> {}; +template +struct type_identity { + typedef T type; +}; +template +struct make_unsigned; +template <> +struct make_unsigned : type_identity {}; +template <> +struct make_unsigned : type_identity {}; +template <> +struct make_unsigned : type_identity {}; +template <> +struct make_unsigned : type_identity {}; +template <> +struct make_unsigned : type_identity {}; +template <> +struct make_unsigned : type_identity {}; +template <> +struct make_unsigned : type_identity {}; +template <> +struct make_unsigned : type_identity {}; +template <> +struct make_unsigned : type_identity {}; +template <> +struct make_unsigned : type_identity {}; +template <> +struct make_unsigned : type_identity {}; +template +struct make_void { + typedef void type; +}; +template +struct remove_const { + typedef T type; +}; +template +struct remove_const { + typedef T type; +}; +ARDUINOJSON_END_PRIVATE_NAMESPACE +#ifdef _MSC_VER +# pragma warning(push) +# pragma warning(disable : 4310) +#endif +ARDUINOJSON_BEGIN_PRIVATE_NAMESPACE +template +struct numeric_limits; +template +struct numeric_limits::value>::type> { + static constexpr T lowest() { + return 0; + } + static constexpr T highest() { + return T(-1); + } +}; +template +struct numeric_limits< + T, typename enable_if::value && is_signed::value>::type> { + static constexpr T lowest() { + return T(T(1) << (sizeof(T) * 8 - 1)); + } + static constexpr T highest() { + return T(~lowest()); + } +}; +ARDUINOJSON_END_PRIVATE_NAMESPACE +#ifdef _MSC_VER +# pragma warning(pop) +#endif +ARDUINOJSON_BEGIN_PRIVATE_NAMESPACE +struct StringNode { + using references_type = uint_t::type; + using length_type = uint_t::type; + struct StringNode* next; + references_type references; + length_type length; + char data[1]; + static constexpr size_t maxLength = numeric_limits::highest(); + static constexpr size_t sizeForLength(size_t n) { + return n + 1 + offsetof(StringNode, data); + } + static StringNode* create(size_t length, Allocator* allocator) { + if (length > maxLength) + return nullptr; + auto node = reinterpret_cast( + allocator->allocate(sizeForLength(length))); + if (node) { + node->length = length_type(length); + node->references = 1; + } + return node; + } + static StringNode* resize(StringNode* node, size_t length, + Allocator* allocator) { + ARDUINOJSON_ASSERT(node != nullptr); + StringNode* newNode; + if (length <= maxLength) + newNode = reinterpret_cast( + allocator->reallocate(node, sizeForLength(length))); + else + newNode = nullptr; + if (newNode) + newNode->length = length_type(length); + else + allocator->deallocate(node); + return newNode; + } + static void destroy(StringNode* node, Allocator* allocator) { + allocator->deallocate(node); + } +}; +constexpr size_t sizeofString(size_t n) { + return StringNode::sizeForLength(n); +} +using nullptr_t = decltype(nullptr); +template +T&& forward(typename remove_reference::type& t) noexcept { + return static_cast(t); +} +template +typename remove_reference::type&& move(T&& t) { + return static_cast::type&&>(t); +} +template +void swap_(T& a, T& b) { + T tmp = move(a); + a = move(b); + b = move(tmp); +} +ARDUINOJSON_END_PRIVATE_NAMESPACE +#include +#ifdef _MSC_VER // Visual Studio +# define FORCE_INLINE // __forceinline causes C4714 when returning std::string +# ifndef ARDUINOJSON_DEPRECATED +# define ARDUINOJSON_DEPRECATED(msg) __declspec(deprecated(msg)) +# endif +#elif defined(__GNUC__) // GCC or Clang +# define FORCE_INLINE __attribute__((always_inline)) +# ifndef ARDUINOJSON_DEPRECATED +# if __GNUC__ > 4 || (__GNUC__ == 4 && __GNUC_MINOR__ >= 5) +# define ARDUINOJSON_DEPRECATED(msg) __attribute__((deprecated(msg))) +# else +# define ARDUINOJSON_DEPRECATED(msg) __attribute__((deprecated)) +# endif +# endif +#else // Other compilers +# define FORCE_INLINE +# ifndef ARDUINOJSON_DEPRECATED +# define ARDUINOJSON_DEPRECATED(msg) +# endif +#endif +#if defined(__has_attribute) +# if __has_attribute(no_sanitize) +# define ARDUINOJSON_NO_SANITIZE(check) __attribute__((no_sanitize(check))) +# else +# define ARDUINOJSON_NO_SANITIZE(check) +# endif +#else +# define ARDUINOJSON_NO_SANITIZE(check) +#endif +ARDUINOJSON_BEGIN_PRIVATE_NAMESPACE +template +struct StringAdapter; +template +struct SizedStringAdapter; +template +typename StringAdapter::AdaptedString adaptString(const TString& s) { + return StringAdapter::adapt(s); +} +template +typename StringAdapter::AdaptedString adaptString(TChar* p) { + return StringAdapter::adapt(p); +} +template +typename SizedStringAdapter::AdaptedString adaptString(TChar* p, + size_t n) { + return SizedStringAdapter::adapt(p, n); +} +template +struct IsChar + : integral_constant::value && sizeof(T) == 1> {}; +class ZeroTerminatedRamString { + public: + static const size_t typeSortKey = 3; + ZeroTerminatedRamString(const char* str) : str_(str) {} + bool isNull() const { + return !str_; + } + FORCE_INLINE size_t size() const { + return str_ ? ::strlen(str_) : 0; + } + char operator[](size_t i) const { + ARDUINOJSON_ASSERT(str_ != 0); + ARDUINOJSON_ASSERT(i <= size()); + return str_[i]; + } + const char* data() const { + return str_; + } + friend int stringCompare(ZeroTerminatedRamString a, + ZeroTerminatedRamString b) { + ARDUINOJSON_ASSERT(!a.isNull()); + ARDUINOJSON_ASSERT(!b.isNull()); + return ::strcmp(a.str_, b.str_); + } + friend bool stringEquals(ZeroTerminatedRamString a, + ZeroTerminatedRamString b) { + return stringCompare(a, b) == 0; + } + bool isLinked() const { + return false; + } + protected: + const char* str_; +}; +template +struct StringAdapter::value>::type> { + typedef ZeroTerminatedRamString AdaptedString; + static AdaptedString adapt(const TChar* p) { + return AdaptedString(reinterpret_cast(p)); + } +}; +template +struct StringAdapter::value>::type> { + typedef ZeroTerminatedRamString AdaptedString; + static AdaptedString adapt(const TChar* p) { + return AdaptedString(reinterpret_cast(p)); + } +}; +class StaticStringAdapter : public ZeroTerminatedRamString { + public: + StaticStringAdapter(const char* str) : ZeroTerminatedRamString(str) {} + bool isLinked() const { + return true; + } +}; +template <> +struct StringAdapter { + typedef StaticStringAdapter AdaptedString; + static AdaptedString adapt(const char* p) { + return AdaptedString(p); + } +}; +class SizedRamString { + public: + static const size_t typeSortKey = 2; + SizedRamString(const char* str, size_t sz) : str_(str), size_(sz) {} + bool isNull() const { + return !str_; + } + size_t size() const { + return size_; + } + char operator[](size_t i) const { + ARDUINOJSON_ASSERT(str_ != 0); + ARDUINOJSON_ASSERT(i <= size()); + return str_[i]; + } + const char* data() const { + return str_; + } + bool isLinked() const { + return false; + } + protected: + const char* str_; + size_t size_; +}; +template +struct SizedStringAdapter::value>::type> { + typedef SizedRamString AdaptedString; + static AdaptedString adapt(const TChar* p, size_t n) { + return AdaptedString(reinterpret_cast(p), n); + } +}; +ARDUINOJSON_END_PRIVATE_NAMESPACE +#if ARDUINOJSON_ENABLE_STD_STREAM +#include +#endif +ARDUINOJSON_BEGIN_PUBLIC_NAMESPACE +class JsonString { + public: + enum Ownership { Copied, Linked }; + JsonString() : data_(0), size_(0), ownership_(Linked) {} + JsonString(const char* data, Ownership ownership = Linked) + : data_(data), size_(data ? ::strlen(data) : 0), ownership_(ownership) {} + JsonString(const char* data, size_t sz, Ownership ownership = Linked) + : data_(data), size_(sz), ownership_(ownership) {} + const char* c_str() const { + return data_; + } + bool isNull() const { + return !data_; + } + bool isLinked() const { + return ownership_ == Linked; + } + size_t size() const { + return size_; + } + explicit operator bool() const { + return data_ != 0; + } + friend bool operator==(JsonString lhs, JsonString rhs) { + if (lhs.size_ != rhs.size_) + return false; + if (lhs.data_ == rhs.data_) + return true; + if (!lhs.data_) + return false; + if (!rhs.data_) + return false; + return memcmp(lhs.data_, rhs.data_, lhs.size_) == 0; + } + friend bool operator!=(JsonString lhs, JsonString rhs) { + return !(lhs == rhs); + } +#if ARDUINOJSON_ENABLE_STD_STREAM + friend std::ostream& operator<<(std::ostream& lhs, const JsonString& rhs) { + lhs.write(rhs.c_str(), static_cast(rhs.size())); + return lhs; + } +#endif + private: + const char* data_; + size_t size_; + Ownership ownership_; +}; +ARDUINOJSON_END_PUBLIC_NAMESPACE +ARDUINOJSON_BEGIN_PRIVATE_NAMESPACE +class JsonStringAdapter : public SizedRamString { + public: + JsonStringAdapter(const JsonString& s) + : SizedRamString(s.c_str(), s.size()), linked_(s.isLinked()) {} + bool isLinked() const { + return linked_; + } + private: + bool linked_; +}; +template <> +struct StringAdapter { + typedef JsonStringAdapter AdaptedString; + static AdaptedString adapt(const JsonString& s) { + return AdaptedString(s); + } +}; +namespace string_traits_impl { +template +struct has_cstr : false_type {}; +template +struct has_cstr().c_str()), + const char*>::value>::type> + : true_type {}; +template +struct has_data : false_type {}; +template +struct has_data().data()), + const char*>::value>::type> + : true_type {}; +template +struct has_length : false_type {}; +template +struct has_length< + T, typename enable_if< + is_same().length()), size_t>::value>::type> + : true_type {}; +template +struct has_size : false_type {}; +template +struct has_size< + T, typename enable_if< + is_same().size()), size_t>::value>::type> + : true_type {}; +} // namespace string_traits_impl +template +struct string_traits { + enum { + has_cstr = string_traits_impl::has_cstr::value, + has_length = string_traits_impl::has_length::value, + has_data = string_traits_impl::has_data::value, + has_size = string_traits_impl::has_size::value + }; +}; +template +struct StringAdapter< + T, + typename enable_if< + (string_traits::has_cstr || string_traits::has_data) && + (string_traits::has_length || string_traits::has_size)>::type> { + typedef SizedRamString AdaptedString; + static AdaptedString adapt(const T& s) { + return AdaptedString(get_data(s), get_size(s)); + } + private: + template + static typename enable_if::has_size, size_t>::type get_size( + const U& s) { + return s.size(); + } + template + static typename enable_if::has_size, size_t>::type get_size( + const U& s) { + return s.length(); + } + template + static typename enable_if::has_data, const char*>::type + get_data(const U& s) { + return s.data(); + } + template + static typename enable_if::has_data, const char*>::type + get_data(const U& s) { + return s.c_str(); + } +}; +ARDUINOJSON_END_PRIVATE_NAMESPACE +#if ARDUINOJSON_ENABLE_PROGMEM +#ifdef ARDUINO +#else +class __FlashStringHelper; +#include +#endif +ARDUINOJSON_BEGIN_PRIVATE_NAMESPACE +struct pgm_p { + pgm_p(const void* p) : address(reinterpret_cast(p)) {} + const char* address; +}; +ARDUINOJSON_END_PRIVATE_NAMESPACE +#ifndef strlen_P +inline size_t strlen_P(ArduinoJson::detail::pgm_p s) { + const char* p = s.address; + ARDUINOJSON_ASSERT(p != NULL); + while (pgm_read_byte(p)) + p++; + return size_t(p - s.address); +} +#endif +#ifndef strncmp_P +inline int strncmp_P(const char* a, ArduinoJson::detail::pgm_p b, size_t n) { + const char* s1 = a; + const char* s2 = b.address; + ARDUINOJSON_ASSERT(s1 != NULL); + ARDUINOJSON_ASSERT(s2 != NULL); + while (n-- > 0) { + char c1 = *s1++; + char c2 = static_cast(pgm_read_byte(s2++)); + if (c1 < c2) + return -1; + if (c1 > c2) + return 1; + if (c1 == 0 /* and c2 as well */) + return 0; + } + return 0; +} +#endif +#ifndef strcmp_P +inline int strcmp_P(const char* a, ArduinoJson::detail::pgm_p b) { + const char* s1 = a; + const char* s2 = b.address; + ARDUINOJSON_ASSERT(s1 != NULL); + ARDUINOJSON_ASSERT(s2 != NULL); + for (;;) { + char c1 = *s1++; + char c2 = static_cast(pgm_read_byte(s2++)); + if (c1 < c2) + return -1; + if (c1 > c2) + return 1; + if (c1 == 0 /* and c2 as well */) + return 0; + } +} +#endif +#ifndef memcmp_P +inline int memcmp_P(const void* a, ArduinoJson::detail::pgm_p b, size_t n) { + const uint8_t* p1 = reinterpret_cast(a); + const char* p2 = b.address; + ARDUINOJSON_ASSERT(p1 != NULL); + ARDUINOJSON_ASSERT(p2 != NULL); + while (n-- > 0) { + uint8_t v1 = *p1++; + uint8_t v2 = pgm_read_byte(p2++); + if (v1 != v2) + return v1 - v2; + } + return 0; +} +#endif +#ifndef memcpy_P +inline void* memcpy_P(void* dst, ArduinoJson::detail::pgm_p src, size_t n) { + uint8_t* d = reinterpret_cast(dst); + const char* s = src.address; + ARDUINOJSON_ASSERT(d != NULL); + ARDUINOJSON_ASSERT(s != NULL); + while (n-- > 0) { + *d++ = pgm_read_byte(s++); + } + return dst; +} +#endif +#ifndef pgm_read_dword +inline uint32_t pgm_read_dword(ArduinoJson::detail::pgm_p p) { + uint32_t result; + memcpy_P(&result, p.address, 4); + return result; +} +#endif +#ifndef pgm_read_float +inline float pgm_read_float(ArduinoJson::detail::pgm_p p) { + float result; + memcpy_P(&result, p.address, sizeof(float)); + return result; +} +#endif +#ifndef pgm_read_double +# if defined(__SIZEOF_DOUBLE__) && defined(__SIZEOF_FLOAT__) && \ + __SIZEOF_DOUBLE__ == __SIZEOF_FLOAT__ +inline double pgm_read_double(ArduinoJson::detail::pgm_p p) { + return pgm_read_float(p.address); +} +# else +inline double pgm_read_double(ArduinoJson::detail::pgm_p p) { + double result; + memcpy_P(&result, p.address, sizeof(double)); + return result; +} +# endif +#endif +#ifndef pgm_read_ptr +inline void* pgm_read_ptr(ArduinoJson::detail::pgm_p p) { + void* result; + memcpy_P(&result, p.address, sizeof(result)); + return result; +} +#endif +ARDUINOJSON_BEGIN_PRIVATE_NAMESPACE +class FlashString { + public: + static const size_t typeSortKey = 1; + FlashString(const __FlashStringHelper* str, size_t sz) + : str_(reinterpret_cast(str)), size_(sz) {} + bool isNull() const { + return !str_; + } + char operator[](size_t i) const { + ARDUINOJSON_ASSERT(str_ != 0); + ARDUINOJSON_ASSERT(i <= size_); + return static_cast(pgm_read_byte(str_ + i)); + } + const char* data() const { + return nullptr; + } + size_t size() const { + return size_; + } + friend bool stringEquals(FlashString a, SizedRamString b) { + ARDUINOJSON_ASSERT(a.typeSortKey < b.typeSortKey); + ARDUINOJSON_ASSERT(!a.isNull()); + ARDUINOJSON_ASSERT(!b.isNull()); + if (a.size() != b.size()) + return false; + return ::memcmp_P(b.data(), a.str_, a.size_) == 0; + } + friend int stringCompare(FlashString a, SizedRamString b) { + ARDUINOJSON_ASSERT(a.typeSortKey < b.typeSortKey); + ARDUINOJSON_ASSERT(!a.isNull()); + ARDUINOJSON_ASSERT(!b.isNull()); + size_t minsize = a.size() < b.size() ? a.size() : b.size(); + int res = ::memcmp_P(b.data(), a.str_, minsize); + if (res) + return -res; + if (a.size() < b.size()) + return -1; + if (a.size() > b.size()) + return 1; + return 0; + } + friend void stringGetChars(FlashString s, char* p, size_t n) { + ARDUINOJSON_ASSERT(s.size() <= n); + ::memcpy_P(p, s.str_, n); + } + bool isLinked() const { + return false; + } + private: + const char* str_; + size_t size_; +}; +template <> +struct StringAdapter { + typedef FlashString AdaptedString; + static AdaptedString adapt(const __FlashStringHelper* s) { + return AdaptedString(s, s ? strlen_P(reinterpret_cast(s)) : 0); + } +}; +template <> +struct SizedStringAdapter { + typedef FlashString AdaptedString; + static AdaptedString adapt(const __FlashStringHelper* s, size_t n) { + return AdaptedString(s, n); + } +}; +ARDUINOJSON_END_PRIVATE_NAMESPACE +#endif +ARDUINOJSON_BEGIN_PRIVATE_NAMESPACE +template +typename enable_if::type +stringCompare(TAdaptedString1 s1, TAdaptedString2 s2) { + ARDUINOJSON_ASSERT(!s1.isNull()); + ARDUINOJSON_ASSERT(!s2.isNull()); + size_t size1 = s1.size(); + size_t size2 = s2.size(); + size_t n = size1 < size2 ? size1 : size2; + for (size_t i = 0; i < n; i++) { + if (s1[i] != s2[i]) + return s1[i] - s2[i]; + } + if (size1 < size2) + return -1; + if (size1 > size2) + return 1; + return 0; +} +template +typename enable_if< + (TAdaptedString1::typeSortKey > TAdaptedString2::typeSortKey), int>::type +stringCompare(TAdaptedString1 s1, TAdaptedString2 s2) { + return -stringCompare(s2, s1); +} +template +typename enable_if::type +stringEquals(TAdaptedString1 s1, TAdaptedString2 s2) { + ARDUINOJSON_ASSERT(!s1.isNull()); + ARDUINOJSON_ASSERT(!s2.isNull()); + size_t size1 = s1.size(); + size_t size2 = s2.size(); + if (size1 != size2) + return false; + for (size_t i = 0; i < size1; i++) { + if (s1[i] != s2[i]) + return false; + } + return true; +} +template +typename enable_if< + (TAdaptedString1::typeSortKey > TAdaptedString2::typeSortKey), bool>::type +stringEquals(TAdaptedString1 s1, TAdaptedString2 s2) { + return stringEquals(s2, s1); +} +template +static void stringGetChars(TAdaptedString s, char* p, size_t n) { + ARDUINOJSON_ASSERT(s.size() <= n); + for (size_t i = 0; i < n; i++) { + p[i] = s[i]; + } +} +class VariantSlot; +class VariantPool; +class StringPool { + public: + StringPool() = default; + StringPool(const StringPool&) = delete; + void operator=(StringPool&& src) = delete; + ~StringPool() { + ARDUINOJSON_ASSERT(strings_ == nullptr); + } + friend void swap(StringPool& a, StringPool& b) { + swap_(a.strings_, b.strings_); + } + void clear(Allocator* allocator) { + while (strings_) { + auto node = strings_; + strings_ = node->next; + StringNode::destroy(node, allocator); + } + } + size_t size() const { + size_t total = 0; + for (auto node = strings_; node; node = node->next) + total += sizeofString(node->length); + return total; + } + template + StringNode* add(TAdaptedString str, Allocator* allocator) { + ARDUINOJSON_ASSERT(str.isNull() == false); + auto node = get(str); + if (node) { + node->references++; + return node; + } + size_t n = str.size(); + node = StringNode::create(n, allocator); + if (!node) + return nullptr; + stringGetChars(str, node->data, n); + node->data[n] = 0; // force NUL terminator + add(node); + return node; + } + void add(StringNode* node) { + ARDUINOJSON_ASSERT(node != nullptr); + node->next = strings_; + strings_ = node; + } + template + StringNode* get(const TAdaptedString& str) const { + for (auto node = strings_; node; node = node->next) { + if (stringEquals(str, adaptString(node->data, node->length))) + return node; + } + return nullptr; + } + void dereference(const char* s, Allocator* allocator) { + StringNode* prev = nullptr; + for (auto node = strings_; node; node = node->next) { + if (node->data == s) { + if (--node->references == 0) { + if (prev) + prev->next = node->next; + else + strings_ = node->next; + StringNode::destroy(node, allocator); + } + return; + } + prev = node; + } + } + private: + StringNode* strings_ = nullptr; +}; +class VariantSlot; +using SlotId = uint_t::type; +using SlotCount = SlotId; +const SlotId NULL_SLOT = SlotId(-1); +class SlotWithId { + public: + SlotWithId() : slot_(nullptr), id_(NULL_SLOT) {} + SlotWithId(VariantSlot* slot, SlotId id) : slot_(slot), id_(id) { + ARDUINOJSON_ASSERT((slot == nullptr) == (id == NULL_SLOT)); + } + SlotId id() const { + return id_; + } + operator VariantSlot*() { + return slot_; + } + VariantSlot* operator->() { + ARDUINOJSON_ASSERT(slot_ != nullptr); + return slot_; + } + private: + VariantSlot* slot_; + SlotId id_; +}; +class VariantPool { + public: + void create(SlotCount cap, Allocator* allocator); + void destroy(Allocator* allocator); + SlotWithId allocSlot(); + VariantSlot* getSlot(SlotId id) const; + void clear(); + void shrinkToFit(Allocator*); + SlotCount usage() const; + static SlotCount bytesToSlots(size_t); + static size_t slotsToBytes(SlotCount); + private: + SlotCount capacity_; + SlotCount usage_; + VariantSlot* slots_; +}; +using PoolCount = SlotId; +class VariantPoolList { + public: + VariantPoolList() = default; + ~VariantPoolList() { + ARDUINOJSON_ASSERT(count_ == 0); + } + friend void swap(VariantPoolList& a, VariantPoolList& b) { + bool aUsedPreallocated = a.pools_ == a.preallocatedPools_; + bool bUsedPreallocated = b.pools_ == b.preallocatedPools_; + if (aUsedPreallocated && bUsedPreallocated) { + for (PoolCount i = 0; i < ARDUINOJSON_INITIAL_POOL_COUNT; i++) + swap_(a.preallocatedPools_[i], b.preallocatedPools_[i]); + } else if (bUsedPreallocated) { + for (PoolCount i = 0; i < b.count_; i++) + a.preallocatedPools_[i] = b.preallocatedPools_[i]; + b.pools_ = a.pools_; + a.pools_ = a.preallocatedPools_; + } else if (aUsedPreallocated) { + for (PoolCount i = 0; i < a.count_; i++) + b.preallocatedPools_[i] = a.preallocatedPools_[i]; + a.pools_ = b.pools_; + b.pools_ = b.preallocatedPools_; + } else { + swap_(a.pools_, b.pools_); + } + swap_(a.count_, b.count_); + swap_(a.capacity_, b.capacity_); + swap_(a.freeList_, b.freeList_); + } + VariantPoolList& operator=(VariantPoolList&& src) { + ARDUINOJSON_ASSERT(count_ == 0); + if (src.pools_ == src.preallocatedPools_) { + memcpy(preallocatedPools_, src.preallocatedPools_, + sizeof(preallocatedPools_)); + pools_ = preallocatedPools_; + } else { + pools_ = src.pools_; + src.pools_ = nullptr; + } + count_ = src.count_; + capacity_ = src.capacity_; + src.count_ = 0; + src.capacity_ = 0; + return *this; + } + SlotWithId allocSlot(Allocator* allocator) { + if (freeList_ != NULL_SLOT) { + return allocFromFreeList(); + } + if (count_) { + auto slot = allocFromLastPool(); + if (slot) + return slot; + } + auto pool = addPool(allocator); + if (!pool) + return {}; + return allocFromLastPool(); + } + void freeSlot(SlotWithId slot); + VariantSlot* getSlot(SlotId id) const { + if (id == NULL_SLOT) + return nullptr; + auto poolIndex = SlotId(id / ARDUINOJSON_POOL_CAPACITY); + auto indexInPool = SlotId(id % ARDUINOJSON_POOL_CAPACITY); + ARDUINOJSON_ASSERT(poolIndex < count_); + return pools_[poolIndex].getSlot(indexInPool); + } + void clear(Allocator* allocator) { + for (PoolCount i = 0; i < count_; i++) + pools_[i].destroy(allocator); + count_ = 0; + freeList_ = NULL_SLOT; + if (pools_ != preallocatedPools_) { + allocator->deallocate(pools_); + pools_ = preallocatedPools_; + capacity_ = ARDUINOJSON_INITIAL_POOL_COUNT; + } + } + SlotCount usage() const { + SlotCount total = 0; + for (PoolCount i = 0; i < count_; i++) + total = SlotCount(total + pools_[i].usage()); + return total; + } + void shrinkToFit(Allocator* allocator) { + if (count_ > 0) + pools_[count_ - 1].shrinkToFit(allocator); + if (pools_ != preallocatedPools_ && count_ != capacity_) { + pools_ = static_cast( + allocator->reallocate(pools_, count_ * sizeof(VariantPool))); + ARDUINOJSON_ASSERT(pools_ != nullptr); // realloc to smaller can't fail + capacity_ = count_; + } + } + private: + SlotWithId allocFromFreeList(); + SlotWithId allocFromLastPool() { + ARDUINOJSON_ASSERT(count_ > 0); + auto poolIndex = SlotId(count_ - 1); + auto slot = pools_[poolIndex].allocSlot(); + if (!slot) + return {}; + return {slot, SlotId(poolIndex * ARDUINOJSON_POOL_CAPACITY + slot.id())}; + } + VariantPool* addPool(Allocator* allocator) { + if (count_ == capacity_ && !increaseCapacity(allocator)) + return nullptr; + auto pool = &pools_[count_++]; + SlotCount poolCapacity = ARDUINOJSON_POOL_CAPACITY; + if (count_ == maxPools) // last pool is smaller because of NULL_SLOT + poolCapacity--; + pool->create(poolCapacity, allocator); + return pool; + } + bool increaseCapacity(Allocator* allocator) { + if (capacity_ == maxPools) + return false; + void* newPools; + auto newCapacity = PoolCount(capacity_ * 2); + if (pools_ == preallocatedPools_) { + newPools = allocator->allocate(newCapacity * sizeof(VariantPool)); + if (!newPools) + return false; + memcpy(newPools, preallocatedPools_, sizeof(preallocatedPools_)); + } else { + newPools = + allocator->reallocate(pools_, newCapacity * sizeof(VariantPool)); + if (!newPools) + return false; + } + pools_ = static_cast(newPools); + capacity_ = newCapacity; + return true; + } + VariantPool preallocatedPools_[ARDUINOJSON_INITIAL_POOL_COUNT]; + VariantPool* pools_ = preallocatedPools_; + PoolCount count_ = 0; + PoolCount capacity_ = ARDUINOJSON_INITIAL_POOL_COUNT; + SlotId freeList_ = NULL_SLOT; + public: + static const PoolCount maxPools = + PoolCount(NULL_SLOT / ARDUINOJSON_POOL_CAPACITY + 1); +}; +class VariantSlot; +class VariantPool; +class ResourceManager { + public: + ResourceManager(Allocator* allocator = DefaultAllocator::instance()) + : allocator_(allocator), overflowed_(false) {} + ~ResourceManager() { + stringPool_.clear(allocator_); + variantPools_.clear(allocator_); + } + ResourceManager(const ResourceManager&) = delete; + ResourceManager& operator=(const ResourceManager& src) = delete; + friend void swap(ResourceManager& a, ResourceManager& b) { + swap(a.stringPool_, b.stringPool_); + swap(a.variantPools_, b.variantPools_); + swap_(a.allocator_, b.allocator_); + swap_(a.overflowed_, b.overflowed_); + } + Allocator* allocator() const { + return allocator_; + } + size_t size() const { + return VariantPool::slotsToBytes(variantPools_.usage()) + + stringPool_.size(); + } + bool overflowed() const { + return overflowed_; + } + SlotWithId allocSlot() { + auto p = variantPools_.allocSlot(allocator_); + if (!p) + overflowed_ = true; + return p; + } + void freeSlot(SlotWithId id) { + variantPools_.freeSlot(id); + } + VariantSlot* getSlot(SlotId id) const { + return variantPools_.getSlot(id); + } + template + StringNode* saveString(TAdaptedString str) { + if (str.isNull()) + return 0; + auto node = stringPool_.add(str, allocator_); + if (!node) + overflowed_ = true; + return node; + } + void saveString(StringNode* node) { + stringPool_.add(node); + } + template + StringNode* getString(const TAdaptedString& str) const { + return stringPool_.get(str); + } + StringNode* createString(size_t length) { + auto node = StringNode::create(length, allocator_); + if (!node) + overflowed_ = true; + return node; + } + StringNode* resizeString(StringNode* node, size_t length) { + node = StringNode::resize(node, length, allocator_); + if (!node) + overflowed_ = true; + return node; + } + void destroyString(StringNode* node) { + StringNode::destroy(node, allocator_); + } + void dereferenceString(const char* s) { + stringPool_.dereference(s, allocator_); + } + void clear() { + variantPools_.clear(allocator_); + overflowed_ = false; + stringPool_.clear(allocator_); + } + void shrinkToFit() { + variantPools_.shrinkToFit(allocator_); + } + private: + Allocator* allocator_; + bool overflowed_; + StringPool stringPool_; + VariantPoolList variantPools_; +}; +template +struct IsString : false_type {}; +template +struct IsString< + T, typename make_void::AdaptedString>::type> + : true_type {}; +ARDUINOJSON_END_PRIVATE_NAMESPACE +ARDUINOJSON_BEGIN_PUBLIC_NAMESPACE +template +class SerializedValue { + public: + explicit SerializedValue(T str) : str_(str) {} + operator T() const { + return str_; + } + const char* data() const { + return str_.c_str(); + } + size_t size() const { + return str_.length(); + } + private: + T str_; +}; +template +class SerializedValue { + public: + explicit SerializedValue(TChar* p, size_t n) : data_(p), size_(n) {} + operator TChar*() const { + return data_; + } + TChar* data() const { + return data_; + } + size_t size() const { + return size_; + } + private: + TChar* data_; + size_t size_; +}; +using RawString = SerializedValue; +template +inline SerializedValue serialized(T str) { + return SerializedValue(str); +} +template +inline SerializedValue serialized(TChar* p) { + return SerializedValue(p, detail::adaptString(p).size()); +} +template +inline SerializedValue serialized(TChar* p, size_t n) { + return SerializedValue(p, n); +} +ARDUINOJSON_END_PUBLIC_NAMESPACE +#if defined(__clang__) +# pragma clang diagnostic push +# pragma clang diagnostic ignored "-Wconversion" +#elif defined(__GNUC__) +# pragma GCC diagnostic push +# pragma GCC diagnostic ignored "-Wconversion" +#endif +ARDUINOJSON_BEGIN_PRIVATE_NAMESPACE +#ifndef isnan +template +bool isnan(T x) { + return x != x; +} +#endif +#ifndef isinf +template +bool isinf(T x) { + return x != 0.0 && x * 2 == x; +} +#endif +template +struct alias_cast_t { + union { + F raw; + T data; + }; +}; +template +T alias_cast(F raw_data) { + alias_cast_t ac; + ac.raw = raw_data; + return ac.data; +} +ARDUINOJSON_END_PRIVATE_NAMESPACE +#if ARDUINOJSON_ENABLE_PROGMEM +#endif +ARDUINOJSON_BEGIN_PRIVATE_NAMESPACE +#if ARDUINOJSON_ENABLE_PROGMEM +# ifndef ARDUINOJSON_DEFINE_PROGMEM_ARRAY +# define ARDUINOJSON_DEFINE_PROGMEM_ARRAY(type, name, ...) \ + static type const name[] PROGMEM = __VA_ARGS__; +# endif +template +inline const T* pgm_read(const T* const* p) { + return reinterpret_cast(pgm_read_ptr(p)); +} +inline uint32_t pgm_read(const uint32_t* p) { + return pgm_read_dword(p); +} +inline double pgm_read(const double* p) { + return pgm_read_double(p); +} +inline float pgm_read(const float* p) { + return pgm_read_float(p); +} +#else +# ifndef ARDUINOJSON_DEFINE_PROGMEM_ARRAY +# define ARDUINOJSON_DEFINE_PROGMEM_ARRAY(type, name, ...) \ + static type const name[] = __VA_ARGS__; +# endif +template +inline T pgm_read(const T* p) { + return *p; +} +#endif +template +class pgm_ptr { + public: + explicit pgm_ptr(const T* ptr) : ptr_(ptr) {} + T operator[](intptr_t index) const { + return pgm_read(ptr_ + index); + } + private: + const T* ptr_; +}; +template +struct FloatTraits {}; +template +struct FloatTraits { + typedef uint64_t mantissa_type; + static const short mantissa_bits = 52; + static const mantissa_type mantissa_max = + (mantissa_type(1) << mantissa_bits) - 1; + typedef int16_t exponent_type; + static const exponent_type exponent_max = 308; + static pgm_ptr positiveBinaryPowersOfTen() { + ARDUINOJSON_DEFINE_PROGMEM_ARRAY( // + uint64_t, factors, + { + 0x4024000000000000, // 1e1 + 0x4059000000000000, // 1e2 + 0x40C3880000000000, // 1e4 + 0x4197D78400000000, // 1e8 + 0x4341C37937E08000, // 1e16 + 0x4693B8B5B5056E17, // 1e32 + 0x4D384F03E93FF9F5, // 1e64 + 0x5A827748F9301D32, // 1e128 + 0x75154FDD7F73BF3C, // 1e256 + }); + return pgm_ptr(reinterpret_cast(factors)); + } + static pgm_ptr negativeBinaryPowersOfTen() { + ARDUINOJSON_DEFINE_PROGMEM_ARRAY( // + uint64_t, factors, + { + 0x3FB999999999999A, // 1e-1 + 0x3F847AE147AE147B, // 1e-2 + 0x3F1A36E2EB1C432D, // 1e-4 + 0x3E45798EE2308C3A, // 1e-8 + 0x3C9CD2B297D889BC, // 1e-16 + 0x3949F623D5A8A733, // 1e-32 + 0x32A50FFD44F4A73D, // 1e-64 + 0x255BBA08CF8C979D, // 1e-128 + 0x0AC8062864AC6F43 // 1e-256 + }); + return pgm_ptr(reinterpret_cast(factors)); + } + static T nan() { + return forge(0x7ff8000000000000); + } + static T inf() { + return forge(0x7ff0000000000000); + } + static T highest() { + return forge(0x7FEFFFFFFFFFFFFF); + } + template // int64_t + static T highest_for( + typename enable_if::value && is_signed::value && + sizeof(TOut) == 8, + signed>::type* = 0) { + return forge(0x43DFFFFFFFFFFFFF); // 9.2233720368547748e+18 + } + template // uint64_t + static T highest_for( + typename enable_if::value && is_unsigned::value && + sizeof(TOut) == 8, + unsigned>::type* = 0) { + return forge(0x43EFFFFFFFFFFFFF); // 1.8446744073709549568e+19 + } + static T lowest() { + return forge(0xFFEFFFFFFFFFFFFF); + } + static T forge(uint64_t bits) { + return alias_cast(bits); + } +}; +template +struct FloatTraits { + typedef uint32_t mantissa_type; + static const short mantissa_bits = 23; + static const mantissa_type mantissa_max = + (mantissa_type(1) << mantissa_bits) - 1; + typedef int8_t exponent_type; + static const exponent_type exponent_max = 38; + static pgm_ptr positiveBinaryPowersOfTen() { + ARDUINOJSON_DEFINE_PROGMEM_ARRAY(uint32_t, factors, + { + 0x41200000, // 1e1f + 0x42c80000, // 1e2f + 0x461c4000, // 1e4f + 0x4cbebc20, // 1e8f + 0x5a0e1bca, // 1e16f + 0x749dc5ae // 1e32f + }); + return pgm_ptr(reinterpret_cast(factors)); + } + static pgm_ptr negativeBinaryPowersOfTen() { + ARDUINOJSON_DEFINE_PROGMEM_ARRAY(uint32_t, factors, + { + 0x3dcccccd, // 1e-1f + 0x3c23d70a, // 1e-2f + 0x38d1b717, // 1e-4f + 0x322bcc77, // 1e-8f + 0x24e69595, // 1e-16f + 0x0a4fb11f // 1e-32f + }); + return pgm_ptr(reinterpret_cast(factors)); + } + static T forge(uint32_t bits) { + return alias_cast(bits); + } + static T nan() { + return forge(0x7fc00000); + } + static T inf() { + return forge(0x7f800000); + } + static T highest() { + return forge(0x7f7fffff); + } + template // int32_t + static T highest_for( + typename enable_if::value && is_signed::value && + sizeof(TOut) == 4, + signed>::type* = 0) { + return forge(0x4EFFFFFF); // 2.14748352E9 + } + template // uint32_t + static T highest_for( + typename enable_if::value && is_unsigned::value && + sizeof(TOut) == 4, + unsigned>::type* = 0) { + return forge(0x4F7FFFFF); // 4.29496704E9 + } + template // int64_t + static T highest_for( + typename enable_if::value && is_signed::value && + sizeof(TOut) == 8, + signed>::type* = 0) { + return forge(0x5EFFFFFF); // 9.22337148709896192E18 + } + template // uint64_t + static T highest_for( + typename enable_if::value && is_unsigned::value && + sizeof(TOut) == 8, + unsigned>::type* = 0) { + return forge(0x5F7FFFFF); // 1.844674297419792384E19 + } + static T lowest() { + return forge(0xFf7fffff); + } +}; +template +inline TFloat make_float(TFloat m, TExponent e) { + using traits = FloatTraits; + auto powersOfTen = e > 0 ? traits::positiveBinaryPowersOfTen() + : traits::negativeBinaryPowersOfTen(); + if (e <= 0) + e = TExponent(-e); + for (uint8_t index = 0; e != 0; index++) { + if (e & 1) + m *= powersOfTen[index]; + e >>= 1; + } + return m; +} +ARDUINOJSON_END_PRIVATE_NAMESPACE +ARDUINOJSON_BEGIN_PUBLIC_NAMESPACE +#if ARDUINOJSON_USE_DOUBLE +typedef double JsonFloat; +#else +typedef float JsonFloat; +#endif +ARDUINOJSON_END_PUBLIC_NAMESPACE +ARDUINOJSON_BEGIN_PRIVATE_NAMESPACE +template +typename enable_if::value && is_unsigned::value && + is_integral::value && sizeof(TOut) <= sizeof(TIn), + bool>::type +canConvertNumber(TIn value) { + return value <= TIn(numeric_limits::highest()); +} +template +typename enable_if::value && is_unsigned::value && + is_integral::value && sizeof(TIn) < sizeof(TOut), + bool>::type +canConvertNumber(TIn) { + return true; +} +template +typename enable_if::value && is_floating_point::value, + bool>::type +canConvertNumber(TIn) { + return true; +} +template +typename enable_if::value && is_signed::value && + is_integral::value && is_signed::value && + sizeof(TOut) < sizeof(TIn), + bool>::type +canConvertNumber(TIn value) { + return value >= TIn(numeric_limits::lowest()) && + value <= TIn(numeric_limits::highest()); +} +template +typename enable_if::value && is_signed::value && + is_integral::value && is_signed::value && + sizeof(TIn) <= sizeof(TOut), + bool>::type +canConvertNumber(TIn) { + return true; +} +template +typename enable_if::value && is_signed::value && + is_integral::value && is_unsigned::value && + sizeof(TOut) >= sizeof(TIn), + bool>::type +canConvertNumber(TIn value) { + if (value < 0) + return false; + return TOut(value) <= numeric_limits::highest(); +} +template +typename enable_if::value && is_signed::value && + is_integral::value && is_unsigned::value && + sizeof(TOut) < sizeof(TIn), + bool>::type +canConvertNumber(TIn value) { + if (value < 0) + return false; + return value <= TIn(numeric_limits::highest()); +} +template +typename enable_if::value && is_integral::value && + sizeof(TOut) < sizeof(TIn), + bool>::type +canConvertNumber(TIn value) { + return value >= numeric_limits::lowest() && + value <= numeric_limits::highest(); +} +template +typename enable_if::value && is_integral::value && + sizeof(TOut) >= sizeof(TIn), + bool>::type +canConvertNumber(TIn value) { + return value >= numeric_limits::lowest() && + value <= FloatTraits::template highest_for(); +} +template +TOut convertNumber(TIn value) { + return canConvertNumber(value) ? TOut(value) : 0; +} +ARDUINOJSON_END_PRIVATE_NAMESPACE +#if defined(__clang__) +# pragma clang diagnostic pop +#elif defined(__GNUC__) +# pragma GCC diagnostic pop +#endif +ARDUINOJSON_BEGIN_PRIVATE_NAMESPACE +class VariantData; +class VariantSlot; +class CollectionIterator { + friend class CollectionData; + public: + CollectionIterator() : slot_(nullptr), currentId_(NULL_SLOT) {} + void next(const ResourceManager* resources); + bool done() const { + return slot_ == nullptr; + } + bool operator==(const CollectionIterator& other) const { + return slot_ == other.slot_; + } + bool operator!=(const CollectionIterator& other) const { + return slot_ != other.slot_; + } + VariantData* operator->() { + ARDUINOJSON_ASSERT(slot_ != nullptr); + return data(); + } + VariantData& operator*() { + ARDUINOJSON_ASSERT(slot_ != nullptr); + return *data(); + } + const VariantData& operator*() const { + ARDUINOJSON_ASSERT(slot_ != nullptr); + return *data(); + } + const char* key() const; + bool ownsKey() const; + void setKey(StringNode*); + void setKey(const char*); + VariantData* data() { + return reinterpret_cast(slot_); + } + const VariantData* data() const { + return reinterpret_cast(slot_); + } + private: + CollectionIterator(VariantSlot* slot, SlotId slotId); + VariantSlot* slot_; + SlotId currentId_, nextId_; +}; +class CollectionData { + SlotId head_ = NULL_SLOT; + SlotId tail_ = NULL_SLOT; + public: + static void* operator new(size_t, void* p) noexcept { + return p; + } + static void operator delete(void*, void*) noexcept {} + using iterator = CollectionIterator; + iterator createIterator(const ResourceManager* resources) const { + return iterator(resources->getSlot(head_), head_); + } + size_t size(const ResourceManager*) const; + size_t nesting(const ResourceManager*) const; + void clear(ResourceManager* resources); + static void clear(CollectionData* collection, ResourceManager* resources) { + if (!collection) + return; + collection->clear(resources); + } + void remove(iterator it, ResourceManager* resources); + static void remove(CollectionData* collection, iterator it, + ResourceManager* resources) { + if (collection) + return collection->remove(it, resources); + } + SlotId head() const { + return head_; + } + protected: + iterator addSlot(ResourceManager*); + private: + SlotWithId getPreviousSlot(VariantSlot*, const ResourceManager*) const; + void releaseSlot(SlotWithId, ResourceManager*); +}; +inline const VariantData* collectionToVariant( + const CollectionData* collection) { + const void* data = collection; // prevent warning cast-align + return reinterpret_cast(data); +} +inline VariantData* collectionToVariant(CollectionData* collection) { + void* data = collection; // prevent warning cast-align + return reinterpret_cast(data); +} +class ArrayData : public CollectionData { + public: + VariantData* addElement(ResourceManager* resources) { + return addSlot(resources).data(); + } + static VariantData* addElement(ArrayData* array, ResourceManager* resources) { + if (!array) + return nullptr; + return array->addElement(resources); + } + VariantData* getOrAddElement(size_t index, ResourceManager* resources); + VariantData* getElement(size_t index, const ResourceManager* resources) const; + static VariantData* getElement(const ArrayData* array, size_t index, + const ResourceManager* resources) { + if (!array) + return nullptr; + return array->getElement(index, resources); + } + void removeElement(size_t index, ResourceManager* resources); + static void removeElement(ArrayData* array, size_t index, + ResourceManager* resources) { + if (!array) + return; + array->removeElement(index, resources); + } + bool copyFrom(const ArrayData& src, ResourceManager* resources); + static bool copy(ArrayData* dst, const ArrayData* src, + ResourceManager* resources) { + if (!dst || !src) + return false; + return dst->copyFrom(*src, resources); + } + private: + iterator at(size_t index, const ResourceManager* resources) const; +}; +ARDUINOJSON_END_PRIVATE_NAMESPACE +ARDUINOJSON_BEGIN_PUBLIC_NAMESPACE +#if ARDUINOJSON_USE_LONG_LONG +typedef int64_t JsonInteger; +typedef uint64_t JsonUInt; +#else +typedef long JsonInteger; +typedef unsigned long JsonUInt; +#endif +ARDUINOJSON_END_PUBLIC_NAMESPACE +#define ARDUINOJSON_ASSERT_INTEGER_TYPE_IS_SUPPORTED(T) \ + static_assert(sizeof(T) <= sizeof(ArduinoJson::JsonInteger), \ + "To use 64-bit integers with ArduinoJson, you must set " \ + "ARDUINOJSON_USE_LONG_LONG to 1. See " \ + "https://arduinojson.org/v7/api/config/use_long_long/"); +ARDUINOJSON_BEGIN_PRIVATE_NAMESPACE +class ObjectData : public CollectionData { + public: + VariantData* addMember(StringNode* key, ResourceManager* resources) { + ARDUINOJSON_ASSERT(key != nullptr); + auto it = addSlot(resources); + if (it.done()) + return nullptr; + it.setKey(key); + return it.data(); + } + template + VariantData* addMember(TAdaptedString key, ResourceManager* resources) { + ARDUINOJSON_ASSERT(!key.isNull()); + if (key.isLinked()) { + auto it = addSlot(resources); + if (!it.done()) + it.setKey(key.data()); + return it.data(); + } else { + auto storedKey = resources->saveString(key); + if (!storedKey) + return nullptr; + auto it = addSlot(resources); + if (!it.done()) + it.setKey(storedKey); + return it.data(); + } + } + template + VariantData* getOrAddMember(TAdaptedString key, ResourceManager* resources); + template + VariantData* getMember(TAdaptedString key, + const ResourceManager* resources) const; + template + static VariantData* getMember(const ObjectData* object, TAdaptedString key, + const ResourceManager* resources) { + if (!object) + return nullptr; + return object->getMember(key, resources); + } + template + void removeMember(TAdaptedString key, ResourceManager* resources); + template + static void removeMember(ObjectData* obj, TAdaptedString key, + ResourceManager* resources) { + if (!obj) + return; + obj->removeMember(key, resources); + } + private: + template + iterator findKey(TAdaptedString key, const ResourceManager* resources) const; +}; +enum { + VALUE_MASK = 0x7F, + OWNED_VALUE_BIT = 0x01, + VALUE_IS_NULL = 0, + VALUE_IS_RAW_STRING = 0x03, + VALUE_IS_LINKED_STRING = 0x04, + VALUE_IS_OWNED_STRING = 0x05, + VALUE_IS_BOOLEAN = 0x06, + NUMBER_BIT = 0x08, + VALUE_IS_UNSIGNED_INTEGER = 0x08, + VALUE_IS_SIGNED_INTEGER = 0x0A, + VALUE_IS_FLOAT = 0x0C, + COLLECTION_MASK = 0x60, + VALUE_IS_OBJECT = 0x20, + VALUE_IS_ARRAY = 0x40, + OWNED_KEY_BIT = 0x80 +}; +union VariantContent { + VariantContent() {} + JsonFloat asFloat; + bool asBoolean; + JsonUInt asUnsignedInteger; + JsonInteger asSignedInteger; + ArrayData asArray; + ObjectData asObject; + CollectionData asCollection; + const char* asLinkedString; + struct StringNode* asOwnedString; +}; +struct StringNode; +class VariantSlot { + VariantContent content_; + uint8_t flags_; + SlotId next_; + const char* key_; + public: + static void* operator new(size_t, void* p) noexcept { + return p; + } + static void operator delete(void*, void*) noexcept {} + VariantSlot() : flags_(0), next_(NULL_SLOT), key_(0) {} + VariantData* data() { + return reinterpret_cast(&content_); + } + const VariantData* data() const { + return reinterpret_cast(&content_); + } + SlotId next() const { + return next_; + } + void setNext(SlotId slot) { + next_ = slot; + } + void setKey(const char* k) { + ARDUINOJSON_ASSERT(k); + flags_ &= VALUE_MASK; + key_ = k; + } + void setKey(StringNode* k) { + ARDUINOJSON_ASSERT(k); + flags_ |= OWNED_KEY_BIT; + key_ = k->data; + } + const char* key() const { + return key_; + } + bool ownsKey() const { + return (flags_ & OWNED_KEY_BIT) != 0; + } +}; +inline VariantData* slotData(VariantSlot* slot) { + return reinterpret_cast(slot); +} +constexpr size_t sizeofArray(size_t n) { + return n * sizeof(VariantSlot); +} +constexpr size_t sizeofObject(size_t n) { + return n * sizeof(VariantSlot); +} +template +T parseNumber(const char* s); +class VariantData { + VariantContent content_; // must be first to allow cast from array to variant + uint8_t flags_; + public: + VariantData() : flags_(VALUE_IS_NULL) {} + template + typename TVisitor::result_type accept(TVisitor& visit) const { + switch (type()) { + case VALUE_IS_FLOAT: + return visit.visit(content_.asFloat); + case VALUE_IS_ARRAY: + return visit.visit(content_.asArray); + case VALUE_IS_OBJECT: + return visit.visit(content_.asObject); + case VALUE_IS_LINKED_STRING: + return visit.visit(JsonString(content_.asLinkedString)); + case VALUE_IS_OWNED_STRING: + return visit.visit(JsonString(content_.asOwnedString->data, + content_.asOwnedString->length, + JsonString::Copied)); + case VALUE_IS_RAW_STRING: + return visit.visit(RawString(content_.asOwnedString->data, + content_.asOwnedString->length)); + case VALUE_IS_SIGNED_INTEGER: + return visit.visit(content_.asSignedInteger); + case VALUE_IS_UNSIGNED_INTEGER: + return visit.visit(content_.asUnsignedInteger); + case VALUE_IS_BOOLEAN: + return visit.visit(content_.asBoolean != 0); + default: + return visit.visit(nullptr); + } + } + template + static typename TVisitor::result_type accept(const VariantData* var, + TVisitor& visit) { + if (var != 0) + return var->accept(visit); + else + return visit.visit(nullptr); + } + VariantData* addElement(ResourceManager* resources) { + auto array = isNull() ? &toArray() : asArray(); + return detail::ArrayData::addElement(array, resources); + } + static VariantData* addElement(VariantData* var, ResourceManager* resources) { + if (!var) + return nullptr; + return var->addElement(resources); + } + bool asBoolean() const { + switch (type()) { + case VALUE_IS_BOOLEAN: + return content_.asBoolean; + case VALUE_IS_SIGNED_INTEGER: + case VALUE_IS_UNSIGNED_INTEGER: + return content_.asUnsignedInteger != 0; + case VALUE_IS_FLOAT: + return content_.asFloat != 0; + case VALUE_IS_NULL: + return false; + default: + return true; + } + } + ArrayData* asArray() { + return isArray() ? &content_.asArray : 0; + } + const ArrayData* asArray() const { + return const_cast(this)->asArray(); + } + CollectionData* asCollection() { + return isCollection() ? &content_.asCollection : 0; + } + const CollectionData* asCollection() const { + return const_cast(this)->asCollection(); + } + template + T asFloat() const { + static_assert(is_floating_point::value, "T must be a floating point"); + switch (type()) { + case VALUE_IS_BOOLEAN: + return static_cast(content_.asBoolean); + case VALUE_IS_UNSIGNED_INTEGER: + return static_cast(content_.asUnsignedInteger); + case VALUE_IS_SIGNED_INTEGER: + return static_cast(content_.asSignedInteger); + case VALUE_IS_LINKED_STRING: + case VALUE_IS_OWNED_STRING: + return parseNumber(content_.asOwnedString->data); + case VALUE_IS_FLOAT: + return static_cast(content_.asFloat); + default: + return 0; + } + } + template + T asIntegral() const { + static_assert(is_integral::value, "T must be an integral type"); + switch (type()) { + case VALUE_IS_BOOLEAN: + return content_.asBoolean; + case VALUE_IS_UNSIGNED_INTEGER: + return convertNumber(content_.asUnsignedInteger); + case VALUE_IS_SIGNED_INTEGER: + return convertNumber(content_.asSignedInteger); + case VALUE_IS_LINKED_STRING: + return parseNumber(content_.asLinkedString); + case VALUE_IS_OWNED_STRING: + return parseNumber(content_.asOwnedString->data); + case VALUE_IS_FLOAT: + return convertNumber(content_.asFloat); + default: + return 0; + } + } + ObjectData* asObject() { + return isObject() ? &content_.asObject : 0; + } + const ObjectData* asObject() const { + return const_cast(this)->asObject(); + } + JsonString asRawString() const { + switch (type()) { + case VALUE_IS_RAW_STRING: + return JsonString(content_.asOwnedString->data, + content_.asOwnedString->length, JsonString::Copied); + default: + return JsonString(); + } + } + JsonString asString() const { + switch (type()) { + case VALUE_IS_LINKED_STRING: + return JsonString(content_.asLinkedString, JsonString::Linked); + case VALUE_IS_OWNED_STRING: + return JsonString(content_.asOwnedString->data, + content_.asOwnedString->length, JsonString::Copied); + default: + return JsonString(); + } + } + VariantData* getElement(size_t index, + const ResourceManager* resources) const { + return ArrayData::getElement(asArray(), index, resources); + } + static VariantData* getElement(const VariantData* var, size_t index, + const ResourceManager* resources) { + return var != 0 ? var->getElement(index, resources) : 0; + } + template + VariantData* getMember(TAdaptedString key, + const ResourceManager* resources) const { + return ObjectData::getMember(asObject(), key, resources); + } + template + static VariantData* getMember(const VariantData* var, TAdaptedString key, + const ResourceManager* resources) { + if (!var) + return 0; + return var->getMember(key, resources); + } + VariantData* getOrAddElement(size_t index, ResourceManager* resources) { + auto array = isNull() ? &toArray() : asArray(); + if (!array) + return nullptr; + return array->getOrAddElement(index, resources); + } + template + VariantData* getOrAddMember(TAdaptedString key, ResourceManager* resources) { + if (key.isNull()) + return nullptr; + auto obj = isNull() ? &toObject() : asObject(); + if (!obj) + return nullptr; + return obj->getOrAddMember(key, resources); + } + bool isArray() const { + return (flags_ & VALUE_IS_ARRAY) != 0; + } + bool isBoolean() const { + return type() == VALUE_IS_BOOLEAN; + } + bool isCollection() const { + return (flags_ & COLLECTION_MASK) != 0; + } + bool isFloat() const { + return (flags_ & NUMBER_BIT) != 0; + } + template + bool isInteger() const { + switch (type()) { + case VALUE_IS_UNSIGNED_INTEGER: + return canConvertNumber(content_.asUnsignedInteger); + case VALUE_IS_SIGNED_INTEGER: + return canConvertNumber(content_.asSignedInteger); + default: + return false; + } + } + bool isNull() const { + return type() == VALUE_IS_NULL; + } + static bool isNull(const VariantData* var) { + if (!var) + return true; + return var->isNull(); + } + bool isObject() const { + return (flags_ & VALUE_IS_OBJECT) != 0; + } + bool isString() const { + return type() == VALUE_IS_LINKED_STRING || type() == VALUE_IS_OWNED_STRING; + } + size_t nesting(const ResourceManager* resources) const { + auto collection = asCollection(); + if (collection) + return collection->nesting(resources); + else + return 0; + } + static size_t nesting(const VariantData* var, + const ResourceManager* resources) { + if (!var) + return 0; + return var->nesting(resources); + } + void removeElement(size_t index, ResourceManager* resources) { + ArrayData::removeElement(asArray(), index, resources); + } + static void removeElement(VariantData* var, size_t index, + ResourceManager* resources) { + if (!var) + return; + var->removeElement(index, resources); + } + template + void removeMember(TAdaptedString key, ResourceManager* resources) { + ObjectData::removeMember(asObject(), key, resources); + } + template + static void removeMember(VariantData* var, TAdaptedString key, + ResourceManager* resources) { + if (!var) + return; + var->removeMember(key, resources); + } + void reset() { + flags_ = VALUE_IS_NULL; + } + void setBoolean(bool value) { + setType(VALUE_IS_BOOLEAN); + content_.asBoolean = value; + } + void setBoolean(bool value, ResourceManager* resources) { + release(resources); + setBoolean(value); + } + void setFloat(JsonFloat value) { + setType(VALUE_IS_FLOAT); + content_.asFloat = value; + } + void setFloat(JsonFloat value, ResourceManager* resources) { + release(resources); + setFloat(value); + } + template + typename enable_if::value>::type setInteger(T value) { + setType(VALUE_IS_SIGNED_INTEGER); + content_.asSignedInteger = value; + } + template + typename enable_if::value>::type setInteger(T value) { + setType(VALUE_IS_UNSIGNED_INTEGER); + content_.asUnsignedInteger = static_cast(value); + } + template + void setInteger(T value, ResourceManager* resources) { + release(resources); + setInteger(value); + } + void setNull() { + setType(VALUE_IS_NULL); + } + void setNull(ResourceManager* resources) { + release(resources); + setNull(); + } + static void setNull(VariantData* var, ResourceManager* resources) { + if (!var) + return; + var->setNull(resources); + } + void setRawString(StringNode* s) { + ARDUINOJSON_ASSERT(s); + setType(VALUE_IS_RAW_STRING); + content_.asOwnedString = s; + } + template + void setRawString(SerializedValue value, ResourceManager* resources) { + release(resources); + auto dup = resources->saveString(adaptString(value.data(), value.size())); + if (dup) + setRawString(dup); + else + setNull(); + } + template + static void setRawString(VariantData* var, SerializedValue value, + ResourceManager* resources) { + if (!var) + return; + var->setRawString(value, resources); + } + template + void setString(TAdaptedString value, ResourceManager* resources) { + setNull(resources); + if (value.isNull()) + return; + if (value.isLinked()) { + setLinkedString(value.data()); + return; + } + auto dup = resources->saveString(value); + if (dup) + setOwnedString(dup); + } + template + static void setString(VariantData* var, TAdaptedString value, + ResourceManager* resources) { + if (!var) + return; + var->setString(value, resources); + } + void setLinkedString(const char* s) { + ARDUINOJSON_ASSERT(s); + setType(VALUE_IS_LINKED_STRING); + content_.asLinkedString = s; + } + void setOwnedString(StringNode* s) { + ARDUINOJSON_ASSERT(s); + setType(VALUE_IS_OWNED_STRING); + content_.asOwnedString = s; + } + size_t size(const ResourceManager* resources) const { + return isCollection() ? content_.asCollection.size(resources) : 0; + } + static size_t size(const VariantData* var, const ResourceManager* resources) { + return var != 0 ? var->size(resources) : 0; + } + ArrayData& toArray() { + setType(VALUE_IS_ARRAY); + new (&content_.asArray) ArrayData(); + return content_.asArray; + } + ArrayData& toArray(ResourceManager* resources) { + release(resources); + return toArray(); + } + static ArrayData* toArray(VariantData* var, ResourceManager* resources) { + if (!var) + return 0; + return &var->toArray(resources); + } + ObjectData& toObject() { + setType(VALUE_IS_OBJECT); + new (&content_.asObject) ObjectData(); + return content_.asObject; + } + ObjectData& toObject(ResourceManager* resources) { + release(resources); + return toObject(); + } + static ObjectData* toObject(VariantData* var, ResourceManager* resources) { + if (!var) + return 0; + return &var->toObject(resources); + } + uint8_t type() const { + return flags_ & VALUE_MASK; + } + private: + void release(ResourceManager* resources) { + if (flags_ & OWNED_VALUE_BIT) + resources->dereferenceString(content_.asOwnedString->data); + auto collection = asCollection(); + if (collection) + collection->clear(resources); + } + void setType(uint8_t t) { + flags_ &= OWNED_KEY_BIT; + flags_ |= t; + } +}; +ARDUINOJSON_END_PRIVATE_NAMESPACE +ARDUINOJSON_BEGIN_PUBLIC_NAMESPACE +class JsonArray; +class JsonObject; +class JsonVariant; +ARDUINOJSON_END_PUBLIC_NAMESPACE +ARDUINOJSON_BEGIN_PRIVATE_NAMESPACE +template +struct VariantTo {}; +template <> +struct VariantTo { + typedef JsonArray type; +}; +template <> +struct VariantTo { + typedef JsonObject type; +}; +template <> +struct VariantTo { + typedef JsonVariant type; +}; +class VariantAttorney { + public: + template + static auto getResourceManager(TClient& client) + -> decltype(client.getResourceManager()) { + return client.getResourceManager(); + } + template + static auto getData(TClient& client) -> decltype(client.getData()) { + return client.getData(); + } + template + static VariantData* getOrCreateData(TClient& client) { + return client.getOrCreateData(); + } +}; +enum CompareResult { + COMPARE_RESULT_DIFFER = 0, + COMPARE_RESULT_EQUAL = 1, + COMPARE_RESULT_GREATER = 2, + COMPARE_RESULT_LESS = 4, + COMPARE_RESULT_GREATER_OR_EQUAL = 3, + COMPARE_RESULT_LESS_OR_EQUAL = 5 +}; +template +CompareResult arithmeticCompare(const T& lhs, const T& rhs) { + if (lhs < rhs) + return COMPARE_RESULT_LESS; + else if (lhs > rhs) + return COMPARE_RESULT_GREATER; + else + return COMPARE_RESULT_EQUAL; +} +template +CompareResult arithmeticCompare( + const T1& lhs, const T2& rhs, + typename enable_if::value && is_integral::value && + sizeof(T1) < sizeof(T2)>::type* = 0) { + return arithmeticCompare(static_cast(lhs), rhs); +} +template +CompareResult arithmeticCompare( + const T1& lhs, const T2& rhs, + typename enable_if::value && is_integral::value && + sizeof(T2) < sizeof(T1)>::type* = 0) { + return arithmeticCompare(lhs, static_cast(rhs)); +} +template +CompareResult arithmeticCompare( + const T1& lhs, const T2& rhs, + typename enable_if::value && is_integral::value && + is_signed::value == is_signed::value && + sizeof(T2) == sizeof(T1)>::type* = 0) { + return arithmeticCompare(lhs, static_cast(rhs)); +} +template +CompareResult arithmeticCompare( + const T1& lhs, const T2& rhs, + typename enable_if::value && is_integral::value && + is_unsigned::value && is_signed::value && + sizeof(T2) == sizeof(T1)>::type* = 0) { + if (rhs < 0) + return COMPARE_RESULT_GREATER; + return arithmeticCompare(lhs, static_cast(rhs)); +} +template +CompareResult arithmeticCompare( + const T1& lhs, const T2& rhs, + typename enable_if::value && is_integral::value && + is_signed::value && is_unsigned::value && + sizeof(T2) == sizeof(T1)>::type* = 0) { + if (lhs < 0) + return COMPARE_RESULT_LESS; + return arithmeticCompare(static_cast(lhs), rhs); +} +template +CompareResult arithmeticCompare( + const T1& lhs, const T2& rhs, + typename enable_if::value || + is_floating_point::value>::type* = 0) { + return arithmeticCompare(static_cast(lhs), + static_cast(rhs)); +} +template +CompareResult arithmeticCompareNegateLeft( + JsonUInt, const T2&, + typename enable_if::value>::type* = 0) { + return COMPARE_RESULT_LESS; +} +template +CompareResult arithmeticCompareNegateLeft( + JsonUInt lhs, const T2& rhs, + typename enable_if::value>::type* = 0) { + if (rhs > 0) + return COMPARE_RESULT_LESS; + return arithmeticCompare(-rhs, static_cast(lhs)); +} +template +CompareResult arithmeticCompareNegateRight( + const T1&, JsonUInt, + typename enable_if::value>::type* = 0) { + return COMPARE_RESULT_GREATER; +} +template +CompareResult arithmeticCompareNegateRight( + const T1& lhs, JsonUInt rhs, + typename enable_if::value>::type* = 0) { + if (lhs > 0) + return COMPARE_RESULT_GREATER; + return arithmeticCompare(static_cast(rhs), -lhs); +} +struct VariantTag {}; +template +struct IsVariant : is_base_of {}; +ARDUINOJSON_END_PRIVATE_NAMESPACE +ARDUINOJSON_BEGIN_PUBLIC_NAMESPACE +class JsonVariantConst; +ARDUINOJSON_END_PUBLIC_NAMESPACE +ARDUINOJSON_BEGIN_PRIVATE_NAMESPACE +template +CompareResult compare(JsonVariantConst lhs, + const T& rhs); // VariantCompare.cpp +struct VariantOperatorTag {}; +template +struct VariantOperators : VariantOperatorTag { + template + friend + typename enable_if::value && !is_array::value, T>::type + operator|(const TVariant& variant, const T& defaultValue) { + if (variant.template is()) + return variant.template as(); + else + return defaultValue; + } + friend const char* operator|(const TVariant& variant, + const char* defaultValue) { + if (variant.template is()) + return variant.template as(); + else + return defaultValue; + } + template + friend typename enable_if::value, JsonVariantConst>::type + operator|(const TVariant& variant, T defaultValue) { + if (variant) + return variant; + else + return defaultValue; + } + template + friend bool operator==(T* lhs, TVariant rhs) { + return compare(rhs, lhs) == COMPARE_RESULT_EQUAL; + } + template + friend bool operator==(const T& lhs, TVariant rhs) { + return compare(rhs, lhs) == COMPARE_RESULT_EQUAL; + } + template + friend bool operator==(TVariant lhs, T* rhs) { + return compare(lhs, rhs) == COMPARE_RESULT_EQUAL; + } + template + friend + typename enable_if::value, bool>::type + operator==(TVariant lhs, const T& rhs) { + return compare(lhs, rhs) == COMPARE_RESULT_EQUAL; + } + template + friend bool operator!=(T* lhs, TVariant rhs) { + return compare(rhs, lhs) != COMPARE_RESULT_EQUAL; + } + template + friend bool operator!=(const T& lhs, TVariant rhs) { + return compare(rhs, lhs) != COMPARE_RESULT_EQUAL; + } + template + friend bool operator!=(TVariant lhs, T* rhs) { + return compare(lhs, rhs) != COMPARE_RESULT_EQUAL; + } + template + friend + typename enable_if::value, bool>::type + operator!=(TVariant lhs, const T& rhs) { + return compare(lhs, rhs) != COMPARE_RESULT_EQUAL; + } + template + friend bool operator<(T* lhs, TVariant rhs) { + return compare(rhs, lhs) == COMPARE_RESULT_GREATER; + } + template + friend bool operator<(const T& lhs, TVariant rhs) { + return compare(rhs, lhs) == COMPARE_RESULT_GREATER; + } + template + friend bool operator<(TVariant lhs, T* rhs) { + return compare(lhs, rhs) == COMPARE_RESULT_LESS; + } + template + friend + typename enable_if::value, bool>::type + operator<(TVariant lhs, const T& rhs) { + return compare(lhs, rhs) == COMPARE_RESULT_LESS; + } + template + friend bool operator<=(T* lhs, TVariant rhs) { + return (compare(rhs, lhs) & COMPARE_RESULT_GREATER_OR_EQUAL) != 0; + } + template + friend bool operator<=(const T& lhs, TVariant rhs) { + return (compare(rhs, lhs) & COMPARE_RESULT_GREATER_OR_EQUAL) != 0; + } + template + friend bool operator<=(TVariant lhs, T* rhs) { + return (compare(lhs, rhs) & COMPARE_RESULT_LESS_OR_EQUAL) != 0; + } + template + friend + typename enable_if::value, bool>::type + operator<=(TVariant lhs, const T& rhs) { + return (compare(lhs, rhs) & COMPARE_RESULT_LESS_OR_EQUAL) != 0; + } + template + friend bool operator>(T* lhs, TVariant rhs) { + return compare(rhs, lhs) == COMPARE_RESULT_LESS; + } + template + friend bool operator>(const T& lhs, TVariant rhs) { + return compare(rhs, lhs) == COMPARE_RESULT_LESS; + } + template + friend bool operator>(TVariant lhs, T* rhs) { + return compare(lhs, rhs) == COMPARE_RESULT_GREATER; + } + template + friend + typename enable_if::value, bool>::type + operator>(TVariant lhs, const T& rhs) { + return compare(lhs, rhs) == COMPARE_RESULT_GREATER; + } + template + friend bool operator>=(T* lhs, TVariant rhs) { + return (compare(rhs, lhs) & COMPARE_RESULT_LESS_OR_EQUAL) != 0; + } + template + friend bool operator>=(const T& lhs, TVariant rhs) { + return (compare(rhs, lhs) & COMPARE_RESULT_LESS_OR_EQUAL) != 0; + } + template + friend bool operator>=(TVariant lhs, T* rhs) { + return (compare(lhs, rhs) & COMPARE_RESULT_GREATER_OR_EQUAL) != 0; + } + template + friend + typename enable_if::value, bool>::type + operator>=(TVariant lhs, const T& rhs) { + return (compare(lhs, rhs) & COMPARE_RESULT_GREATER_OR_EQUAL) != 0; + } +}; +ARDUINOJSON_END_PRIVATE_NAMESPACE +ARDUINOJSON_BEGIN_PUBLIC_NAMESPACE +class JsonArray; +class JsonObject; +class JsonVariantConst : public detail::VariantTag, + public detail::VariantOperators { + friend class detail::VariantAttorney; + public: + JsonVariantConst() : data_(nullptr), resources_(nullptr) {} + explicit JsonVariantConst(const detail::VariantData* data, + const detail::ResourceManager* resources) + : data_(data), resources_(resources) {} + bool isNull() const { + return detail::VariantData::isNull(data_); + } + bool isUnbound() const { + return !data_; + } + size_t nesting() const { + return detail::VariantData::nesting(data_, resources_); + } + size_t size() const { + return detail::VariantData::size(data_, resources_); + } + template + typename detail::enable_if::value && + !detail::is_same::value, + T>::type + as() const { + return Converter::fromJson(*this); + } + template + typename detail::enable_if::value && + !detail::is_same::value, + bool>::type + is() const { + return Converter::checkJson(*this); + } + template + operator T() const { + return as(); + } + JsonVariantConst operator[](size_t index) const { + return JsonVariantConst( + detail::VariantData::getElement(data_, index, resources_), resources_); + } + template + typename detail::enable_if::value, + JsonVariantConst>::type + operator[](const TString& key) const { + return JsonVariantConst(detail::VariantData::getMember( + data_, detail::adaptString(key), resources_), + resources_); + } + template + typename detail::enable_if::value, + JsonVariantConst>::type + operator[](TChar* key) const { + return JsonVariantConst(detail::VariantData::getMember( + data_, detail::adaptString(key), resources_), + resources_); + } + template + typename detail::enable_if::value, bool>::type + containsKey(const TString& key) const { + return detail::VariantData::getMember(getData(), detail::adaptString(key), + resources_) != 0; + } + template + typename detail::enable_if::value, bool>::type + containsKey(TChar* key) const { + return detail::VariantData::getMember(getData(), detail::adaptString(key), + resources_) != 0; + } + ARDUINOJSON_DEPRECATED("always returns zero") + size_t memoryUsage() const { + return 0; + } + protected: + const detail::VariantData* getData() const { + return data_; + } + const detail::ResourceManager* getResourceManager() const { + return resources_; + } + private: + const detail::VariantData* data_; + const detail::ResourceManager* resources_; +}; +class JsonVariant; +ARDUINOJSON_END_PUBLIC_NAMESPACE +ARDUINOJSON_BEGIN_PRIVATE_NAMESPACE +template +class ElementProxy; +template +class MemberProxy; +template +class VariantRefBase : public VariantTag { + friend class VariantAttorney; + public: + void clear() const { + VariantData::setNull(getOrCreateData(), getResourceManager()); + } + bool isNull() const { + return VariantData::isNull(getData()); + } + bool isUnbound() const { + return !getData(); + } + template + typename enable_if::value, T>::type as() + const { + return Converter::fromJson(getVariantConst()); + } + template + typename enable_if::value, T>::type as() const; + template ::value>::type> + operator T() const { + return as(); + } + template + typename enable_if::value, JsonArray>::type to() const; + template + typename enable_if::value, JsonObject>::type to() + const; + template + typename enable_if::value, JsonVariant>::type to() + const; + template + FORCE_INLINE + typename enable_if::value, bool>::type + is() const; + template + FORCE_INLINE + typename enable_if::value, bool>::type + is() const { + return Converter::checkJson(getVariantConst()); + } + template + bool set(const T& value) const; + template + bool set(T* value) const; + size_t size() const { + return VariantData::size(getData(), getResourceManager()); + } + size_t nesting() const { + return VariantData::nesting(getData(), getResourceManager()); + } + template + typename enable_if::value, T>::type add() const { + return add().template to(); + } + template + typename enable_if::value, T>::type add() const; + template + bool add(const T& value) const { + return add().set(value); + } + template + bool add(T* value) const { + return add().set(value); + } + void remove(size_t index) const { + VariantData::removeElement(getData(), index, getResourceManager()); + } + template + typename enable_if::value>::type remove(TChar* key) const { + VariantData::removeMember(getData(), adaptString(key), + getResourceManager()); + } + template + typename enable_if::value>::type remove( + const TString& key) const { + VariantData::removeMember(getData(), adaptString(key), + getResourceManager()); + } + ElementProxy operator[](size_t index) const; + template + typename enable_if::value, bool>::type containsKey( + const TString& key) const; + template + typename enable_if::value, bool>::type containsKey( + TChar* key) const; + template + FORCE_INLINE typename enable_if::value, + MemberProxy>::type + operator[](const TString& key) const; + template + FORCE_INLINE typename enable_if::value, + MemberProxy>::type + operator[](TChar* key) const; + ARDUINOJSON_DEPRECATED("use add() instead") + JsonVariant add() const; + ARDUINOJSON_DEPRECATED("use add() instead") + JsonArray createNestedArray() const; + template + ARDUINOJSON_DEPRECATED("use var[key].to() instead") + JsonArray createNestedArray(TChar* key) const; + template + ARDUINOJSON_DEPRECATED("use var[key].to() instead") + JsonArray createNestedArray(const TString& key) const; + ARDUINOJSON_DEPRECATED("use add() instead") + JsonObject createNestedObject() const; + template + ARDUINOJSON_DEPRECATED("use var[key].to() instead") + JsonObject createNestedObject(TChar* key) const; + template + ARDUINOJSON_DEPRECATED("use var[key].to() instead") + JsonObject createNestedObject(const TString& key) const; + ARDUINOJSON_DEPRECATED("always returns zero") + size_t memoryUsage() const { + return 0; + } + ARDUINOJSON_DEPRECATED("performs a deep copy") + void shallowCopy(JsonVariantConst src) const { + set(src); + } + private: + TDerived& derived() { + return static_cast(*this); + } + const TDerived& derived() const { + return static_cast(*this); + } + ResourceManager* getResourceManager() const { + return VariantAttorney::getResourceManager(derived()); + } + VariantData* getData() const { + return VariantAttorney::getData(derived()); + } + VariantData* getOrCreateData() const { + return VariantAttorney::getOrCreateData(derived()); + } + FORCE_INLINE ArduinoJson::JsonVariant getVariant() const; + FORCE_INLINE ArduinoJson::JsonVariantConst getVariantConst() const { + return ArduinoJson::JsonVariantConst(getData(), getResourceManager()); + } + ArduinoJson::JsonVariant getOrCreateVariant() const; +}; +template +class ElementProxy : public VariantRefBase>, + public VariantOperators> { + friend class VariantAttorney; + public: + ElementProxy(TUpstream upstream, size_t index) + : upstream_(upstream), index_(index) {} + ElementProxy(const ElementProxy& src) + : upstream_(src.upstream_), index_(src.index_) {} + ElementProxy& operator=(const ElementProxy& src) { + this->set(src); + return *this; + } + template + ElementProxy& operator=(const T& src) { + this->set(src); + return *this; + } + template + ElementProxy& operator=(T* src) { + this->set(src); + return *this; + } + private: + ResourceManager* getResourceManager() const { + return VariantAttorney::getResourceManager(upstream_); + } + FORCE_INLINE VariantData* getData() const { + return VariantData::getElement( + VariantAttorney::getData(upstream_), index_, + VariantAttorney::getResourceManager(upstream_)); + } + VariantData* getOrCreateData() const { + auto data = VariantAttorney::getOrCreateData(upstream_); + if (!data) + return nullptr; + return data->getOrAddElement( + index_, VariantAttorney::getResourceManager(upstream_)); + } + TUpstream upstream_; + size_t index_; +}; +ARDUINOJSON_END_PRIVATE_NAMESPACE +ARDUINOJSON_BEGIN_PUBLIC_NAMESPACE +class JsonVariant : public detail::VariantRefBase, + public detail::VariantOperators { + friend class detail::VariantAttorney; + public: + JsonVariant() : data_(0), resources_(0) {} + JsonVariant(detail::VariantData* data, detail::ResourceManager* resources) + : data_(data), resources_(resources) {} + private: + detail::ResourceManager* getResourceManager() const { + return resources_; + } + detail::VariantData* getData() const { + return data_; + } + detail::VariantData* getOrCreateData() const { + return data_; + } + detail::VariantData* data_; + detail::ResourceManager* resources_; +}; +namespace detail { +bool copyVariant(JsonVariant dst, JsonVariantConst src); +} +template <> +struct Converter : private detail::VariantAttorney { + static void toJson(JsonVariantConst src, JsonVariant dst) { + copyVariant(dst, src); + } + static JsonVariant fromJson(JsonVariant src) { + return src; + } + static detail::InvalidConversion fromJson( + JsonVariantConst); + static bool checkJson(JsonVariant src) { + auto data = getData(src); + return !!data; + } + static bool checkJson(JsonVariantConst) { + return false; + } +}; +template <> +struct Converter : private detail::VariantAttorney { + static void toJson(JsonVariantConst src, JsonVariant dst) { + copyVariant(dst, src); + } + static JsonVariantConst fromJson(JsonVariantConst src) { + return JsonVariantConst(getData(src), getResourceManager(src)); + } + static bool checkJson(JsonVariantConst src) { + auto data = getData(src); + return !!data; + } +}; +template +class Ptr { + public: + Ptr(T value) : value_(value) {} + T* operator->() { + return &value_; + } + T& operator*() { + return value_; + } + private: + T value_; +}; +class JsonArrayIterator { + friend class JsonArray; + public: + JsonArrayIterator() {} + explicit JsonArrayIterator(detail::ArrayData::iterator iterator, + detail::ResourceManager* resources) + : iterator_(iterator), resources_(resources) {} + JsonVariant operator*() { + return JsonVariant(iterator_.data(), resources_); + } + Ptr operator->() { + return operator*(); + } + bool operator==(const JsonArrayIterator& other) const { + return iterator_ == other.iterator_; + } + bool operator!=(const JsonArrayIterator& other) const { + return iterator_ != other.iterator_; + } + JsonArrayIterator& operator++() { + iterator_.next(resources_); + return *this; + } + private: + detail::ArrayData::iterator iterator_; + detail::ResourceManager* resources_; +}; +class JsonArrayConstIterator { + friend class JsonArray; + public: + JsonArrayConstIterator() {} + explicit JsonArrayConstIterator(detail::ArrayData::iterator iterator, + const detail::ResourceManager* resources) + : iterator_(iterator), resources_(resources) {} + JsonVariantConst operator*() const { + return JsonVariantConst(iterator_.data(), resources_); + } + Ptr operator->() { + return operator*(); + } + bool operator==(const JsonArrayConstIterator& other) const { + return iterator_ == other.iterator_; + } + bool operator!=(const JsonArrayConstIterator& other) const { + return iterator_ != other.iterator_; + } + JsonArrayConstIterator& operator++() { + iterator_.next(resources_); + return *this; + } + private: + detail::ArrayData::iterator iterator_; + const detail::ResourceManager* resources_; +}; +class JsonObject; +class JsonArrayConst : public detail::VariantOperators { + friend class JsonArray; + friend class detail::VariantAttorney; + public: + typedef JsonArrayConstIterator iterator; + iterator begin() const { + if (!data_) + return iterator(); + return iterator(data_->createIterator(resources_), resources_); + } + iterator end() const { + return iterator(); + } + JsonArrayConst() : data_(0) {} + JsonArrayConst(const detail::ArrayData* data, + const detail::ResourceManager* resources) + : data_(data), resources_(resources) {} + JsonVariantConst operator[](size_t index) const { + return JsonVariantConst( + detail::ArrayData::getElement(data_, index, resources_), resources_); + } + operator JsonVariantConst() const { + return JsonVariantConst(getData(), resources_); + } + bool isNull() const { + return data_ == 0; + } + operator bool() const { + return data_ != 0; + } + size_t nesting() const { + return detail::VariantData::nesting(getData(), resources_); + } + size_t size() const { + return data_ ? data_->size(resources_) : 0; + } + ARDUINOJSON_DEPRECATED("always returns zero") + size_t memoryUsage() const { + return 0; + } + private: + const detail::VariantData* getData() const { + return collectionToVariant(data_); + } + const detail::ArrayData* data_; + const detail::ResourceManager* resources_; +}; +inline bool operator==(JsonArrayConst lhs, JsonArrayConst rhs) { + if (!lhs && !rhs) + return true; + if (!lhs || !rhs) + return false; + auto a = lhs.begin(); + auto b = rhs.begin(); + for (;;) { + if (a == b) // same pointer or both null + return true; + if (a == lhs.end() || b == rhs.end()) + return false; + if (*a != *b) + return false; + ++a; + ++b; + } +} +class JsonObject; +class JsonArray : public detail::VariantOperators { + friend class detail::VariantAttorney; + public: + typedef JsonArrayIterator iterator; + JsonArray() : data_(0), resources_(0) {} + JsonArray(detail::ArrayData* data, detail::ResourceManager* resources) + : data_(data), resources_(resources) {} + operator JsonVariant() { + void* data = data_; // prevent warning cast-align + return JsonVariant(reinterpret_cast(data), + resources_); + } + operator JsonArrayConst() const { + return JsonArrayConst(data_, resources_); + } + template + typename detail::enable_if::value, T>::type + add() const { + return add().to(); + } + template + typename detail::enable_if::value, T>::type + add() const { + return JsonVariant(detail::ArrayData::addElement(data_, resources_), + resources_); + } + template + bool add(const T& value) const { + return add().set(value); + } + template + bool add(T* value) const { + return add().set(value); + } + iterator begin() const { + if (!data_) + return iterator(); + return iterator(data_->createIterator(resources_), resources_); + } + iterator end() const { + return iterator(); + } + bool set(JsonArrayConst src) const { + if (!data_) + return false; + clear(); + for (auto element : src) { + if (!add(element)) + return false; + } + return true; + } + void remove(iterator it) const { + detail::ArrayData::remove(data_, it.iterator_, resources_); + } + void remove(size_t index) const { + detail::ArrayData::removeElement(data_, index, resources_); + } + void clear() const { + detail::ArrayData::clear(data_, resources_); + } + detail::ElementProxy operator[](size_t index) const { + return {*this, index}; + } + operator JsonVariantConst() const { + return JsonVariantConst(collectionToVariant(data_), resources_); + } + bool isNull() const { + return data_ == 0; + } + operator bool() const { + return data_ != 0; + } + size_t nesting() const { + return detail::VariantData::nesting(collectionToVariant(data_), resources_); + } + size_t size() const { + return data_ ? data_->size(resources_) : 0; + } + ARDUINOJSON_DEPRECATED("use add() instead") + JsonVariant add() const { + return add(); + } + ARDUINOJSON_DEPRECATED("use add() instead") + JsonArray createNestedArray() const { + return add(); + } + ARDUINOJSON_DEPRECATED("use add() instead") + JsonObject createNestedObject() const; + ARDUINOJSON_DEPRECATED("always returns zero") + size_t memoryUsage() const { + return 0; + } + private: + detail::ResourceManager* getResourceManager() const { + return resources_; + } + detail::VariantData* getData() const { + return collectionToVariant(data_); + } + detail::VariantData* getOrCreateData() const { + return collectionToVariant(data_); + } + detail::ArrayData* data_; + detail::ResourceManager* resources_; +}; +class JsonPair { + public: + JsonPair(detail::ObjectData::iterator iterator, + detail::ResourceManager* resources) + : iterator_(iterator), resources_(resources) {} + JsonString key() const { + if (!iterator_.done()) + return JsonString(iterator_.key(), iterator_.ownsKey() + ? JsonString::Copied + : JsonString::Linked); + else + return JsonString(); + } + JsonVariant value() { + return JsonVariant(iterator_.data(), resources_); + } + private: + detail::ObjectData::iterator iterator_; + detail::ResourceManager* resources_; +}; +class JsonPairConst { + public: + JsonPairConst(detail::ObjectData::iterator iterator, + const detail::ResourceManager* resources) + : iterator_(iterator), resources_(resources) {} + JsonString key() const { + if (!iterator_.done()) + return JsonString(iterator_.key(), iterator_.ownsKey() + ? JsonString::Copied + : JsonString::Linked); + else + return JsonString(); + } + JsonVariantConst value() const { + return JsonVariantConst(iterator_.data(), resources_); + } + private: + detail::ObjectData::iterator iterator_; + const detail::ResourceManager* resources_; +}; +class JsonObjectIterator { + friend class JsonObject; + public: + JsonObjectIterator() {} + explicit JsonObjectIterator(detail::ObjectData::iterator iterator, + detail::ResourceManager* resources) + : iterator_(iterator), resources_(resources) {} + JsonPair operator*() const { + return JsonPair(iterator_, resources_); + } + Ptr operator->() { + return operator*(); + } + bool operator==(const JsonObjectIterator& other) const { + return iterator_ == other.iterator_; + } + bool operator!=(const JsonObjectIterator& other) const { + return iterator_ != other.iterator_; + } + JsonObjectIterator& operator++() { + iterator_.next(resources_); + return *this; + } + private: + detail::ObjectData::iterator iterator_; + detail::ResourceManager* resources_; +}; +class JsonObjectConstIterator { + friend class JsonObject; + public: + JsonObjectConstIterator() {} + explicit JsonObjectConstIterator(detail::ObjectData::iterator iterator, + const detail::ResourceManager* resources) + : iterator_(iterator), resources_(resources) {} + JsonPairConst operator*() const { + return JsonPairConst(iterator_, resources_); + } + Ptr operator->() { + return operator*(); + } + bool operator==(const JsonObjectConstIterator& other) const { + return iterator_ == other.iterator_; + } + bool operator!=(const JsonObjectConstIterator& other) const { + return iterator_ != other.iterator_; + } + JsonObjectConstIterator& operator++() { + iterator_.next(resources_); + return *this; + } + private: + detail::ObjectData::iterator iterator_; + const detail::ResourceManager* resources_; +}; +class JsonObjectConst : public detail::VariantOperators { + friend class JsonObject; + friend class detail::VariantAttorney; + public: + typedef JsonObjectConstIterator iterator; + JsonObjectConst() : data_(0) {} + JsonObjectConst(const detail::ObjectData* data, + const detail::ResourceManager* resources) + : data_(data), resources_(resources) {} + operator JsonVariantConst() const { + return JsonVariantConst(getData(), resources_); + } + bool isNull() const { + return data_ == 0; + } + operator bool() const { + return data_ != 0; + } + size_t nesting() const { + return detail::VariantData::nesting(getData(), resources_); + } + size_t size() const { + return data_ ? data_->size(resources_) : 0; + } + iterator begin() const { + if (!data_) + return iterator(); + return iterator(data_->createIterator(resources_), resources_); + } + iterator end() const { + return iterator(); + } + template + bool containsKey(const TString& key) const { + return detail::ObjectData::getMember(data_, detail::adaptString(key), + resources_) != 0; + } + template + bool containsKey(TChar* key) const { + return detail::ObjectData::getMember(data_, detail::adaptString(key), + resources_) != 0; + } + template + typename detail::enable_if::value, + JsonVariantConst>::type + operator[](const TString& key) const { + return JsonVariantConst(detail::ObjectData::getMember( + data_, detail::adaptString(key), resources_), + resources_); + } + template + typename detail::enable_if::value, + JsonVariantConst>::type + operator[](TChar* key) const { + return JsonVariantConst(detail::ObjectData::getMember( + data_, detail::adaptString(key), resources_), + resources_); + } + ARDUINOJSON_DEPRECATED("always returns zero") + size_t memoryUsage() const { + return 0; + } + private: + const detail::VariantData* getData() const { + return collectionToVariant(data_); + } + const detail::ObjectData* data_; + const detail::ResourceManager* resources_; +}; +inline bool operator==(JsonObjectConst lhs, JsonObjectConst rhs) { + if (!lhs && !rhs) // both are null + return true; + if (!lhs || !rhs) // only one is null + return false; + size_t count = 0; + for (auto kvp : lhs) { + auto rhsValue = rhs[kvp.key()]; + if (rhsValue.isUnbound()) + return false; + if (kvp.value() != rhsValue) + return false; + count++; + } + return count == rhs.size(); +} +ARDUINOJSON_END_PUBLIC_NAMESPACE +ARDUINOJSON_BEGIN_PRIVATE_NAMESPACE +template +class MemberProxy + : public VariantRefBase>, + public VariantOperators> { + friend class VariantAttorney; + public: + MemberProxy(TUpstream upstream, TStringRef key) + : upstream_(upstream), key_(key) {} + MemberProxy(const MemberProxy& src) + : upstream_(src.upstream_), key_(src.key_) {} + MemberProxy& operator=(const MemberProxy& src) { + this->set(src); + return *this; + } + template + MemberProxy& operator=(const T& src) { + this->set(src); + return *this; + } + template + MemberProxy& operator=(T* src) { + this->set(src); + return *this; + } + private: + ResourceManager* getResourceManager() const { + return VariantAttorney::getResourceManager(upstream_); + } + VariantData* getData() const { + return VariantData::getMember( + VariantAttorney::getData(upstream_), adaptString(key_), + VariantAttorney::getResourceManager(upstream_)); + } + VariantData* getOrCreateData() const { + auto data = VariantAttorney::getOrCreateData(upstream_); + if (!data) + return nullptr; + return data->getOrAddMember(adaptString(key_), + VariantAttorney::getResourceManager(upstream_)); + } + private: + TUpstream upstream_; + TStringRef key_; +}; +ARDUINOJSON_END_PRIVATE_NAMESPACE +ARDUINOJSON_BEGIN_PUBLIC_NAMESPACE +class JsonArray; +class JsonObject : public detail::VariantOperators { + friend class detail::VariantAttorney; + public: + typedef JsonObjectIterator iterator; + JsonObject() : data_(0), resources_(0) {} + JsonObject(detail::ObjectData* data, detail::ResourceManager* resource) + : data_(data), resources_(resource) {} + operator JsonVariant() const { + void* data = data_; // prevent warning cast-align + return JsonVariant(reinterpret_cast(data), + resources_); + } + operator JsonObjectConst() const { + return JsonObjectConst(data_, resources_); + } + operator JsonVariantConst() const { + return JsonVariantConst(collectionToVariant(data_), resources_); + } + bool isNull() const { + return data_ == 0; + } + operator bool() const { + return data_ != 0; + } + size_t nesting() const { + return detail::VariantData::nesting(collectionToVariant(data_), resources_); + } + size_t size() const { + return data_ ? data_->size(resources_) : 0; + } + iterator begin() const { + if (!data_) + return iterator(); + return iterator(data_->createIterator(resources_), resources_); + } + iterator end() const { + return iterator(); + } + void clear() const { + detail::ObjectData::clear(data_, resources_); + } + bool set(JsonObjectConst src) { + if (!data_ || !src.data_) + return false; + clear(); + for (auto kvp : src) { + if (!operator[](kvp.key()).set(kvp.value())) + return false; + } + return true; + } + template + typename detail::enable_if::value, + detail::MemberProxy>::type + operator[](const TString& key) const { + return {*this, key}; + } + template + typename detail::enable_if::value, + detail::MemberProxy>::type + operator[](TChar* key) const { + return {*this, key}; + } + FORCE_INLINE void remove(iterator it) const { + detail::ObjectData::remove(data_, it.iterator_, resources_); + } + template + FORCE_INLINE void remove(const TString& key) const { + detail::ObjectData::removeMember(data_, detail::adaptString(key), + resources_); + } + template + FORCE_INLINE void remove(TChar* key) const { + detail::ObjectData::removeMember(data_, detail::adaptString(key), + resources_); + } + template + typename detail::enable_if::value, bool>::type + containsKey(const TString& key) const { + return detail::ObjectData::getMember(data_, detail::adaptString(key), + resources_) != 0; + } + template + typename detail::enable_if::value, bool>::type + containsKey(TChar* key) const { + return detail::ObjectData::getMember(data_, detail::adaptString(key), + resources_) != 0; + } + template + ARDUINOJSON_DEPRECATED("use obj[key].to() instead") + JsonArray createNestedArray(TChar* key) const { + return operator[](key).template to(); + } + template + ARDUINOJSON_DEPRECATED("use obj[key].to() instead") + JsonArray createNestedArray(const TString& key) const { + return operator[](key).template to(); + } + template + ARDUINOJSON_DEPRECATED("use obj[key].to() instead") + JsonObject createNestedObject(TChar* key) { + return operator[](key).template to(); + } + template + ARDUINOJSON_DEPRECATED("use obj[key].to() instead") + JsonObject createNestedObject(const TString& key) { + return operator[](key).template to(); + } + ARDUINOJSON_DEPRECATED("always returns zero") + size_t memoryUsage() const { + return 0; + } + private: + detail::ResourceManager* getResourceManager() const { + return resources_; + } + detail::VariantData* getData() const { + return detail::collectionToVariant(data_); + } + detail::VariantData* getOrCreateData() const { + return detail::collectionToVariant(data_); + } + detail::ObjectData* data_; + detail::ResourceManager* resources_; +}; +class JsonDocument : public detail::VariantOperators { + friend class detail::VariantAttorney; + public: + explicit JsonDocument(Allocator* alloc = detail::DefaultAllocator::instance()) + : resources_(alloc) {} + JsonDocument(const JsonDocument& src) : JsonDocument(src.allocator()) { + set(src); + } + JsonDocument(JsonDocument&& src) + : JsonDocument(detail::DefaultAllocator::instance()) { + swap(*this, src); + } + template + JsonDocument(const T& src, + Allocator* alloc = detail::DefaultAllocator::instance(), + typename detail::enable_if< + detail::is_same::value || + detail::is_same::value || + detail::is_same::value || + detail::is_same::value || + detail::is_same::value || + detail::is_same::value>::type* = 0) + : JsonDocument(alloc) { + set(src); + } + JsonDocument& operator=(JsonDocument src) { + swap(*this, src); + return *this; + } + template + JsonDocument& operator=(const T& src) { + set(src); + return *this; + } + Allocator* allocator() const { + return resources_.allocator(); + } + void shrinkToFit() { + resources_.shrinkToFit(); + } + template + T as() { + return getVariant().template as(); + } + template + T as() const { + return getVariant().template as(); + } + void clear() { + resources_.clear(); + data_.reset(); + } + template + bool is() { + return getVariant().template is(); + } + template + bool is() const { + return getVariant().template is(); + } + bool isNull() const { + return getVariant().isNull(); + } + bool overflowed() const { + return resources_.overflowed(); + } + size_t nesting() const { + return data_.nesting(&resources_); + } + size_t size() const { + return data_.size(&resources_); + } + bool set(const JsonDocument& src) { + return to().set(src.as()); + } + template + typename detail::enable_if::value, + bool>::type + set(const T& src) { + return to().set(src); + } + template + typename detail::VariantTo::type to() { + clear(); + return getVariant().template to(); + } + template + bool containsKey(TChar* key) const { + return data_.getMember(detail::adaptString(key), &resources_) != 0; + } + template + bool containsKey(const TString& key) const { + return data_.getMember(detail::adaptString(key), &resources_) != 0; + } + template + typename detail::enable_if::value, + detail::MemberProxy>::type + operator[](const TString& key) { + return {*this, key}; + } + template + typename detail::enable_if::value, + detail::MemberProxy>::type + operator[](TChar* key) { + return {*this, key}; + } + template + typename detail::enable_if::value, + JsonVariantConst>::type + operator[](const TString& key) const { + return JsonVariantConst( + data_.getMember(detail::adaptString(key), &resources_), &resources_); + } + template + typename detail::enable_if::value, + JsonVariantConst>::type + operator[](TChar* key) const { + return JsonVariantConst( + data_.getMember(detail::adaptString(key), &resources_), &resources_); + } + detail::ElementProxy operator[](size_t index) { + return {*this, index}; + } + JsonVariantConst operator[](size_t index) const { + return JsonVariantConst(data_.getElement(index, &resources_), &resources_); + } + template + typename detail::enable_if::value, T>::type + add() { + return add().to(); + } + template + typename detail::enable_if::value, T>::type + add() { + return JsonVariant(data_.addElement(&resources_), &resources_); + } + template + bool add(const TValue& value) { + return add().set(value); + } + template + bool add(TChar* value) { + return add().set(value); + } + void remove(size_t index) { + detail::VariantData::removeElement(getData(), index, getResourceManager()); + } + template + typename detail::enable_if::value>::type remove( + TChar* key) { + detail::VariantData::removeMember(getData(), detail::adaptString(key), + getResourceManager()); + } + template + typename detail::enable_if::value>::type remove( + const TString& key) { + detail::VariantData::removeMember(getData(), detail::adaptString(key), + getResourceManager()); + } + operator JsonVariant() { + return getVariant(); + } + operator JsonVariantConst() const { + return getVariant(); + } + friend void swap(JsonDocument& a, JsonDocument& b) { + swap(a.resources_, b.resources_); + swap_(a.data_, b.data_); + } + ARDUINOJSON_DEPRECATED("use add() instead") + JsonVariant add() { + return add(); + } + ARDUINOJSON_DEPRECATED("use add() instead") + JsonArray createNestedArray() { + return add(); + } + template + ARDUINOJSON_DEPRECATED("use doc[key].to() instead") + JsonArray createNestedArray(TChar* key) { + return operator[](key).template to(); + } + template + ARDUINOJSON_DEPRECATED("use doc[key].to() instead") + JsonArray createNestedArray(const TString& key) { + return operator[](key).template to(); + } + ARDUINOJSON_DEPRECATED("use add() instead") + JsonObject createNestedObject() { + return add(); + } + template + ARDUINOJSON_DEPRECATED("use doc[key].to() instead") + JsonObject createNestedObject(TChar* key) { + return operator[](key).template to(); + } + template + ARDUINOJSON_DEPRECATED("use doc[key].to() instead") + JsonObject createNestedObject(const TString& key) { + return operator[](key).template to(); + } + ARDUINOJSON_DEPRECATED("always returns zero") + size_t memoryUsage() const { + return 0; + } + private: + JsonVariant getVariant() { + return JsonVariant(&data_, &resources_); + } + JsonVariantConst getVariant() const { + return JsonVariantConst(&data_, &resources_); + } + detail::ResourceManager* getResourceManager() { + return &resources_; + } + detail::VariantData* getData() { + return &data_; + } + const detail::VariantData* getData() const { + return &data_; + } + detail::VariantData* getOrCreateData() { + return &data_; + } + detail::ResourceManager resources_; + detail::VariantData data_; +}; +inline void convertToJson(const JsonDocument& src, JsonVariant dst) { + dst.set(src.as()); +} +ARDUINOJSON_END_PUBLIC_NAMESPACE +ARDUINOJSON_BEGIN_PRIVATE_NAMESPACE +template +struct VariantDataVisitor { + typedef TResult result_type; + template + TResult visit(const T&) { + return TResult(); + } +}; +template +struct JsonVariantVisitor { + typedef TResult result_type; + template + TResult visit(const T&) { + return TResult(); + } +}; +template +class VisitorAdapter { + public: + using result_type = typename TVisitor::result_type; + VisitorAdapter(TVisitor& visitor, const ResourceManager* resources) + : visitor_(&visitor), resources_(resources) {} + result_type visit(const ArrayData& value) { + return visitor_->visit(JsonArrayConst(&value, resources_)); + } + result_type visit(const ObjectData& value) { + return visitor_->visit(JsonObjectConst(&value, resources_)); + } + template + result_type visit(const T& value) { + return visitor_->visit(value); + } + private: + TVisitor* visitor_; + const ResourceManager* resources_; +}; +template +typename TVisitor::result_type accept(JsonVariantConst variant, + TVisitor& visit) { + auto data = VariantAttorney::getData(variant); + if (!data) + return visit.visit(nullptr); + auto resources = VariantAttorney::getResourceManager(variant); + VisitorAdapter adapter(visit, resources); + return data->accept(adapter); +} +struct ComparerBase : JsonVariantVisitor {}; +template +struct Comparer; +template +struct Comparer::value>::type> + : ComparerBase { + T rhs; // TODO: store adapted string? + explicit Comparer(T value) : rhs(value) {} + CompareResult visit(JsonString lhs) { + int i = stringCompare(adaptString(rhs), adaptString(lhs)); + if (i < 0) + return COMPARE_RESULT_GREATER; + else if (i > 0) + return COMPARE_RESULT_LESS; + else + return COMPARE_RESULT_EQUAL; + } + CompareResult visit(nullptr_t) { + if (adaptString(rhs).isNull()) + return COMPARE_RESULT_EQUAL; + else + return COMPARE_RESULT_DIFFER; + } + using ComparerBase::visit; +}; +template +struct Comparer::value || + is_floating_point::value>::type> + : ComparerBase { + T rhs; + explicit Comparer(T value) : rhs(value) {} + CompareResult visit(JsonFloat lhs) { + return arithmeticCompare(lhs, rhs); + } + CompareResult visit(JsonInteger lhs) { + return arithmeticCompare(lhs, rhs); + } + CompareResult visit(JsonUInt lhs) { + return arithmeticCompare(lhs, rhs); + } + CompareResult visit(bool lhs) { + return visit(static_cast(lhs)); + } + using ComparerBase::visit; +}; +struct NullComparer : ComparerBase { + CompareResult visit(nullptr_t) { + return COMPARE_RESULT_EQUAL; + } + using ComparerBase::visit; +}; +template <> +struct Comparer : NullComparer { + explicit Comparer(nullptr_t) : NullComparer() {} +}; +struct ArrayComparer : ComparerBase { + JsonArrayConst rhs_; + explicit ArrayComparer(JsonArrayConst rhs) : rhs_(rhs) {} + CompareResult visit(JsonArrayConst lhs) { + if (rhs_ == lhs) + return COMPARE_RESULT_EQUAL; + else + return COMPARE_RESULT_DIFFER; + } + using ComparerBase::visit; +}; +struct ObjectComparer : ComparerBase { + JsonObjectConst rhs_; + explicit ObjectComparer(JsonObjectConst rhs) : rhs_(rhs) {} + CompareResult visit(JsonObjectConst lhs) { + if (lhs == rhs_) + return COMPARE_RESULT_EQUAL; + else + return COMPARE_RESULT_DIFFER; + } + using ComparerBase::visit; +}; +struct RawComparer : ComparerBase { + RawString rhs_; + explicit RawComparer(RawString rhs) : rhs_(rhs) {} + CompareResult visit(RawString lhs) { + size_t size = rhs_.size() < lhs.size() ? rhs_.size() : lhs.size(); + int n = memcmp(lhs.data(), rhs_.data(), size); + if (n < 0) + return COMPARE_RESULT_LESS; + else if (n > 0) + return COMPARE_RESULT_GREATER; + else + return COMPARE_RESULT_EQUAL; + } + using ComparerBase::visit; +}; +struct VariantComparer : ComparerBase { + JsonVariantConst rhs; + explicit VariantComparer(JsonVariantConst value) : rhs(value) {} + CompareResult visit(JsonArrayConst lhs) { + ArrayComparer comparer(lhs); + return reverseResult(comparer); + } + CompareResult visit(JsonObjectConst lhs) { + ObjectComparer comparer(lhs); + return reverseResult(comparer); + } + CompareResult visit(JsonFloat lhs) { + Comparer comparer(lhs); + return reverseResult(comparer); + } + CompareResult visit(JsonString lhs) { + Comparer comparer(lhs); + return reverseResult(comparer); + } + CompareResult visit(RawString value) { + RawComparer comparer(value); + return reverseResult(comparer); + } + CompareResult visit(JsonInteger lhs) { + Comparer comparer(lhs); + return reverseResult(comparer); + } + CompareResult visit(JsonUInt lhs) { + Comparer comparer(lhs); + return reverseResult(comparer); + } + CompareResult visit(bool lhs) { + Comparer comparer(lhs); + return reverseResult(comparer); + } + CompareResult visit(nullptr_t) { + NullComparer comparer; + return reverseResult(comparer); + } + private: + template + CompareResult reverseResult(TComparer& comparer) { + CompareResult reversedResult = accept(rhs, comparer); + switch (reversedResult) { + case COMPARE_RESULT_GREATER: + return COMPARE_RESULT_LESS; + case COMPARE_RESULT_LESS: + return COMPARE_RESULT_GREATER; + default: + return reversedResult; + } + } +}; +template +struct Comparer::value>::type> + : VariantComparer { + explicit Comparer(const T& value) + : VariantComparer(static_cast(value)) {} +}; +template +CompareResult compare(ArduinoJson::JsonVariantConst lhs, const T& rhs) { + Comparer comparer(rhs); + return accept(lhs, comparer); +} +inline ArrayData::iterator ArrayData::at( + size_t index, const ResourceManager* resources) const { + auto it = createIterator(resources); + while (!it.done() && index) { + it.next(resources); + --index; + } + return it; +} +inline VariantData* ArrayData::getOrAddElement(size_t index, + ResourceManager* resources) { + auto it = createIterator(resources); + while (!it.done() && index > 0) { + it.next(resources); + index--; + } + if (it.done()) + index++; + VariantData* element = it.data(); + while (index > 0) { + element = addElement(resources); + if (!element) + return nullptr; + index--; + } + return element; +} +inline VariantData* ArrayData::getElement( + size_t index, const ResourceManager* resources) const { + return at(index, resources).data(); +} +inline void ArrayData::removeElement(size_t index, ResourceManager* resources) { + remove(at(index, resources), resources); +} +ARDUINOJSON_END_PRIVATE_NAMESPACE +ARDUINOJSON_BEGIN_PUBLIC_NAMESPACE +template +inline typename detail::enable_if::value, bool>::type +copyArray(const T& src, JsonVariant dst) { + return dst.set(src); +} +template +inline typename detail::enable_if< + !detail::is_base_of::value, bool>::type +copyArray(T (&src)[N], const TDestination& dst) { + return copyArray(src, N, dst); +} +template +inline typename detail::enable_if< + !detail::is_base_of::value, bool>::type +copyArray(const T* src, size_t len, const TDestination& dst) { + bool ok = true; + for (size_t i = 0; i < len; i++) { + ok &= copyArray(src[i], dst.template add()); + } + return ok; +} +template +inline bool copyArray(const char* src, size_t, const TDestination& dst) { + return dst.set(src); +} +template +inline bool copyArray(const T& src, JsonDocument& dst) { + return copyArray(src, dst.to()); +} +template +inline bool copyArray(const T* src, size_t len, JsonDocument& dst) { + return copyArray(src, len, dst.to()); +} +template +inline typename detail::enable_if::value, size_t>::type +copyArray(JsonVariantConst src, T& dst) { + dst = src.as(); + return 1; +} +template +inline size_t copyArray(JsonArrayConst src, T (&dst)[N]) { + return copyArray(src, dst, N); +} +template +inline size_t copyArray(JsonArrayConst src, T* dst, size_t len) { + size_t i = 0; + for (JsonArrayConst::iterator it = src.begin(); it != src.end() && i < len; + ++it) + copyArray(*it, dst[i++]); + return i; +} +template +inline size_t copyArray(JsonVariantConst src, char (&dst)[N]) { + JsonString s = src; + size_t len = N - 1; + if (len > s.size()) + len = s.size(); + memcpy(dst, s.c_str(), len); + dst[len] = 0; + return 1; +} +template +inline typename detail::enable_if< + detail::is_array::value && + detail::is_base_of::value, + size_t>::type +copyArray(const TSource& src, T& dst) { + return copyArray(src.template as(), dst); +} +ARDUINOJSON_END_PUBLIC_NAMESPACE +ARDUINOJSON_BEGIN_PRIVATE_NAMESPACE +#if ARDUINOJSON_ENABLE_ALIGNMENT +inline bool isAligned(size_t value) { + const size_t mask = sizeof(void*) - 1; + size_t addr = value; + return (addr & mask) == 0; +} +inline size_t addPadding(size_t bytes) { + const size_t mask = sizeof(void*) - 1; + return (bytes + mask) & ~mask; +} +template +struct AddPadding { + static const size_t mask = sizeof(void*) - 1; + static const size_t value = (bytes + mask) & ~mask; +}; +#else +inline bool isAligned(size_t) { + return true; +} +inline size_t addPadding(size_t bytes) { + return bytes; +} +template +struct AddPadding { + static const size_t value = bytes; +}; +#endif +template +inline bool isAligned(T* ptr) { + return isAligned(reinterpret_cast(ptr)); +} +template +inline T* addPadding(T* p) { + size_t address = addPadding(reinterpret_cast(p)); + return reinterpret_cast(address); +} +inline CollectionIterator::CollectionIterator(VariantSlot* slot, SlotId slotId) + : slot_(slot), currentId_(slotId) { + nextId_ = slot_ ? slot_->next() : NULL_SLOT; +} +inline const char* CollectionIterator::key() const { + ARDUINOJSON_ASSERT(slot_ != nullptr); + return slot_->key(); +} +inline void CollectionIterator::setKey(const char* s) { + ARDUINOJSON_ASSERT(slot_ != nullptr); + ARDUINOJSON_ASSERT(s != nullptr); + return slot_->setKey(s); +} +inline void CollectionIterator::setKey(StringNode* s) { + ARDUINOJSON_ASSERT(slot_ != nullptr); + ARDUINOJSON_ASSERT(s != nullptr); + return slot_->setKey(s); +} +inline bool CollectionIterator::ownsKey() const { + ARDUINOJSON_ASSERT(slot_ != nullptr); + return slot_->ownsKey(); +} +inline void CollectionIterator::next(const ResourceManager* resources) { + ARDUINOJSON_ASSERT(currentId_ != NULL_SLOT); + slot_ = resources->getSlot(nextId_); + currentId_ = nextId_; + if (slot_) + nextId_ = slot_->next(); +} +inline CollectionData::iterator CollectionData::addSlot( + ResourceManager* resources) { + auto slot = resources->allocSlot(); + if (!slot) + return {}; + if (tail_ != NULL_SLOT) { + auto tail = resources->getSlot(tail_); + tail->setNext(slot.id()); + tail_ = slot.id(); + } else { + head_ = slot.id(); + tail_ = slot.id(); + } + return iterator(slot, slot.id()); +} +inline void CollectionData::clear(ResourceManager* resources) { + auto next = head_; + while (next != NULL_SLOT) { + auto currId = next; + auto slot = resources->getSlot(next); + next = slot->next(); + releaseSlot(SlotWithId(slot, currId), resources); + } + head_ = NULL_SLOT; + tail_ = NULL_SLOT; +} +inline SlotWithId CollectionData::getPreviousSlot( + VariantSlot* target, const ResourceManager* resources) const { + auto prev = SlotWithId(); + auto currentId = head_; + while (currentId != NULL_SLOT) { + auto currentSlot = resources->getSlot(currentId); + if (currentSlot == target) + return prev; + prev = SlotWithId(currentSlot, currentId); + currentId = currentSlot->next(); + } + return SlotWithId(); +} +inline void CollectionData::remove(iterator it, ResourceManager* resources) { + if (it.done()) + return; + auto curr = it.slot_; + auto prev = getPreviousSlot(curr, resources); + auto next = curr->next(); + if (prev) + prev->setNext(next); + else + head_ = next; + if (next == NULL_SLOT) + tail_ = prev.id(); + releaseSlot({it.slot_, it.currentId_}, resources); +} +inline size_t CollectionData::nesting(const ResourceManager* resources) const { + size_t maxChildNesting = 0; + for (auto it = createIterator(resources); !it.done(); it.next(resources)) { + size_t childNesting = it->nesting(resources); + if (childNesting > maxChildNesting) + maxChildNesting = childNesting; + } + return maxChildNesting + 1; +} +inline size_t CollectionData::size(const ResourceManager* resources) const { + size_t count = 0; + for (auto it = createIterator(resources); !it.done(); it.next(resources)) + count++; + return count; +} +inline void CollectionData::releaseSlot(SlotWithId slot, + ResourceManager* resources) { + if (slot->ownsKey()) + resources->dereferenceString(slot->key()); + slot->data()->setNull(resources); + resources->freeSlot(slot); +} +inline void VariantPool::create(SlotCount cap, Allocator* allocator) { + ARDUINOJSON_ASSERT(cap > 0); + slots_ = + reinterpret_cast(allocator->allocate(slotsToBytes(cap))); + capacity_ = slots_ ? cap : 0; + usage_ = 0; +} +inline void VariantPool::destroy(Allocator* allocator) { + if (slots_) + allocator->deallocate(slots_); + slots_ = nullptr; + capacity_ = 0; + usage_ = 0; +} +inline void VariantPool::shrinkToFit(Allocator* allocator) { + auto newSlots = reinterpret_cast( + allocator->reallocate(slots_, slotsToBytes(usage_))); + if (newSlots) { + slots_ = newSlots; + capacity_ = usage_; + } +} +inline SlotWithId VariantPool::allocSlot() { + if (!slots_) + return {}; + if (usage_ >= capacity_) + return {}; + auto index = usage_++; + auto slot = &slots_[index]; + return {new (slot) VariantSlot, SlotId(index)}; +} +inline VariantSlot* VariantPool::getSlot(SlotId id) const { + ARDUINOJSON_ASSERT(id < usage_); + return &slots_[id]; +} +inline SlotCount VariantPool::usage() const { + return usage_; +} +inline void VariantPool::clear() { + usage_ = 0; +} +inline SlotCount VariantPool::bytesToSlots(size_t n) { + return static_cast(n / sizeof(VariantSlot)); +} +inline size_t VariantPool::slotsToBytes(SlotCount n) { + return n * sizeof(VariantSlot); +} +inline SlotWithId VariantPoolList::allocFromFreeList() { + ARDUINOJSON_ASSERT(freeList_ != NULL_SLOT); + auto id = freeList_; + auto slot = getSlot(freeList_); + freeList_ = slot->next(); + return {new (slot) VariantSlot, id}; +} +inline void VariantPoolList::freeSlot(SlotWithId slot) { + slot->setNext(freeList_); + freeList_ = slot.id(); +} +template +inline VariantData* ObjectData::getMember( + TAdaptedString key, const ResourceManager* resources) const { + return findKey(key, resources).data(); +} +template +VariantData* ObjectData::getOrAddMember(TAdaptedString key, + ResourceManager* resources) { + auto it = findKey(key, resources); + if (!it.done()) + return it.data(); + return addMember(key, resources); +} +template +inline ObjectData::iterator ObjectData::findKey( + TAdaptedString key, const ResourceManager* resources) const { + if (key.isNull()) + return iterator(); + for (auto it = createIterator(resources); !it.done(); it.next(resources)) { + if (stringEquals(key, adaptString(it.key()))) + return it; + } + return iterator(); +} +template +inline void ObjectData::removeMember(TAdaptedString key, + ResourceManager* resources) { + remove(findKey(key, resources), resources); +} +class EscapeSequence { + public: + static char escapeChar(char c) { + const char* p = escapeTable(true); + while (p[0] && p[1] != c) { + p += 2; + } + return p[0]; + } + static char unescapeChar(char c) { + const char* p = escapeTable(false); + for (;;) { + if (p[0] == '\0') + return 0; + if (p[0] == c) + return p[1]; + p += 2; + } + } + private: + static const char* escapeTable(bool excludeSolidus) { + return &"//\"\"\\\\b\bf\fn\nr\rt\t"[excludeSolidus ? 2 : 0]; + } +}; +template +struct FloatParts { + uint32_t integral; + uint32_t decimal; + int16_t exponent; + int8_t decimalPlaces; + FloatParts(TFloat value) { + uint32_t maxDecimalPart = sizeof(TFloat) >= 8 ? 1000000000 : 1000000; + decimalPlaces = sizeof(TFloat) >= 8 ? 9 : 6; + exponent = normalize(value); + integral = uint32_t(value); + for (uint32_t tmp = integral; tmp >= 10; tmp /= 10) { + maxDecimalPart /= 10; + decimalPlaces--; + } + TFloat remainder = (value - TFloat(integral)) * TFloat(maxDecimalPart); + decimal = uint32_t(remainder); + remainder = remainder - TFloat(decimal); + decimal += uint32_t(remainder * 2); + if (decimal >= maxDecimalPart) { + decimal = 0; + integral++; + if (exponent && integral >= 10) { + exponent++; + integral = 1; + } + } + while (decimal % 10 == 0 && decimalPlaces > 0) { + decimal /= 10; + decimalPlaces--; + } + } + static int16_t normalize(TFloat& value) { + typedef FloatTraits traits; + int16_t powersOf10 = 0; + int8_t index = sizeof(TFloat) == 8 ? 8 : 5; + int bit = 1 << index; + if (value >= ARDUINOJSON_POSITIVE_EXPONENTIATION_THRESHOLD) { + for (; index >= 0; index--) { + if (value >= traits::positiveBinaryPowersOfTen()[index]) { + value *= traits::negativeBinaryPowersOfTen()[index]; + powersOf10 = int16_t(powersOf10 + bit); + } + bit >>= 1; + } + } + if (value > 0 && value <= ARDUINOJSON_NEGATIVE_EXPONENTIATION_THRESHOLD) { + for (; index >= 0; index--) { + if (value < traits::negativeBinaryPowersOfTen()[index] * 10) { + value *= traits::positiveBinaryPowersOfTen()[index]; + powersOf10 = int16_t(powersOf10 - bit); + } + bit >>= 1; + } + } + return powersOf10; + } +}; +template +class CountingDecorator { + public: + explicit CountingDecorator(TWriter& writer) : writer_(writer), count_(0) {} + void write(uint8_t c) { + count_ += writer_.write(c); + } + void write(const uint8_t* s, size_t n) { + count_ += writer_.write(s, n); + } + size_t count() const { + return count_; + } + private: + TWriter writer_; + size_t count_; +}; +template +class TextFormatter { + public: + explicit TextFormatter(TWriter writer) : writer_(writer) {} + TextFormatter& operator=(const TextFormatter&) = delete; + size_t bytesWritten() const { + return writer_.count(); + } + void writeBoolean(bool value) { + if (value) + writeRaw("true"); + else + writeRaw("false"); + } + void writeString(const char* value) { + ARDUINOJSON_ASSERT(value != NULL); + writeRaw('\"'); + while (*value) + writeChar(*value++); + writeRaw('\"'); + } + void writeString(const char* value, size_t n) { + ARDUINOJSON_ASSERT(value != NULL); + writeRaw('\"'); + while (n--) + writeChar(*value++); + writeRaw('\"'); + } + void writeChar(char c) { + char specialChar = EscapeSequence::escapeChar(c); + if (specialChar) { + writeRaw('\\'); + writeRaw(specialChar); + } else if (c) { + writeRaw(c); + } else { + writeRaw("\\u0000"); + } + } + template + void writeFloat(T value) { + if (isnan(value)) + return writeRaw(ARDUINOJSON_ENABLE_NAN ? "NaN" : "null"); +#if ARDUINOJSON_ENABLE_INFINITY + if (value < 0.0) { + writeRaw('-'); + value = -value; + } + if (isinf(value)) + return writeRaw("Infinity"); +#else + if (isinf(value)) + return writeRaw("null"); + if (value < 0.0) { + writeRaw('-'); + value = -value; + } +#endif + FloatParts parts(value); + writeInteger(parts.integral); + if (parts.decimalPlaces) + writeDecimals(parts.decimal, parts.decimalPlaces); + if (parts.exponent) { + writeRaw('e'); + writeInteger(parts.exponent); + } + } + template + typename enable_if::value>::type writeInteger(T value) { + typedef typename make_unsigned::type unsigned_type; + unsigned_type unsigned_value; + if (value < 0) { + writeRaw('-'); + unsigned_value = unsigned_type(unsigned_type(~value) + 1); + } else { + unsigned_value = unsigned_type(value); + } + writeInteger(unsigned_value); + } + template + typename enable_if::value>::type writeInteger(T value) { + char buffer[22]; + char* end = buffer + sizeof(buffer); + char* begin = end; + do { + *--begin = char(value % 10 + '0'); + value = T(value / 10); + } while (value); + writeRaw(begin, end); + } + void writeDecimals(uint32_t value, int8_t width) { + char buffer[16]; + char* end = buffer + sizeof(buffer); + char* begin = end; + while (width--) { + *--begin = char(value % 10 + '0'); + value /= 10; + } + *--begin = '.'; + writeRaw(begin, end); + } + void writeRaw(const char* s) { + writer_.write(reinterpret_cast(s), strlen(s)); + } + void writeRaw(const char* s, size_t n) { + writer_.write(reinterpret_cast(s), n); + } + void writeRaw(const char* begin, const char* end) { + writer_.write(reinterpret_cast(begin), + static_cast(end - begin)); + } + template + void writeRaw(const char (&s)[N]) { + writer_.write(reinterpret_cast(s), N - 1); + } + void writeRaw(char c) { + writer_.write(static_cast(c)); + } + protected: + CountingDecorator writer_; +}; +class DummyWriter { + public: + size_t write(uint8_t) { + return 1; + } + size_t write(const uint8_t*, size_t n) { + return n; + } +}; +template