From 4aa8f57d3609c12c10c3877c83b06d0c52975e7a Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Thu, 19 Feb 2026 14:08:44 -0600 Subject: [PATCH] [json] Add SerializationBuffer for stack-first JSON serialization (#13625) --- esphome/components/api/user_services.h | 4 +- esphome/components/json/json_util.cpp | 81 +++++++- esphome/components/json/json_util.h | 111 +++++++++- esphome/components/mqtt/mqtt_client.cpp | 4 +- esphome/components/web_server/web_server.cpp | 191 +++++++++--------- esphome/components/web_server/web_server.h | 138 +++++++------ .../web_server_idf/web_server_idf.cpp | 6 +- .../web_server_idf/web_server_idf.h | 3 +- tests/components/json/common.yaml | 13 +- 9 files changed, 367 insertions(+), 184 deletions(-) diff --git a/esphome/components/api/user_services.h b/esphome/components/api/user_services.h index 85fba2a435..0fc529108c 100644 --- a/esphome/components/api/user_services.h +++ b/esphome/components/api/user_services.h @@ -264,9 +264,9 @@ template class APIRespondAction : public Action { // Build and send JSON response json::JsonBuilder builder; this->json_builder_(x..., builder.root()); - std::string json_str = builder.serialize(); + auto json_buf = builder.serialize(); this->parent_->send_action_response(call_id, success, StringRef(error_message), - reinterpret_cast(json_str.data()), json_str.size()); + reinterpret_cast(json_buf.data()), json_buf.size()); return; } #endif diff --git a/esphome/components/json/json_util.cpp b/esphome/components/json/json_util.cpp index 69f8bfc61a..6c60a04d20 100644 --- a/esphome/components/json/json_util.cpp +++ b/esphome/components/json/json_util.cpp @@ -15,7 +15,7 @@ static const char *const TAG = "json"; static SpiRamAllocator global_json_allocator; #endif -std::string build_json(const json_build_t &f) { +SerializationBuffer<> build_json(const json_build_t &f) { // NOLINTBEGIN(clang-analyzer-cplusplus.NewDeleteLeaks) false positive with ArduinoJson JsonBuilder builder; JsonObject root = builder.root(); @@ -66,14 +66,83 @@ JsonDocument parse_json(const uint8_t *data, size_t len) { // NOLINTEND(clang-analyzer-cplusplus.NewDeleteLeaks) } -std::string JsonBuilder::serialize() { +SerializationBuffer<> JsonBuilder::serialize() { + // =========================================================================================== + // CRITICAL: NRVO (Named Return Value Optimization) - DO NOT REFACTOR WITHOUT UNDERSTANDING + // =========================================================================================== + // + // This function is carefully structured to enable NRVO. The compiler constructs `result` + // directly in the caller's stack frame, eliminating the move constructor call entirely. + // + // WITHOUT NRVO: Each return would trigger SerializationBuffer's move constructor, which + // must memcpy up to 512 bytes of stack buffer content. This happens on EVERY JSON + // serialization (sensor updates, web server responses, MQTT publishes, etc.). + // + // WITH NRVO: Zero memcpy, zero move constructor overhead. The buffer lives directly + // where the caller needs it. + // + // Requirements for NRVO to work: + // 1. Single named variable (`result`) returned from ALL paths + // 2. All paths must return the SAME variable (not different variables) + // 3. No std::move() on the return statement + // + // If you must modify this function: + // - Keep a single `result` variable declared at the top + // - All code paths must return `result` (not a different variable) + // - Verify NRVO still works by checking the disassembly for move constructor calls + // - Test: objdump -d -C firmware.elf | grep "SerializationBuffer.*SerializationBuffer" + // Should show only destructor, NOT move constructor + // + // Try stack buffer first. 640 bytes covers 99.9% of JSON payloads (sensors ~200B, + // lights ~170B, climate ~500-700B). Only entities with 40+ options exceed this. + // + // IMPORTANT: ArduinoJson's serializeJson() with a bounded buffer returns the actual + // bytes written (truncated count), NOT the would-be size like snprintf(). When the + // payload exceeds the buffer, the return value equals the buffer capacity. The heap + // fallback doubles the buffer size until the payload fits. This avoids instantiating + // measureJson()'s DummyWriter templates (~736 bytes flash) at the cost of temporarily + // over-allocating heap (at most 2x) for the rare payloads that exceed 640 bytes. + // + // =========================================================================================== + constexpr size_t buf_size = SerializationBuffer<>::BUFFER_SIZE; + SerializationBuffer<> result(buf_size - 1); // Max content size (reserve 1 for null) + if (doc_.overflowed()) { ESP_LOGE(TAG, "JSON document overflow"); - return "{}"; + auto *buf = result.data_writable_(); + buf[0] = '{'; + buf[1] = '}'; + buf[2] = '\0'; + result.set_size_(2); + return result; } - std::string output; - serializeJson(doc_, output); - return output; + + size_t size = serializeJson(doc_, result.data_writable_(), buf_size); + if (size < buf_size) { + // Fits in stack buffer - update size to actual length + result.set_size_(size); + return result; + } + + // Payload exceeded stack buffer. Double the buffer and retry until it fits. + // In practice, one iteration (1024 bytes) covers all known entity types. + // Payloads exceeding 1024 bytes are not known to exist in real configurations. + // Cap at 5120 as a safety limit to prevent runaway allocation. + constexpr size_t max_heap_size = 5120; + size_t heap_size = buf_size * 2; + while (heap_size <= max_heap_size) { + result.reallocate_heap_(heap_size - 1); + size = serializeJson(doc_, result.data_writable_(), heap_size); + if (size < heap_size) { + result.set_size_(size); + return result; + } + heap_size *= 2; + } + // Payload exceeds 5120 bytes - return truncated result + ESP_LOGW(TAG, "JSON payload too large, truncated to %zu bytes", size); + result.set_size_(size); + return result; } } // namespace json diff --git a/esphome/components/json/json_util.h b/esphome/components/json/json_util.h index c472b9a9ec..0dc9ff883c 100644 --- a/esphome/components/json/json_util.h +++ b/esphome/components/json/json_util.h @@ -1,5 +1,7 @@ #pragma once +#include +#include #include #include "esphome/core/defines.h" @@ -14,6 +16,108 @@ namespace esphome { namespace json { +/// Buffer for JSON serialization that uses stack allocation for small payloads. +/// Template parameter STACK_SIZE specifies the stack buffer size (default 512 bytes). +/// Supports move semantics for efficient return-by-value. +template class SerializationBuffer { + public: + static constexpr size_t BUFFER_SIZE = STACK_SIZE; ///< Stack buffer size for this instantiation + + /// Construct with known size (typically from measureJson) + explicit SerializationBuffer(size_t size) : size_(size) { + if (size + 1 <= STACK_SIZE) { + buffer_ = stack_buffer_; + } else { + heap_buffer_ = new char[size + 1]; + buffer_ = heap_buffer_; + } + buffer_[0] = '\0'; + } + + ~SerializationBuffer() { delete[] heap_buffer_; } + + // Move constructor - works with same template instantiation + SerializationBuffer(SerializationBuffer &&other) noexcept : heap_buffer_(other.heap_buffer_), size_(other.size_) { + if (other.buffer_ == other.stack_buffer_) { + // Stack buffer - must copy content + std::memcpy(stack_buffer_, other.stack_buffer_, size_ + 1); + buffer_ = stack_buffer_; + } else { + // Heap buffer - steal ownership + buffer_ = heap_buffer_; + other.heap_buffer_ = nullptr; + } + // Leave moved-from object in valid empty state + other.stack_buffer_[0] = '\0'; + other.buffer_ = other.stack_buffer_; + other.size_ = 0; + } + + // Move assignment + SerializationBuffer &operator=(SerializationBuffer &&other) noexcept { + if (this != &other) { + delete[] heap_buffer_; + heap_buffer_ = other.heap_buffer_; + size_ = other.size_; + if (other.buffer_ == other.stack_buffer_) { + std::memcpy(stack_buffer_, other.stack_buffer_, size_ + 1); + buffer_ = stack_buffer_; + } else { + buffer_ = heap_buffer_; + other.heap_buffer_ = nullptr; + } + // Leave moved-from object in valid empty state + other.stack_buffer_[0] = '\0'; + other.buffer_ = other.stack_buffer_; + other.size_ = 0; + } + return *this; + } + + // Delete copy operations + SerializationBuffer(const SerializationBuffer &) = delete; + SerializationBuffer &operator=(const SerializationBuffer &) = delete; + + /// Get null-terminated C string + const char *c_str() const { return buffer_; } + /// Get data pointer + const char *data() const { return buffer_; } + /// Get string length (excluding null terminator) + size_t size() const { return size_; } + + /// Implicit conversion to std::string for backward compatibility + /// WARNING: This allocates a new std::string on the heap. Prefer using + /// c_str() or data()/size() directly when possible to avoid allocation. + operator std::string() const { return std::string(buffer_, size_); } // NOLINT(google-explicit-constructor) + + private: + friend class JsonBuilder; ///< Allows JsonBuilder::serialize() to call private methods + + /// Get writable buffer (for serialization) + char *data_writable_() { return buffer_; } + /// Set actual size after serialization (must not exceed allocated size) + /// Also ensures null termination for c_str() safety + void set_size_(size_t size) { + size_ = size; + buffer_[size] = '\0'; + } + + /// Reallocate to heap buffer with new size (for when stack buffer is too small) + /// This invalidates any previous buffer content. Used by JsonBuilder::serialize(). + void reallocate_heap_(size_t size) { + delete[] heap_buffer_; + heap_buffer_ = new char[size + 1]; + buffer_ = heap_buffer_; + size_ = size; + buffer_[0] = '\0'; + } + + char stack_buffer_[STACK_SIZE]; + char *heap_buffer_{nullptr}; + char *buffer_; + size_t size_; +}; + #ifdef USE_PSRAM // Build an allocator for the JSON Library using the RAMAllocator class // This is only compiled when PSRAM is enabled @@ -47,7 +151,8 @@ using json_parse_t = std::function; using json_build_t = std::function; /// Build a JSON string with the provided json build function. -std::string build_json(const json_build_t &f); +/// Returns SerializationBuffer for stack-first allocation; implicitly converts to std::string. +SerializationBuffer<> build_json(const json_build_t &f); /// Parse a JSON string and run the provided json parse function if it's valid. bool parse_json(const std::string &data, const json_parse_t &f); @@ -72,7 +177,9 @@ class JsonBuilder { return root_; } - std::string serialize(); + /// Serialize the JSON document to a SerializationBuffer (stack-first allocation) + /// Uses 512-byte stack buffer by default, falls back to heap for larger JSON + SerializationBuffer<> serialize(); private: #ifdef USE_PSRAM diff --git a/esphome/components/mqtt/mqtt_client.cpp b/esphome/components/mqtt/mqtt_client.cpp index 9905b4677e..c433804dd9 100644 --- a/esphome/components/mqtt/mqtt_client.cpp +++ b/esphome/components/mqtt/mqtt_client.cpp @@ -543,8 +543,8 @@ bool MQTTClientComponent::publish(const char *topic, const char *payload, size_t } bool MQTTClientComponent::publish_json(const char *topic, const json::json_build_t &f, uint8_t qos, bool retain) { - std::string message = json::build_json(f); - return this->publish(topic, message.c_str(), message.length(), qos, retain); + auto message = json::build_json(f); + return this->publish(topic, message.c_str(), message.size(), qos, retain); } void MQTTClientComponent::enable() { diff --git a/esphome/components/web_server/web_server.cpp b/esphome/components/web_server/web_server.cpp index f352e5896c..4b572417c1 100644 --- a/esphome/components/web_server/web_server.cpp +++ b/esphome/components/web_server/web_server.cpp @@ -214,7 +214,7 @@ DeferredUpdateEventSource::deq_push_back_with_dedup_(void *source, message_gener void DeferredUpdateEventSource::process_deferred_queue_() { while (!deferred_queue_.empty()) { DeferredEvent &de = deferred_queue_.front(); - std::string message = de.message_generator_(web_server_, de.source_); + auto message = de.message_generator_(web_server_, de.source_); if (this->send(message.c_str(), "state") != DISCARDED) { // O(n) but memory efficiency is more important than speed here which is why std::vector was chosen deferred_queue_.erase(deferred_queue_.begin()); @@ -271,7 +271,7 @@ void DeferredUpdateEventSource::deferrable_send_state(void *source, const char * // deferred queue still not empty which means downstream event queue full, no point trying to send first deq_push_back_with_dedup_(source, message_generator); } else { - std::string message = message_generator(web_server_, source); + auto message = message_generator(web_server_, source); if (this->send(message.c_str(), "state") == DISCARDED) { deq_push_back_with_dedup_(source, message_generator); } else { @@ -325,7 +325,7 @@ void DeferredUpdateEventSourceList::on_client_connect_(DeferredUpdateEventSource ws->defer([ws, source]() { // Configure reconnect timeout and send config // this should always go through since the AsyncEventSourceClient event queue is empty on connect - std::string message = ws->get_config_json(); + auto message = ws->get_config_json(); source->try_send_nodefer(message.c_str(), "ping", millis(), 30000); #ifdef USE_WEBSERVER_SORTING @@ -334,10 +334,10 @@ void DeferredUpdateEventSourceList::on_client_connect_(DeferredUpdateEventSource JsonObject root = builder.root(); root[ESPHOME_F("name")] = group.second.name; root[ESPHOME_F("sorting_weight")] = group.second.weight; - message = builder.serialize(); + auto group_msg = builder.serialize(); // up to 31 groups should be able to be queued initially without defer - source->try_send_nodefer(message.c_str(), "sorting_group"); + source->try_send_nodefer(group_msg.c_str(), "sorting_group"); } #endif @@ -370,7 +370,7 @@ void WebServer::set_css_include(const char *css_include) { this->css_include_ = void WebServer::set_js_include(const char *js_include) { this->js_include_ = js_include; } #endif -std::string WebServer::get_config_json() { +json::SerializationBuffer<> WebServer::get_config_json() { json::JsonBuilder builder; JsonObject root = builder.root(); @@ -606,20 +606,20 @@ void WebServer::handle_sensor_request(AsyncWebServerRequest *request, const UrlM // Note: request->method() is always HTTP_GET here (canHandle ensures this) if (entity_match.action_is_empty) { auto detail = get_request_detail(request); - std::string data = this->sensor_json_(obj, obj->state, detail); + auto data = this->sensor_json_(obj, obj->state, detail); request->send(200, "application/json", data.c_str()); return; } } request->send(404); } -std::string WebServer::sensor_state_json_generator(WebServer *web_server, void *source) { +json::SerializationBuffer<> WebServer::sensor_state_json_generator(WebServer *web_server, void *source) { return web_server->sensor_json_((sensor::Sensor *) (source), ((sensor::Sensor *) (source))->state, DETAIL_STATE); } -std::string WebServer::sensor_all_json_generator(WebServer *web_server, void *source) { +json::SerializationBuffer<> WebServer::sensor_all_json_generator(WebServer *web_server, void *source) { return web_server->sensor_json_((sensor::Sensor *) (source), ((sensor::Sensor *) (source))->state, DETAIL_ALL); } -std::string WebServer::sensor_json_(sensor::Sensor *obj, float value, JsonDetail start_config) { +json::SerializationBuffer<> WebServer::sensor_json_(sensor::Sensor *obj, float value, JsonDetail start_config) { json::JsonBuilder builder; JsonObject root = builder.root(); @@ -653,23 +653,23 @@ void WebServer::handle_text_sensor_request(AsyncWebServerRequest *request, const // Note: request->method() is always HTTP_GET here (canHandle ensures this) if (entity_match.action_is_empty) { auto detail = get_request_detail(request); - std::string data = this->text_sensor_json_(obj, obj->state, detail); + auto data = this->text_sensor_json_(obj, obj->state, detail); request->send(200, "application/json", data.c_str()); return; } } request->send(404); } -std::string WebServer::text_sensor_state_json_generator(WebServer *web_server, void *source) { +json::SerializationBuffer<> WebServer::text_sensor_state_json_generator(WebServer *web_server, void *source) { return web_server->text_sensor_json_((text_sensor::TextSensor *) (source), ((text_sensor::TextSensor *) (source))->state, DETAIL_STATE); } -std::string WebServer::text_sensor_all_json_generator(WebServer *web_server, void *source) { +json::SerializationBuffer<> WebServer::text_sensor_all_json_generator(WebServer *web_server, void *source) { return web_server->text_sensor_json_((text_sensor::TextSensor *) (source), ((text_sensor::TextSensor *) (source))->state, DETAIL_ALL); } -std::string WebServer::text_sensor_json_(text_sensor::TextSensor *obj, const std::string &value, - JsonDetail start_config) { +json::SerializationBuffer<> WebServer::text_sensor_json_(text_sensor::TextSensor *obj, const std::string &value, + JsonDetail start_config) { json::JsonBuilder builder; JsonObject root = builder.root(); @@ -714,7 +714,7 @@ void WebServer::handle_switch_request(AsyncWebServerRequest *request, const UrlM if (request->method() == HTTP_GET && entity_match.action_is_empty) { auto detail = get_request_detail(request); - std::string data = this->switch_json_(obj, obj->state, detail); + auto data = this->switch_json_(obj, obj->state, detail); request->send(200, "application/json", data.c_str()); return; } @@ -739,13 +739,13 @@ void WebServer::handle_switch_request(AsyncWebServerRequest *request, const UrlM } request->send(404); } -std::string WebServer::switch_state_json_generator(WebServer *web_server, void *source) { +json::SerializationBuffer<> WebServer::switch_state_json_generator(WebServer *web_server, void *source) { return web_server->switch_json_((switch_::Switch *) (source), ((switch_::Switch *) (source))->state, DETAIL_STATE); } -std::string WebServer::switch_all_json_generator(WebServer *web_server, void *source) { +json::SerializationBuffer<> WebServer::switch_all_json_generator(WebServer *web_server, void *source) { return web_server->switch_json_((switch_::Switch *) (source), ((switch_::Switch *) (source))->state, DETAIL_ALL); } -std::string WebServer::switch_json_(switch_::Switch *obj, bool value, JsonDetail start_config) { +json::SerializationBuffer<> WebServer::switch_json_(switch_::Switch *obj, bool value, JsonDetail start_config) { json::JsonBuilder builder; JsonObject root = builder.root(); @@ -767,7 +767,7 @@ void WebServer::handle_button_request(AsyncWebServerRequest *request, const UrlM continue; if (request->method() == HTTP_GET && entity_match.action_is_empty) { auto detail = get_request_detail(request); - std::string data = this->button_json_(obj, detail); + auto data = this->button_json_(obj, detail); request->send(200, "application/json", data.c_str()); } else if (match.method_equals(ESPHOME_F("press"))) { DEFER_ACTION(obj, obj->press()); @@ -780,10 +780,10 @@ void WebServer::handle_button_request(AsyncWebServerRequest *request, const UrlM } request->send(404); } -std::string WebServer::button_all_json_generator(WebServer *web_server, void *source) { +json::SerializationBuffer<> WebServer::button_all_json_generator(WebServer *web_server, void *source) { return web_server->button_json_((button::Button *) (source), DETAIL_ALL); } -std::string WebServer::button_json_(button::Button *obj, JsonDetail start_config) { +json::SerializationBuffer<> WebServer::button_json_(button::Button *obj, JsonDetail start_config) { json::JsonBuilder builder; JsonObject root = builder.root(); @@ -810,22 +810,23 @@ void WebServer::handle_binary_sensor_request(AsyncWebServerRequest *request, con // Note: request->method() is always HTTP_GET here (canHandle ensures this) if (entity_match.action_is_empty) { auto detail = get_request_detail(request); - std::string data = this->binary_sensor_json_(obj, obj->state, detail); + auto data = this->binary_sensor_json_(obj, obj->state, detail); request->send(200, "application/json", data.c_str()); return; } } request->send(404); } -std::string WebServer::binary_sensor_state_json_generator(WebServer *web_server, void *source) { +json::SerializationBuffer<> WebServer::binary_sensor_state_json_generator(WebServer *web_server, void *source) { return web_server->binary_sensor_json_((binary_sensor::BinarySensor *) (source), ((binary_sensor::BinarySensor *) (source))->state, DETAIL_STATE); } -std::string WebServer::binary_sensor_all_json_generator(WebServer *web_server, void *source) { +json::SerializationBuffer<> WebServer::binary_sensor_all_json_generator(WebServer *web_server, void *source) { return web_server->binary_sensor_json_((binary_sensor::BinarySensor *) (source), ((binary_sensor::BinarySensor *) (source))->state, DETAIL_ALL); } -std::string WebServer::binary_sensor_json_(binary_sensor::BinarySensor *obj, bool value, JsonDetail start_config) { +json::SerializationBuffer<> WebServer::binary_sensor_json_(binary_sensor::BinarySensor *obj, bool value, + JsonDetail start_config) { json::JsonBuilder builder; JsonObject root = builder.root(); @@ -852,7 +853,7 @@ void WebServer::handle_fan_request(AsyncWebServerRequest *request, const UrlMatc if (request->method() == HTTP_GET && entity_match.action_is_empty) { auto detail = get_request_detail(request); - std::string data = this->fan_json_(obj, detail); + auto data = this->fan_json_(obj, detail); request->send(200, "application/json", data.c_str()); } else if (match.method_equals(ESPHOME_F("toggle"))) { DEFER_ACTION(obj, obj->toggle().perform()); @@ -893,13 +894,13 @@ void WebServer::handle_fan_request(AsyncWebServerRequest *request, const UrlMatc } request->send(404); } -std::string WebServer::fan_state_json_generator(WebServer *web_server, void *source) { +json::SerializationBuffer<> WebServer::fan_state_json_generator(WebServer *web_server, void *source) { return web_server->fan_json_((fan::Fan *) (source), DETAIL_STATE); } -std::string WebServer::fan_all_json_generator(WebServer *web_server, void *source) { +json::SerializationBuffer<> WebServer::fan_all_json_generator(WebServer *web_server, void *source) { return web_server->fan_json_((fan::Fan *) (source), DETAIL_ALL); } -std::string WebServer::fan_json_(fan::Fan *obj, JsonDetail start_config) { +json::SerializationBuffer<> WebServer::fan_json_(fan::Fan *obj, JsonDetail start_config) { json::JsonBuilder builder; JsonObject root = builder.root(); @@ -933,7 +934,7 @@ void WebServer::handle_light_request(AsyncWebServerRequest *request, const UrlMa if (request->method() == HTTP_GET && entity_match.action_is_empty) { auto detail = get_request_detail(request); - std::string data = this->light_json_(obj, detail); + auto data = this->light_json_(obj, detail); request->send(200, "application/json", data.c_str()); } else if (match.method_equals(ESPHOME_F("toggle"))) { DEFER_ACTION(obj, obj->toggle().perform()); @@ -972,13 +973,13 @@ void WebServer::handle_light_request(AsyncWebServerRequest *request, const UrlMa } request->send(404); } -std::string WebServer::light_state_json_generator(WebServer *web_server, void *source) { +json::SerializationBuffer<> WebServer::light_state_json_generator(WebServer *web_server, void *source) { return web_server->light_json_((light::LightState *) (source), DETAIL_STATE); } -std::string WebServer::light_all_json_generator(WebServer *web_server, void *source) { +json::SerializationBuffer<> WebServer::light_all_json_generator(WebServer *web_server, void *source) { return web_server->light_json_((light::LightState *) (source), DETAIL_ALL); } -std::string WebServer::light_json_(light::LightState *obj, JsonDetail start_config) { +json::SerializationBuffer<> WebServer::light_json_(light::LightState *obj, JsonDetail start_config) { json::JsonBuilder builder; JsonObject root = builder.root(); @@ -1012,7 +1013,7 @@ void WebServer::handle_cover_request(AsyncWebServerRequest *request, const UrlMa if (request->method() == HTTP_GET && entity_match.action_is_empty) { auto detail = get_request_detail(request); - std::string data = this->cover_json_(obj, detail); + auto data = this->cover_json_(obj, detail); request->send(200, "application/json", data.c_str()); return; } @@ -1060,13 +1061,13 @@ void WebServer::handle_cover_request(AsyncWebServerRequest *request, const UrlMa } request->send(404); } -std::string WebServer::cover_state_json_generator(WebServer *web_server, void *source) { +json::SerializationBuffer<> WebServer::cover_state_json_generator(WebServer *web_server, void *source) { return web_server->cover_json_((cover::Cover *) (source), DETAIL_STATE); } -std::string WebServer::cover_all_json_generator(WebServer *web_server, void *source) { +json::SerializationBuffer<> WebServer::cover_all_json_generator(WebServer *web_server, void *source) { return web_server->cover_json_((cover::Cover *) (source), DETAIL_ALL); } -std::string WebServer::cover_json_(cover::Cover *obj, JsonDetail start_config) { +json::SerializationBuffer<> WebServer::cover_json_(cover::Cover *obj, JsonDetail start_config) { json::JsonBuilder builder; JsonObject root = builder.root(); @@ -1101,7 +1102,7 @@ void WebServer::handle_number_request(AsyncWebServerRequest *request, const UrlM if (request->method() == HTTP_GET && entity_match.action_is_empty) { auto detail = get_request_detail(request); - std::string data = this->number_json_(obj, obj->state, detail); + auto data = this->number_json_(obj, obj->state, detail); request->send(200, "application/json", data.c_str()); return; } @@ -1120,13 +1121,13 @@ void WebServer::handle_number_request(AsyncWebServerRequest *request, const UrlM request->send(404); } -std::string WebServer::number_state_json_generator(WebServer *web_server, void *source) { +json::SerializationBuffer<> WebServer::number_state_json_generator(WebServer *web_server, void *source) { return web_server->number_json_((number::Number *) (source), ((number::Number *) (source))->state, DETAIL_STATE); } -std::string WebServer::number_all_json_generator(WebServer *web_server, void *source) { +json::SerializationBuffer<> WebServer::number_all_json_generator(WebServer *web_server, void *source) { return web_server->number_json_((number::Number *) (source), ((number::Number *) (source))->state, DETAIL_ALL); } -std::string WebServer::number_json_(number::Number *obj, float value, JsonDetail start_config) { +json::SerializationBuffer<> WebServer::number_json_(number::Number *obj, float value, JsonDetail start_config) { json::JsonBuilder builder; JsonObject root = builder.root(); @@ -1168,7 +1169,7 @@ void WebServer::handle_date_request(AsyncWebServerRequest *request, const UrlMat continue; if (request->method() == HTTP_GET && entity_match.action_is_empty) { auto detail = get_request_detail(request); - std::string data = this->date_json_(obj, detail); + auto data = this->date_json_(obj, detail); request->send(200, "application/json", data.c_str()); return; } @@ -1194,13 +1195,13 @@ void WebServer::handle_date_request(AsyncWebServerRequest *request, const UrlMat request->send(404); } -std::string WebServer::date_state_json_generator(WebServer *web_server, void *source) { +json::SerializationBuffer<> WebServer::date_state_json_generator(WebServer *web_server, void *source) { return web_server->date_json_((datetime::DateEntity *) (source), DETAIL_STATE); } -std::string WebServer::date_all_json_generator(WebServer *web_server, void *source) { +json::SerializationBuffer<> WebServer::date_all_json_generator(WebServer *web_server, void *source) { return web_server->date_json_((datetime::DateEntity *) (source), DETAIL_ALL); } -std::string WebServer::date_json_(datetime::DateEntity *obj, JsonDetail start_config) { +json::SerializationBuffer<> WebServer::date_json_(datetime::DateEntity *obj, JsonDetail start_config) { json::JsonBuilder builder; JsonObject root = builder.root(); @@ -1229,7 +1230,7 @@ void WebServer::handle_time_request(AsyncWebServerRequest *request, const UrlMat continue; if (request->method() == HTTP_GET && entity_match.action_is_empty) { auto detail = get_request_detail(request); - std::string data = this->time_json_(obj, detail); + auto data = this->time_json_(obj, detail); request->send(200, "application/json", data.c_str()); return; } @@ -1254,13 +1255,13 @@ void WebServer::handle_time_request(AsyncWebServerRequest *request, const UrlMat } request->send(404); } -std::string WebServer::time_state_json_generator(WebServer *web_server, void *source) { +json::SerializationBuffer<> WebServer::time_state_json_generator(WebServer *web_server, void *source) { return web_server->time_json_((datetime::TimeEntity *) (source), DETAIL_STATE); } -std::string WebServer::time_all_json_generator(WebServer *web_server, void *source) { +json::SerializationBuffer<> WebServer::time_all_json_generator(WebServer *web_server, void *source) { return web_server->time_json_((datetime::TimeEntity *) (source), DETAIL_ALL); } -std::string WebServer::time_json_(datetime::TimeEntity *obj, JsonDetail start_config) { +json::SerializationBuffer<> WebServer::time_json_(datetime::TimeEntity *obj, JsonDetail start_config) { json::JsonBuilder builder; JsonObject root = builder.root(); @@ -1289,7 +1290,7 @@ void WebServer::handle_datetime_request(AsyncWebServerRequest *request, const Ur continue; if (request->method() == HTTP_GET && entity_match.action_is_empty) { auto detail = get_request_detail(request); - std::string data = this->datetime_json_(obj, detail); + auto data = this->datetime_json_(obj, detail); request->send(200, "application/json", data.c_str()); return; } @@ -1314,13 +1315,13 @@ void WebServer::handle_datetime_request(AsyncWebServerRequest *request, const Ur } request->send(404); } -std::string WebServer::datetime_state_json_generator(WebServer *web_server, void *source) { +json::SerializationBuffer<> WebServer::datetime_state_json_generator(WebServer *web_server, void *source) { return web_server->datetime_json_((datetime::DateTimeEntity *) (source), DETAIL_STATE); } -std::string WebServer::datetime_all_json_generator(WebServer *web_server, void *source) { +json::SerializationBuffer<> WebServer::datetime_all_json_generator(WebServer *web_server, void *source) { return web_server->datetime_json_((datetime::DateTimeEntity *) (source), DETAIL_ALL); } -std::string WebServer::datetime_json_(datetime::DateTimeEntity *obj, JsonDetail start_config) { +json::SerializationBuffer<> WebServer::datetime_json_(datetime::DateTimeEntity *obj, JsonDetail start_config) { json::JsonBuilder builder; JsonObject root = builder.root(); @@ -1351,7 +1352,7 @@ void WebServer::handle_text_request(AsyncWebServerRequest *request, const UrlMat if (request->method() == HTTP_GET && entity_match.action_is_empty) { auto detail = get_request_detail(request); - std::string data = this->text_json_(obj, obj->state, detail); + auto data = this->text_json_(obj, obj->state, detail); request->send(200, "application/json", data.c_str()); return; } @@ -1370,13 +1371,13 @@ void WebServer::handle_text_request(AsyncWebServerRequest *request, const UrlMat request->send(404); } -std::string WebServer::text_state_json_generator(WebServer *web_server, void *source) { +json::SerializationBuffer<> WebServer::text_state_json_generator(WebServer *web_server, void *source) { return web_server->text_json_((text::Text *) (source), ((text::Text *) (source))->state, DETAIL_STATE); } -std::string WebServer::text_all_json_generator(WebServer *web_server, void *source) { +json::SerializationBuffer<> WebServer::text_all_json_generator(WebServer *web_server, void *source) { return web_server->text_json_((text::Text *) (source), ((text::Text *) (source))->state, DETAIL_ALL); } -std::string WebServer::text_json_(text::Text *obj, const std::string &value, JsonDetail start_config) { +json::SerializationBuffer<> WebServer::text_json_(text::Text *obj, const std::string &value, JsonDetail start_config) { json::JsonBuilder builder; JsonObject root = builder.root(); @@ -1408,7 +1409,7 @@ void WebServer::handle_select_request(AsyncWebServerRequest *request, const UrlM if (request->method() == HTTP_GET && entity_match.action_is_empty) { auto detail = get_request_detail(request); - std::string data = this->select_json_(obj, obj->has_state() ? obj->current_option() : StringRef(), detail); + auto data = this->select_json_(obj, obj->has_state() ? obj->current_option() : StringRef(), detail); request->send(200, "application/json", data.c_str()); return; } @@ -1427,15 +1428,15 @@ void WebServer::handle_select_request(AsyncWebServerRequest *request, const UrlM } request->send(404); } -std::string WebServer::select_state_json_generator(WebServer *web_server, void *source) { +json::SerializationBuffer<> WebServer::select_state_json_generator(WebServer *web_server, void *source) { auto *obj = (select::Select *) (source); return web_server->select_json_(obj, obj->has_state() ? obj->current_option() : StringRef(), DETAIL_STATE); } -std::string WebServer::select_all_json_generator(WebServer *web_server, void *source) { +json::SerializationBuffer<> WebServer::select_all_json_generator(WebServer *web_server, void *source) { auto *obj = (select::Select *) (source); return web_server->select_json_(obj, obj->has_state() ? obj->current_option() : StringRef(), DETAIL_ALL); } -std::string WebServer::select_json_(select::Select *obj, StringRef value, JsonDetail start_config) { +json::SerializationBuffer<> WebServer::select_json_(select::Select *obj, StringRef value, JsonDetail start_config) { json::JsonBuilder builder; JsonObject root = builder.root(); @@ -1467,7 +1468,7 @@ void WebServer::handle_climate_request(AsyncWebServerRequest *request, const Url if (request->method() == HTTP_GET && entity_match.action_is_empty) { auto detail = get_request_detail(request); - std::string data = this->climate_json_(obj, detail); + auto data = this->climate_json_(obj, detail); request->send(200, "application/json", data.c_str()); return; } @@ -1500,15 +1501,15 @@ void WebServer::handle_climate_request(AsyncWebServerRequest *request, const Url } request->send(404); } -std::string WebServer::climate_state_json_generator(WebServer *web_server, void *source) { +json::SerializationBuffer<> WebServer::climate_state_json_generator(WebServer *web_server, void *source) { // NOLINTNEXTLINE(clang-analyzer-cplusplus.NewDeleteLeaks) false positive with ArduinoJson return web_server->climate_json_((climate::Climate *) (source), DETAIL_STATE); } -std::string WebServer::climate_all_json_generator(WebServer *web_server, void *source) { +json::SerializationBuffer<> WebServer::climate_all_json_generator(WebServer *web_server, void *source) { // NOLINTNEXTLINE(clang-analyzer-cplusplus.NewDeleteLeaks) false positive with ArduinoJson return web_server->climate_json_((climate::Climate *) (source), DETAIL_ALL); } -std::string WebServer::climate_json_(climate::Climate *obj, JsonDetail start_config) { +json::SerializationBuffer<> WebServer::climate_json_(climate::Climate *obj, JsonDetail start_config) { // NOLINTBEGIN(clang-analyzer-cplusplus.NewDeleteLeaks) false positive with ArduinoJson json::JsonBuilder builder; JsonObject root = builder.root(); @@ -1641,7 +1642,7 @@ void WebServer::handle_lock_request(AsyncWebServerRequest *request, const UrlMat if (request->method() == HTTP_GET && entity_match.action_is_empty) { auto detail = get_request_detail(request); - std::string data = this->lock_json_(obj, obj->state, detail); + auto data = this->lock_json_(obj, obj->state, detail); request->send(200, "application/json", data.c_str()); return; } @@ -1666,13 +1667,13 @@ void WebServer::handle_lock_request(AsyncWebServerRequest *request, const UrlMat } request->send(404); } -std::string WebServer::lock_state_json_generator(WebServer *web_server, void *source) { +json::SerializationBuffer<> WebServer::lock_state_json_generator(WebServer *web_server, void *source) { return web_server->lock_json_((lock::Lock *) (source), ((lock::Lock *) (source))->state, DETAIL_STATE); } -std::string WebServer::lock_all_json_generator(WebServer *web_server, void *source) { +json::SerializationBuffer<> WebServer::lock_all_json_generator(WebServer *web_server, void *source) { return web_server->lock_json_((lock::Lock *) (source), ((lock::Lock *) (source))->state, DETAIL_ALL); } -std::string WebServer::lock_json_(lock::Lock *obj, lock::LockState value, JsonDetail start_config) { +json::SerializationBuffer<> WebServer::lock_json_(lock::Lock *obj, lock::LockState value, JsonDetail start_config) { json::JsonBuilder builder; JsonObject root = builder.root(); @@ -1700,7 +1701,7 @@ void WebServer::handle_valve_request(AsyncWebServerRequest *request, const UrlMa if (request->method() == HTTP_GET && entity_match.action_is_empty) { auto detail = get_request_detail(request); - std::string data = this->valve_json_(obj, detail); + auto data = this->valve_json_(obj, detail); request->send(200, "application/json", data.c_str()); return; } @@ -1746,13 +1747,13 @@ void WebServer::handle_valve_request(AsyncWebServerRequest *request, const UrlMa } request->send(404); } -std::string WebServer::valve_state_json_generator(WebServer *web_server, void *source) { +json::SerializationBuffer<> WebServer::valve_state_json_generator(WebServer *web_server, void *source) { return web_server->valve_json_((valve::Valve *) (source), DETAIL_STATE); } -std::string WebServer::valve_all_json_generator(WebServer *web_server, void *source) { +json::SerializationBuffer<> WebServer::valve_all_json_generator(WebServer *web_server, void *source) { return web_server->valve_json_((valve::Valve *) (source), DETAIL_ALL); } -std::string WebServer::valve_json_(valve::Valve *obj, JsonDetail start_config) { +json::SerializationBuffer<> WebServer::valve_json_(valve::Valve *obj, JsonDetail start_config) { json::JsonBuilder builder; JsonObject root = builder.root(); @@ -1785,7 +1786,7 @@ void WebServer::handle_alarm_control_panel_request(AsyncWebServerRequest *reques if (request->method() == HTTP_GET && entity_match.action_is_empty) { auto detail = get_request_detail(request); - std::string data = this->alarm_control_panel_json_(obj, obj->get_state(), detail); + auto data = this->alarm_control_panel_json_(obj, obj->get_state(), detail); request->send(200, "application/json", data.c_str()); return; } @@ -1825,19 +1826,19 @@ void WebServer::handle_alarm_control_panel_request(AsyncWebServerRequest *reques } request->send(404); } -std::string WebServer::alarm_control_panel_state_json_generator(WebServer *web_server, void *source) { +json::SerializationBuffer<> WebServer::alarm_control_panel_state_json_generator(WebServer *web_server, void *source) { return web_server->alarm_control_panel_json_((alarm_control_panel::AlarmControlPanel *) (source), ((alarm_control_panel::AlarmControlPanel *) (source))->get_state(), DETAIL_STATE); } -std::string WebServer::alarm_control_panel_all_json_generator(WebServer *web_server, void *source) { +json::SerializationBuffer<> WebServer::alarm_control_panel_all_json_generator(WebServer *web_server, void *source) { return web_server->alarm_control_panel_json_((alarm_control_panel::AlarmControlPanel *) (source), ((alarm_control_panel::AlarmControlPanel *) (source))->get_state(), DETAIL_ALL); } -std::string WebServer::alarm_control_panel_json_(alarm_control_panel::AlarmControlPanel *obj, - alarm_control_panel::AlarmControlPanelState value, - JsonDetail start_config) { +json::SerializationBuffer<> WebServer::alarm_control_panel_json_(alarm_control_panel::AlarmControlPanel *obj, + alarm_control_panel::AlarmControlPanelState value, + JsonDetail start_config) { json::JsonBuilder builder; JsonObject root = builder.root(); @@ -1866,7 +1867,7 @@ void WebServer::handle_water_heater_request(AsyncWebServerRequest *request, cons if (request->method() == HTTP_GET && entity_match.action_is_empty) { auto detail = get_request_detail(request); - std::string data = this->water_heater_json_(obj, detail); + auto data = this->water_heater_json_(obj, detail); request->send(200, "application/json", data.c_str()); return; } @@ -1902,14 +1903,14 @@ void WebServer::handle_water_heater_request(AsyncWebServerRequest *request, cons request->send(404); } -std::string WebServer::water_heater_state_json_generator(WebServer *web_server, void *source) { +json::SerializationBuffer<> WebServer::water_heater_state_json_generator(WebServer *web_server, void *source) { return web_server->water_heater_json_(static_cast(source), DETAIL_STATE); } -std::string WebServer::water_heater_all_json_generator(WebServer *web_server, void *source) { +json::SerializationBuffer<> WebServer::water_heater_all_json_generator(WebServer *web_server, void *source) { // NOLINTNEXTLINE(clang-analyzer-cplusplus.NewDeleteLeaks) false positive with ArduinoJson return web_server->water_heater_json_(static_cast(source), DETAIL_ALL); } -std::string WebServer::water_heater_json_(water_heater::WaterHeater *obj, JsonDetail start_config) { +json::SerializationBuffer<> WebServer::water_heater_json_(water_heater::WaterHeater *obj, JsonDetail start_config) { json::JsonBuilder builder; JsonObject root = builder.root(); char buf[PSTR_LOCAL_SIZE]; @@ -1971,7 +1972,7 @@ void WebServer::handle_infrared_request(AsyncWebServerRequest *request, const Ur if (request->method() == HTTP_GET && entity_match.action_is_empty) { auto detail = get_request_detail(request); - std::string data = this->infrared_json_(obj, detail); + auto data = this->infrared_json_(obj, detail); request->send(200, ESPHOME_F("application/json"), data.c_str()); return; } @@ -2031,12 +2032,12 @@ void WebServer::handle_infrared_request(AsyncWebServerRequest *request, const Ur request->send(404); } -std::string WebServer::infrared_all_json_generator(WebServer *web_server, void *source) { +json::SerializationBuffer<> WebServer::infrared_all_json_generator(WebServer *web_server, void *source) { // NOLINTNEXTLINE(clang-analyzer-cplusplus.NewDeleteLeaks) false positive with ArduinoJson return web_server->infrared_json_(static_cast(source), DETAIL_ALL); } -std::string WebServer::infrared_json_(infrared::Infrared *obj, JsonDetail start_config) { +json::SerializationBuffer<> WebServer::infrared_json_(infrared::Infrared *obj, JsonDetail start_config) { json::JsonBuilder builder; JsonObject root = builder.root(); @@ -2071,7 +2072,7 @@ void WebServer::handle_event_request(AsyncWebServerRequest *request, const UrlMa // Note: request->method() is always HTTP_GET here (canHandle ensures this) if (entity_match.action_is_empty) { auto detail = get_request_detail(request); - std::string data = this->event_json_(obj, StringRef(), detail); + auto data = this->event_json_(obj, StringRef(), detail); request->send(200, "application/json", data.c_str()); return; } @@ -2081,16 +2082,16 @@ void WebServer::handle_event_request(AsyncWebServerRequest *request, const UrlMa static StringRef get_event_type(event::Event *event) { return event ? event->get_last_event_type() : StringRef(); } -std::string WebServer::event_state_json_generator(WebServer *web_server, void *source) { +json::SerializationBuffer<> WebServer::event_state_json_generator(WebServer *web_server, void *source) { auto *event = static_cast(source); return web_server->event_json_(event, get_event_type(event), DETAIL_STATE); } // NOLINTBEGIN(clang-analyzer-cplusplus.NewDeleteLeaks) false positive with ArduinoJson -std::string WebServer::event_all_json_generator(WebServer *web_server, void *source) { +json::SerializationBuffer<> WebServer::event_all_json_generator(WebServer *web_server, void *source) { auto *event = static_cast(source); return web_server->event_json_(event, get_event_type(event), DETAIL_ALL); } -std::string WebServer::event_json_(event::Event *obj, StringRef event_type, JsonDetail start_config) { +json::SerializationBuffer<> WebServer::event_json_(event::Event *obj, StringRef event_type, JsonDetail start_config) { json::JsonBuilder builder; JsonObject root = builder.root(); @@ -2124,7 +2125,7 @@ void WebServer::handle_update_request(AsyncWebServerRequest *request, const UrlM if (request->method() == HTTP_GET && entity_match.action_is_empty) { auto detail = get_request_detail(request); - std::string data = this->update_json_(obj, detail); + auto data = this->update_json_(obj, detail); request->send(200, "application/json", data.c_str()); return; } @@ -2140,15 +2141,15 @@ void WebServer::handle_update_request(AsyncWebServerRequest *request, const UrlM } request->send(404); } -std::string WebServer::update_state_json_generator(WebServer *web_server, void *source) { +json::SerializationBuffer<> WebServer::update_state_json_generator(WebServer *web_server, void *source) { // NOLINTNEXTLINE(clang-analyzer-cplusplus.NewDeleteLeaks) false positive with ArduinoJson return web_server->update_json_((update::UpdateEntity *) (source), DETAIL_STATE); } -std::string WebServer::update_all_json_generator(WebServer *web_server, void *source) { +json::SerializationBuffer<> WebServer::update_all_json_generator(WebServer *web_server, void *source) { // NOLINTNEXTLINE(clang-analyzer-cplusplus.NewDeleteLeaks) false positive with ArduinoJson return web_server->update_json_((update::UpdateEntity *) (source), DETAIL_STATE); } -std::string WebServer::update_json_(update::UpdateEntity *obj, JsonDetail start_config) { +json::SerializationBuffer<> WebServer::update_json_(update::UpdateEntity *obj, JsonDetail start_config) { // NOLINTBEGIN(clang-analyzer-cplusplus.NewDeleteLeaks) false positive with ArduinoJson json::JsonBuilder builder; JsonObject root = builder.root(); diff --git a/esphome/components/web_server/web_server.h b/esphome/components/web_server/web_server.h index 6afe618b59..76c1c8b0bd 100644 --- a/esphome/components/web_server/web_server.h +++ b/esphome/components/web_server/web_server.h @@ -2,6 +2,7 @@ #include "list_entities.h" +#include "esphome/components/json/json_util.h" #include "esphome/components/web_server_base/web_server_base.h" #ifdef USE_WEBSERVER #include "esphome/core/component.h" @@ -103,7 +104,7 @@ enum JsonDetail { DETAIL_ALL, DETAIL_STATE }; can be forgotten. */ #if !defined(USE_ESP32) && defined(USE_ARDUINO) -using message_generator_t = std::string(WebServer *, void *); +using message_generator_t = json::SerializationBuffer<>(WebServer *, void *); class DeferredUpdateEventSourceList; class DeferredUpdateEventSource : public AsyncEventSource { @@ -257,7 +258,7 @@ class WebServer : public Controller, public Component, public AsyncWebHandler { void handle_index_request(AsyncWebServerRequest *request); /// Return the webserver configuration as JSON. - std::string get_config_json(); + json::SerializationBuffer<> get_config_json(); #ifdef USE_WEBSERVER_CSS_INCLUDE /// Handle included css request under '/0.css'. @@ -279,8 +280,8 @@ class WebServer : public Controller, public Component, public AsyncWebHandler { /// Handle a sensor request under '/sensor/'. void handle_sensor_request(AsyncWebServerRequest *request, const UrlMatch &match); - static std::string sensor_state_json_generator(WebServer *web_server, void *source); - static std::string sensor_all_json_generator(WebServer *web_server, void *source); + static json::SerializationBuffer<> sensor_state_json_generator(WebServer *web_server, void *source); + static json::SerializationBuffer<> sensor_all_json_generator(WebServer *web_server, void *source); #endif #ifdef USE_SWITCH @@ -289,8 +290,8 @@ class WebServer : public Controller, public Component, public AsyncWebHandler { /// Handle a switch request under '/switch//'. void handle_switch_request(AsyncWebServerRequest *request, const UrlMatch &match); - static std::string switch_state_json_generator(WebServer *web_server, void *source); - static std::string switch_all_json_generator(WebServer *web_server, void *source); + static json::SerializationBuffer<> switch_state_json_generator(WebServer *web_server, void *source); + static json::SerializationBuffer<> switch_all_json_generator(WebServer *web_server, void *source); #endif #ifdef USE_BUTTON @@ -298,7 +299,7 @@ class WebServer : public Controller, public Component, public AsyncWebHandler { void handle_button_request(AsyncWebServerRequest *request, const UrlMatch &match); // Buttons are stateless, so there is no button_state_json_generator - static std::string button_all_json_generator(WebServer *web_server, void *source); + static json::SerializationBuffer<> button_all_json_generator(WebServer *web_server, void *source); #endif #ifdef USE_BINARY_SENSOR @@ -307,8 +308,8 @@ class WebServer : public Controller, public Component, public AsyncWebHandler { /// Handle a binary sensor request under '/binary_sensor/'. void handle_binary_sensor_request(AsyncWebServerRequest *request, const UrlMatch &match); - static std::string binary_sensor_state_json_generator(WebServer *web_server, void *source); - static std::string binary_sensor_all_json_generator(WebServer *web_server, void *source); + static json::SerializationBuffer<> binary_sensor_state_json_generator(WebServer *web_server, void *source); + static json::SerializationBuffer<> binary_sensor_all_json_generator(WebServer *web_server, void *source); #endif #ifdef USE_FAN @@ -317,8 +318,8 @@ class WebServer : public Controller, public Component, public AsyncWebHandler { /// Handle a fan request under '/fan//'. void handle_fan_request(AsyncWebServerRequest *request, const UrlMatch &match); - static std::string fan_state_json_generator(WebServer *web_server, void *source); - static std::string fan_all_json_generator(WebServer *web_server, void *source); + static json::SerializationBuffer<> fan_state_json_generator(WebServer *web_server, void *source); + static json::SerializationBuffer<> fan_all_json_generator(WebServer *web_server, void *source); #endif #ifdef USE_LIGHT @@ -327,8 +328,8 @@ class WebServer : public Controller, public Component, public AsyncWebHandler { /// Handle a light request under '/light//'. void handle_light_request(AsyncWebServerRequest *request, const UrlMatch &match); - static std::string light_state_json_generator(WebServer *web_server, void *source); - static std::string light_all_json_generator(WebServer *web_server, void *source); + static json::SerializationBuffer<> light_state_json_generator(WebServer *web_server, void *source); + static json::SerializationBuffer<> light_all_json_generator(WebServer *web_server, void *source); #endif #ifdef USE_TEXT_SENSOR @@ -337,8 +338,8 @@ class WebServer : public Controller, public Component, public AsyncWebHandler { /// Handle a text sensor request under '/text_sensor/'. void handle_text_sensor_request(AsyncWebServerRequest *request, const UrlMatch &match); - static std::string text_sensor_state_json_generator(WebServer *web_server, void *source); - static std::string text_sensor_all_json_generator(WebServer *web_server, void *source); + static json::SerializationBuffer<> text_sensor_state_json_generator(WebServer *web_server, void *source); + static json::SerializationBuffer<> text_sensor_all_json_generator(WebServer *web_server, void *source); #endif #ifdef USE_COVER @@ -347,8 +348,8 @@ class WebServer : public Controller, public Component, public AsyncWebHandler { /// Handle a cover request under '/cover//'. void handle_cover_request(AsyncWebServerRequest *request, const UrlMatch &match); - static std::string cover_state_json_generator(WebServer *web_server, void *source); - static std::string cover_all_json_generator(WebServer *web_server, void *source); + static json::SerializationBuffer<> cover_state_json_generator(WebServer *web_server, void *source); + static json::SerializationBuffer<> cover_all_json_generator(WebServer *web_server, void *source); #endif #ifdef USE_NUMBER @@ -356,8 +357,8 @@ class WebServer : public Controller, public Component, public AsyncWebHandler { /// Handle a number request under '/number/'. void handle_number_request(AsyncWebServerRequest *request, const UrlMatch &match); - static std::string number_state_json_generator(WebServer *web_server, void *source); - static std::string number_all_json_generator(WebServer *web_server, void *source); + static json::SerializationBuffer<> number_state_json_generator(WebServer *web_server, void *source); + static json::SerializationBuffer<> number_all_json_generator(WebServer *web_server, void *source); #endif #ifdef USE_DATETIME_DATE @@ -365,8 +366,8 @@ class WebServer : public Controller, public Component, public AsyncWebHandler { /// Handle a date request under '/date/'. void handle_date_request(AsyncWebServerRequest *request, const UrlMatch &match); - static std::string date_state_json_generator(WebServer *web_server, void *source); - static std::string date_all_json_generator(WebServer *web_server, void *source); + static json::SerializationBuffer<> date_state_json_generator(WebServer *web_server, void *source); + static json::SerializationBuffer<> date_all_json_generator(WebServer *web_server, void *source); #endif #ifdef USE_DATETIME_TIME @@ -374,8 +375,8 @@ class WebServer : public Controller, public Component, public AsyncWebHandler { /// Handle a time request under '/time/'. void handle_time_request(AsyncWebServerRequest *request, const UrlMatch &match); - static std::string time_state_json_generator(WebServer *web_server, void *source); - static std::string time_all_json_generator(WebServer *web_server, void *source); + static json::SerializationBuffer<> time_state_json_generator(WebServer *web_server, void *source); + static json::SerializationBuffer<> time_all_json_generator(WebServer *web_server, void *source); #endif #ifdef USE_DATETIME_DATETIME @@ -383,8 +384,8 @@ class WebServer : public Controller, public Component, public AsyncWebHandler { /// Handle a datetime request under '/datetime/'. void handle_datetime_request(AsyncWebServerRequest *request, const UrlMatch &match); - static std::string datetime_state_json_generator(WebServer *web_server, void *source); - static std::string datetime_all_json_generator(WebServer *web_server, void *source); + static json::SerializationBuffer<> datetime_state_json_generator(WebServer *web_server, void *source); + static json::SerializationBuffer<> datetime_all_json_generator(WebServer *web_server, void *source); #endif #ifdef USE_TEXT @@ -392,8 +393,8 @@ class WebServer : public Controller, public Component, public AsyncWebHandler { /// Handle a text input request under '/text/'. void handle_text_request(AsyncWebServerRequest *request, const UrlMatch &match); - static std::string text_state_json_generator(WebServer *web_server, void *source); - static std::string text_all_json_generator(WebServer *web_server, void *source); + static json::SerializationBuffer<> text_state_json_generator(WebServer *web_server, void *source); + static json::SerializationBuffer<> text_all_json_generator(WebServer *web_server, void *source); #endif #ifdef USE_SELECT @@ -401,8 +402,8 @@ class WebServer : public Controller, public Component, public AsyncWebHandler { /// Handle a select request under '/select/'. void handle_select_request(AsyncWebServerRequest *request, const UrlMatch &match); - static std::string select_state_json_generator(WebServer *web_server, void *source); - static std::string select_all_json_generator(WebServer *web_server, void *source); + static json::SerializationBuffer<> select_state_json_generator(WebServer *web_server, void *source); + static json::SerializationBuffer<> select_all_json_generator(WebServer *web_server, void *source); #endif #ifdef USE_CLIMATE @@ -410,8 +411,8 @@ class WebServer : public Controller, public Component, public AsyncWebHandler { /// Handle a climate request under '/climate/'. void handle_climate_request(AsyncWebServerRequest *request, const UrlMatch &match); - static std::string climate_state_json_generator(WebServer *web_server, void *source); - static std::string climate_all_json_generator(WebServer *web_server, void *source); + static json::SerializationBuffer<> climate_state_json_generator(WebServer *web_server, void *source); + static json::SerializationBuffer<> climate_all_json_generator(WebServer *web_server, void *source); #endif #ifdef USE_LOCK @@ -420,8 +421,8 @@ class WebServer : public Controller, public Component, public AsyncWebHandler { /// Handle a lock request under '/lock//'. void handle_lock_request(AsyncWebServerRequest *request, const UrlMatch &match); - static std::string lock_state_json_generator(WebServer *web_server, void *source); - static std::string lock_all_json_generator(WebServer *web_server, void *source); + static json::SerializationBuffer<> lock_state_json_generator(WebServer *web_server, void *source); + static json::SerializationBuffer<> lock_all_json_generator(WebServer *web_server, void *source); #endif #ifdef USE_VALVE @@ -430,8 +431,8 @@ class WebServer : public Controller, public Component, public AsyncWebHandler { /// Handle a valve request under '/valve//'. void handle_valve_request(AsyncWebServerRequest *request, const UrlMatch &match); - static std::string valve_state_json_generator(WebServer *web_server, void *source); - static std::string valve_all_json_generator(WebServer *web_server, void *source); + static json::SerializationBuffer<> valve_state_json_generator(WebServer *web_server, void *source); + static json::SerializationBuffer<> valve_all_json_generator(WebServer *web_server, void *source); #endif #ifdef USE_ALARM_CONTROL_PANEL @@ -440,8 +441,8 @@ class WebServer : public Controller, public Component, public AsyncWebHandler { /// Handle a alarm_control_panel request under '/alarm_control_panel/'. void handle_alarm_control_panel_request(AsyncWebServerRequest *request, const UrlMatch &match); - static std::string alarm_control_panel_state_json_generator(WebServer *web_server, void *source); - static std::string alarm_control_panel_all_json_generator(WebServer *web_server, void *source); + static json::SerializationBuffer<> alarm_control_panel_state_json_generator(WebServer *web_server, void *source); + static json::SerializationBuffer<> alarm_control_panel_all_json_generator(WebServer *web_server, void *source); #endif #ifdef USE_WATER_HEATER @@ -450,22 +451,22 @@ class WebServer : public Controller, public Component, public AsyncWebHandler { /// Handle a water_heater request under '/water_heater//'. void handle_water_heater_request(AsyncWebServerRequest *request, const UrlMatch &match); - static std::string water_heater_state_json_generator(WebServer *web_server, void *source); - static std::string water_heater_all_json_generator(WebServer *web_server, void *source); + static json::SerializationBuffer<> water_heater_state_json_generator(WebServer *web_server, void *source); + static json::SerializationBuffer<> water_heater_all_json_generator(WebServer *web_server, void *source); #endif #ifdef USE_INFRARED /// Handle an infrared request under '/infrared//transmit'. void handle_infrared_request(AsyncWebServerRequest *request, const UrlMatch &match); - static std::string infrared_all_json_generator(WebServer *web_server, void *source); + static json::SerializationBuffer<> infrared_all_json_generator(WebServer *web_server, void *source); #endif #ifdef USE_EVENT void on_event(event::Event *obj) override; - static std::string event_state_json_generator(WebServer *web_server, void *source); - static std::string event_all_json_generator(WebServer *web_server, void *source); + static json::SerializationBuffer<> event_state_json_generator(WebServer *web_server, void *source); + static json::SerializationBuffer<> event_all_json_generator(WebServer *web_server, void *source); /// Handle a event request under '/event'. void handle_event_request(AsyncWebServerRequest *request, const UrlMatch &match); @@ -477,8 +478,8 @@ class WebServer : public Controller, public Component, public AsyncWebHandler { /// Handle a update request under '/update/'. void handle_update_request(AsyncWebServerRequest *request, const UrlMatch &match); - static std::string update_state_json_generator(WebServer *web_server, void *source); - static std::string update_all_json_generator(WebServer *web_server, void *source); + static json::SerializationBuffer<> update_state_json_generator(WebServer *web_server, void *source); + static json::SerializationBuffer<> update_all_json_generator(WebServer *web_server, void *source); #endif /// Override the web handler's canHandle method. @@ -586,71 +587,74 @@ class WebServer : public Controller, public Component, public AsyncWebHandler { private: #ifdef USE_SENSOR - std::string sensor_json_(sensor::Sensor *obj, float value, JsonDetail start_config); + json::SerializationBuffer<> sensor_json_(sensor::Sensor *obj, float value, JsonDetail start_config); #endif #ifdef USE_SWITCH - std::string switch_json_(switch_::Switch *obj, bool value, JsonDetail start_config); + json::SerializationBuffer<> switch_json_(switch_::Switch *obj, bool value, JsonDetail start_config); #endif #ifdef USE_BUTTON - std::string button_json_(button::Button *obj, JsonDetail start_config); + json::SerializationBuffer<> button_json_(button::Button *obj, JsonDetail start_config); #endif #ifdef USE_BINARY_SENSOR - std::string binary_sensor_json_(binary_sensor::BinarySensor *obj, bool value, JsonDetail start_config); + json::SerializationBuffer<> binary_sensor_json_(binary_sensor::BinarySensor *obj, bool value, + JsonDetail start_config); #endif #ifdef USE_FAN - std::string fan_json_(fan::Fan *obj, JsonDetail start_config); + json::SerializationBuffer<> fan_json_(fan::Fan *obj, JsonDetail start_config); #endif #ifdef USE_LIGHT - std::string light_json_(light::LightState *obj, JsonDetail start_config); + json::SerializationBuffer<> light_json_(light::LightState *obj, JsonDetail start_config); #endif #ifdef USE_TEXT_SENSOR - std::string text_sensor_json_(text_sensor::TextSensor *obj, const std::string &value, JsonDetail start_config); + json::SerializationBuffer<> text_sensor_json_(text_sensor::TextSensor *obj, const std::string &value, + JsonDetail start_config); #endif #ifdef USE_COVER - std::string cover_json_(cover::Cover *obj, JsonDetail start_config); + json::SerializationBuffer<> cover_json_(cover::Cover *obj, JsonDetail start_config); #endif #ifdef USE_NUMBER - std::string number_json_(number::Number *obj, float value, JsonDetail start_config); + json::SerializationBuffer<> number_json_(number::Number *obj, float value, JsonDetail start_config); #endif #ifdef USE_DATETIME_DATE - std::string date_json_(datetime::DateEntity *obj, JsonDetail start_config); + json::SerializationBuffer<> date_json_(datetime::DateEntity *obj, JsonDetail start_config); #endif #ifdef USE_DATETIME_TIME - std::string time_json_(datetime::TimeEntity *obj, JsonDetail start_config); + json::SerializationBuffer<> time_json_(datetime::TimeEntity *obj, JsonDetail start_config); #endif #ifdef USE_DATETIME_DATETIME - std::string datetime_json_(datetime::DateTimeEntity *obj, JsonDetail start_config); + json::SerializationBuffer<> datetime_json_(datetime::DateTimeEntity *obj, JsonDetail start_config); #endif #ifdef USE_TEXT - std::string text_json_(text::Text *obj, const std::string &value, JsonDetail start_config); + json::SerializationBuffer<> text_json_(text::Text *obj, const std::string &value, JsonDetail start_config); #endif #ifdef USE_SELECT - std::string select_json_(select::Select *obj, StringRef value, JsonDetail start_config); + json::SerializationBuffer<> select_json_(select::Select *obj, StringRef value, JsonDetail start_config); #endif #ifdef USE_CLIMATE - std::string climate_json_(climate::Climate *obj, JsonDetail start_config); + json::SerializationBuffer<> climate_json_(climate::Climate *obj, JsonDetail start_config); #endif #ifdef USE_LOCK - std::string lock_json_(lock::Lock *obj, lock::LockState value, JsonDetail start_config); + json::SerializationBuffer<> lock_json_(lock::Lock *obj, lock::LockState value, JsonDetail start_config); #endif #ifdef USE_VALVE - std::string valve_json_(valve::Valve *obj, JsonDetail start_config); + json::SerializationBuffer<> valve_json_(valve::Valve *obj, JsonDetail start_config); #endif #ifdef USE_ALARM_CONTROL_PANEL - std::string alarm_control_panel_json_(alarm_control_panel::AlarmControlPanel *obj, - alarm_control_panel::AlarmControlPanelState value, JsonDetail start_config); + json::SerializationBuffer<> alarm_control_panel_json_(alarm_control_panel::AlarmControlPanel *obj, + alarm_control_panel::AlarmControlPanelState value, + JsonDetail start_config); #endif #ifdef USE_EVENT - std::string event_json_(event::Event *obj, StringRef event_type, JsonDetail start_config); + json::SerializationBuffer<> event_json_(event::Event *obj, StringRef event_type, JsonDetail start_config); #endif #ifdef USE_WATER_HEATER - std::string water_heater_json_(water_heater::WaterHeater *obj, JsonDetail start_config); + json::SerializationBuffer<> water_heater_json_(water_heater::WaterHeater *obj, JsonDetail start_config); #endif #ifdef USE_INFRARED - std::string infrared_json_(infrared::Infrared *obj, JsonDetail start_config); + json::SerializationBuffer<> infrared_json_(infrared::Infrared *obj, JsonDetail start_config); #endif #ifdef USE_UPDATE - std::string update_json_(update::UpdateEntity *obj, JsonDetail start_config); + json::SerializationBuffer<> update_json_(update::UpdateEntity *obj, JsonDetail start_config); #endif }; diff --git a/esphome/components/web_server_idf/web_server_idf.cpp b/esphome/components/web_server_idf/web_server_idf.cpp index 1798159e7f..4034a22586 100644 --- a/esphome/components/web_server_idf/web_server_idf.cpp +++ b/esphome/components/web_server_idf/web_server_idf.cpp @@ -563,7 +563,7 @@ AsyncEventSourceResponse::AsyncEventSourceResponse(const AsyncWebServerRequest * // Configure reconnect timeout and send config // this should always go through since the tcp send buffer is empty on connect - std::string message = ws->get_config_json(); + auto message = ws->get_config_json(); this->try_send_nodefer(message.c_str(), "ping", millis(), 30000); #ifdef USE_WEBSERVER_SORTING @@ -617,7 +617,7 @@ void AsyncEventSourceResponse::deq_push_back_with_dedup_(void *source, message_g void AsyncEventSourceResponse::process_deferred_queue_() { while (!deferred_queue_.empty()) { DeferredEvent &de = deferred_queue_.front(); - std::string message = de.message_generator_(web_server_, de.source_); + auto message = de.message_generator_(web_server_, de.source_); if (this->try_send_nodefer(message.c_str(), "state")) { // O(n) but memory efficiency is more important than speed here which is why std::vector was chosen deferred_queue_.erase(deferred_queue_.begin()); @@ -854,7 +854,7 @@ void AsyncEventSourceResponse::deferrable_send_state(void *source, const char *e // trying to send first deq_push_back_with_dedup_(source, message_generator); } else { - std::string message = message_generator(web_server_, source); + auto message = message_generator(web_server_, source); if (!this->try_send_nodefer(message.c_str(), "state")) { deq_push_back_with_dedup_(source, message_generator); } diff --git a/esphome/components/web_server_idf/web_server_idf.h b/esphome/components/web_server_idf/web_server_idf.h index 74601ffda8..76ddfa35fd 100644 --- a/esphome/components/web_server_idf/web_server_idf.h +++ b/esphome/components/web_server_idf/web_server_idf.h @@ -16,6 +16,7 @@ #include #ifdef USE_WEBSERVER +#include "esphome/components/json/json_util.h" #include "esphome/components/web_server/list_entities.h" #endif @@ -250,7 +251,7 @@ class AsyncWebHandler { class AsyncEventSource; class AsyncEventSourceResponse; -using message_generator_t = std::string(esphome::web_server::WebServer *, void *); +using message_generator_t = json::SerializationBuffer<>(esphome::web_server::WebServer *, void *); /* This class holds a pointer to the source component that wants to publish a state event, and a pointer to a function diff --git a/tests/components/json/common.yaml b/tests/components/json/common.yaml index c36c7f2a5a..c4bf6c3831 100644 --- a/tests/components/json/common.yaml +++ b/tests/components/json/common.yaml @@ -4,15 +4,16 @@ interval: - interval: 60s then: - lambda: |- - // Test build_json - std::string json_str = esphome::json::build_json([](JsonObject root) { + // Test build_json - returns SerializationBuffer, use auto to avoid heap allocation + auto json_buf = esphome::json::build_json([](JsonObject root) { root["sensor"] = "temperature"; root["value"] = 23.5; root["unit"] = "°C"; }); - ESP_LOGD("test", "Built JSON: %s", json_str.c_str()); + ESP_LOGD("test", "Built JSON: %s", json_buf.c_str()); - // Test parse_json + // Test parse_json - implicit conversion to std::string for backward compatibility + std::string json_str = json_buf; bool parse_ok = esphome::json::parse_json(json_str, [](JsonObject root) { if (root["sensor"].is() && root["value"].is()) { const char* sensor = root["sensor"]; @@ -26,10 +27,10 @@ interval: }); ESP_LOGD("test", "Parse result (JSON syntax only): %s", parse_ok ? "success" : "failed"); - // Test JsonBuilder class + // Test JsonBuilder class - returns SerializationBuffer esphome::json::JsonBuilder builder; JsonObject obj = builder.root(); obj["test"] = "direct_builder"; obj["count"] = 42; - std::string result = builder.serialize(); + auto result = builder.serialize(); ESP_LOGD("test", "JsonBuilder result: %s", result.c_str());