diff --git a/esphome/components/json/json_util.cpp b/esphome/components/json/json_util.cpp index 4d6c7993f7..7b59facfe5 100644 --- a/esphome/components/json/json_util.cpp +++ b/esphome/components/json/json_util.cpp @@ -71,10 +71,22 @@ SerializationBuffer<> JsonBuilder::serialize() { buf[2] = '\0'; return result; } - size_t size = measureJson(doc_); - SerializationBuffer<> result(size); - serializeJson(doc_, result.data_writable(), size + 1); - return result; + // Intentionally avoid measureJson() - it instantiates DummyWriter templates that add ~700 bytes + // of flash. Instead, try serializing to stack buffer first. 768 bytes covers typical JSON payloads + // (sensors ~200B, lights ~170B, climate ~700B). Only entities with many options exceed this. + // serializeJson() returns actual size needed even if truncated, so we can retry with heap if needed. + constexpr size_t buf_size = SerializationBuffer<>::BUFFER_SIZE; + SerializationBuffer<> result(buf_size - 1); // Max content size (reserve 1 for null) + 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; + } + // Needs heap allocation - serialize again with exact size + SerializationBuffer<> heap_result(size); + serializeJson(doc_, heap_result.data_writable(), size + 1); + return heap_result; } } // namespace json diff --git a/esphome/components/json/json_util.h b/esphome/components/json/json_util.h index 56ae17b721..5dbeed8b76 100644 --- a/esphome/components/json/json_util.h +++ b/esphome/components/json/json_util.h @@ -21,6 +21,8 @@ namespace json { /// 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) { @@ -84,6 +86,8 @@ template class SerializationBuffer { size_t size() const { return size_; } /// Get writable buffer (for serialization) char *data_writable() { return buffer_; } + /// Set actual size after serialization (must not exceed allocated size) + void set_size(size_t size) { size_ = size; } /// Implicit conversion to std::string for backward compatibility operator std::string() const { return std::string(buffer_, size_); } // NOLINT(google-explicit-constructor)