ESPHome 2026.11.0-dev
Loading...
Searching...
No Matches
json_util.h
Go to the documentation of this file.
1#pragma once
2
3#include <algorithm>
4#include <cstddef>
5#include <cstring>
6#include <string>
7#include <utility>
8#include <vector>
9
13
14#define ARDUINOJSON_ENABLE_STD_STRING 1 // NOLINT
15
16#define ARDUINOJSON_USE_LONG_LONG 1 // NOLINT
17
18#include <ArduinoJson.h>
19
20namespace esphome::json {
21
22// Link a string into the document instead of copying it. Only for a literal, a codegen table
23// entry or a name set once during setup; never a stack buffer or an entity state.
24inline JsonString linked(const char *s) { return JsonString(s, true); }
25inline JsonString linked(const char *s, size_t len) { return JsonString(s, len, true); }
26inline JsonString linked(const std::string &s) { return JsonString(s.c_str(), s.size(), true); }
27JsonString linked(std::string &&) = delete; // a temporary dies before the document is serialized
28#ifdef USE_ESP8266
29// A PROGMEM string is copied out of flash
30inline ProgmemStr linked(ProgmemStr s) { return s; }
31#endif
32
36template<size_t STACK_SIZE = 640> class SerializationBuffer {
37 public:
38 static constexpr size_t BUFFER_SIZE = STACK_SIZE;
39
41 explicit SerializationBuffer(size_t size) : size_(size) {
42 if (size + 1 <= STACK_SIZE) {
43 buffer_ = stack_buffer_;
44 } else {
45 heap_buffer_ = new char[size + 1];
46 buffer_ = heap_buffer_;
47 }
48 buffer_[0] = '\0';
49 }
50
51 ~SerializationBuffer() { delete[] heap_buffer_; }
52
53 // Move constructor - works with same template instantiation
55 : heap_buffer_(std::exchange(other.heap_buffer_, nullptr)), size_(other.size_) {
56 if (other.buffer_ == other.stack_buffer_) {
57 // Stack buffer - must copy content
58 std::memcpy(stack_buffer_, other.stack_buffer_, size_ + 1);
59 buffer_ = stack_buffer_;
60 } else {
61 // Heap buffer - ownership was taken above
62 buffer_ = heap_buffer_;
63 }
64 // Leave moved-from object in valid empty state
65 other.stack_buffer_[0] = '\0';
66 other.buffer_ = other.stack_buffer_;
67 other.size_ = 0;
68 }
69
70 // Move assignment
72 if (this != &other) {
73 delete[] heap_buffer_;
74 heap_buffer_ = std::exchange(other.heap_buffer_, nullptr);
75 size_ = other.size_;
76 if (other.buffer_ == other.stack_buffer_) {
77 std::memcpy(stack_buffer_, other.stack_buffer_, size_ + 1);
78 buffer_ = stack_buffer_;
79 } else {
80 buffer_ = heap_buffer_;
81 }
82 // Leave moved-from object in valid empty state
83 other.stack_buffer_[0] = '\0';
84 other.buffer_ = other.stack_buffer_;
85 other.size_ = 0;
86 }
87 return *this;
88 }
89
90 // Delete copy operations
93
95 const char *c_str() const { return buffer_; }
97 const char *data() const { return buffer_; }
99 size_t size() const { return size_; }
100
104 operator std::string() const { return std::string(buffer_, size_); } // NOLINT(google-explicit-constructor)
105
106 private:
107 friend class JsonBuilder;
108
110 char *data_writable_() { return buffer_; }
113 void set_size_(size_t size) {
114 size_ = size;
115 buffer_[size] = '\0';
116 }
117
120 void reallocate_heap_(size_t size) {
121 delete[] heap_buffer_;
122 heap_buffer_ = new char[size + 1];
123 buffer_ = heap_buffer_;
124 size_ = size;
125 buffer_[0] = '\0';
126 }
127
128 char stack_buffer_[STACK_SIZE];
129 char *heap_buffer_{nullptr};
130 char *buffer_;
131 size_t size_;
132};
133
134#ifdef USE_PSRAM
135// Build an allocator for the JSON Library using the RAMAllocator class
136// This is only compiled when PSRAM is enabled
137struct SpiRamAllocator : ArduinoJson::Allocator {
138 void *allocate(size_t size) override {
139 RAMAllocator<uint8_t> allocator;
140 return allocator.allocate(size);
141 }
142
143 void deallocate(void *ptr) override {
144 // ArduinoJson's Allocator interface doesn't provide the size parameter in deallocate.
145 // RAMAllocator::deallocate() requires the size, which we don't have access to here.
146 // RAMAllocator::deallocate implementation just calls free() regardless of whether
147 // the memory was allocated with heap_caps_malloc or malloc.
148 // This is safe because ESP-IDF's heap implementation internally tracks the memory region
149 // and routes free() to the appropriate heap.
150 free(ptr); // NOLINT(cppcoreguidelines-owning-memory,cppcoreguidelines-no-malloc)
151 }
152
153 void *reallocate(void *ptr, size_t new_size) override {
154 RAMAllocator<uint8_t> allocator;
155 return allocator.reallocate(static_cast<uint8_t *>(ptr), new_size);
156 }
157};
158#endif
159
161using json_parse_t = std::function<bool(JsonObject)>;
162
164using json_build_t = std::function<void(JsonObject)>;
165
169
171bool parse_json(const std::string &data, const json_parse_t &f);
173bool parse_json(const uint8_t *data, size_t len, const json_parse_t &f);
174
176JsonDocument parse_json(const uint8_t *data, size_t len);
178inline JsonDocument parse_json(const std::string &data) {
179 return parse_json(reinterpret_cast<const uint8_t *>(data.c_str()), data.size());
180}
181
183ArduinoJson::Allocator *heap_json_allocator();
184
185#ifdef USE_JSON_ARENA
187constexpr size_t JSON_POOL_BYTES = ARDUINOJSON_POOL_CAPACITY * sizeof(ArduinoJson::detail::VariantData);
189constexpr size_t JSON_ARENA_SIZE = JSON_POOL_BYTES + 1152;
190static_assert(sizeof(void *) != 4 || JSON_ARENA_SIZE == 2176, "the arena was sized for a 1 KB pool");
191
194template<size_t N> class JsonArena final : public ArduinoJson::Allocator {
195 public:
196 // Takes what the buffer cannot hold
197 explicit JsonArena(ArduinoJson::Allocator *fallback = heap_json_allocator()) : fallback_(fallback) {}
198 // The document points into buf_
199 JsonArena(const JsonArena &) = delete;
200 JsonArena &operator=(const JsonArena &) = delete;
201
202 void *allocate(size_t size) override {
203 if (size > N) {
204 return this->fallback_->allocate(size); // also keeps the rounding below from wrapping
205 }
206 size = (size + ALIGN - 1) & ~(ALIGN - 1);
207 if (size > N - this->used_) {
208 return this->fallback_->allocate(size);
209 }
210 this->last_ = this->used_;
211 this->used_ += size;
212 return this->buf_ + this->last_;
213 }
214 void deallocate(void *ptr) override {
215 if (!this->owns_(ptr)) {
216 this->fallback_->deallocate(ptr);
217 }
218 }
219 void *reallocate(void *ptr, size_t new_size) override {
220 if (!this->owns_(ptr)) {
221 return this->fallback_->reallocate(ptr, new_size);
222 }
223 const size_t off = static_cast<uint8_t *>(ptr) - this->buf_;
224 const size_t size = new_size > N ? N + ALIGN : (new_size + ALIGN - 1) & ~(ALIGN - 1);
225 const bool newest = off == this->last_;
226 if (newest && size <= N - off) {
227 this->used_ = off + size; // the newest block grows or shrinks in place
228 return ptr;
229 }
230 // An older block's size is unknown; copying to the end of the buffer stays in bounds
231 const size_t old_size = newest ? this->used_ - off : N - off;
232 void *moved = this->fallback_->allocate(new_size);
233 if (moved == nullptr) {
234 return nullptr; // the caller keeps ptr, so its arena space stays reserved
235 }
236 std::memcpy(moved, ptr, std::min(new_size, old_size));
237 if (newest) {
238 this->used_ = off; // it moved to the heap, so its arena space is free again
239 }
240 return moved;
241 }
243 size_t used() const { return this->used_; }
244
245 private:
246 static constexpr size_t ALIGN = alignof(std::max_align_t);
247 bool owns_(const void *ptr) const { return ptr >= this->buf_ && ptr < this->buf_ + N; }
248 ArduinoJson::Allocator *fallback_;
249 alignas(ALIGN) uint8_t buf_[N];
250 size_t used_{0};
251 size_t last_{0};
252};
253#endif // USE_JSON_ARENA
254
257 public:
258 // Out of line: inlining the JsonDocument constructor duplicates it at every call site
259 JsonBuilder();
260#ifdef USE_JSON_ARENA
261 // The builder must not outlive the allocator
262 explicit JsonBuilder(ArduinoJson::Allocator *allocator);
263#endif
264
265 JsonObject root() {
266 if (!root_created_) {
267 root_ = doc_.to<JsonObject>();
268 root_created_ = true;
269 }
270 return root_;
271 }
272
275 size_t serialize_to(char *buf, size_t cap);
276
280
281 private:
282 JsonDocument doc_;
283 JsonObject root_;
284 bool root_created_{false};
285};
286
287} // namespace esphome::json
An STL allocator that uses SPI or internal RAM.
Definition helpers.h:2298
T * reallocate(T *p, size_t n)
Definition helpers.h:2340
T * allocate(size_t n)
Definition helpers.h:2325
Bump allocator over a fixed buffer for a document built and serialized in one scope.
Definition json_util.h:194
JsonArena(ArduinoJson::Allocator *fallback=heap_json_allocator())
Definition json_util.h:197
void * allocate(size_t size) override
Definition json_util.h:202
void * reallocate(void *ptr, size_t new_size) override
Definition json_util.h:219
JsonArena(const JsonArena &)=delete
JsonArena & operator=(const JsonArena &)=delete
size_t used() const
Bytes of the buffer handed out so far.
Definition json_util.h:243
void deallocate(void *ptr) override
Definition json_util.h:214
Builder class for creating JSON documents without lambdas.
Definition json_util.h:256
size_t serialize_to(char *buf, size_t cap)
Serialize into a caller owned buffer.
Definition json_util.cpp:80
SerializationBuffer serialize()
Serialize the JSON document to a SerializationBuffer (stack-first allocation) Uses 512-byte stack buf...
Definition json_util.cpp:96
Buffer for JSON serialization that uses stack allocation for small payloads.
Definition json_util.h:36
static constexpr size_t BUFFER_SIZE
Stack buffer size for this instantiation.
Definition json_util.h:38
size_t size() const
Get string length (excluding null terminator)
Definition json_util.h:99
SerializationBuffer(size_t size)
Construct with known size (typically from measureJson)
Definition json_util.h:41
SerializationBuffer & operator=(const SerializationBuffer &)=delete
const char * data() const
Get data pointer.
Definition json_util.h:97
SerializationBuffer(const SerializationBuffer &)=delete
const char * c_str() const
Get null-terminated C string.
Definition json_util.h:95
SerializationBuffer & operator=(SerializationBuffer &&other) noexcept
Definition json_util.h:71
SerializationBuffer(SerializationBuffer &&other) noexcept
Definition json_util.h:54
std::function< void(JsonObject)> json_build_t
Callback function typedef for building JsonObjects.
Definition json_util.h:164
ArduinoJson::Allocator * heap_json_allocator()
The allocator a JsonBuilder uses by default (PSRAM first when available)
Definition json_util.cpp:72
constexpr size_t JSON_ARENA_SIZE
One pool plus room for copied string nodes; a 40 option select with linked options fits.
Definition json_util.h:189
bool parse_json(const std::string &data, const json_parse_t &f)
Parse a JSON string and run the provided json parse function if it's valid.
Definition json_util.cpp:28
constexpr size_t JSON_POOL_BYTES
Size of one ArduinoJson slot pool, the first allocation every document makes (1 KB on 32 bit targets)
Definition json_util.h:187
std::function< bool(JsonObject)> json_parse_t
Callback function typedef for parsing JsonObjects.
Definition json_util.h:161
JsonString linked(const char *s)
Definition json_util.h:24
SerializationBuffer build_json(const json_build_t &f)
Build a JSON string with the provided json build function.
Definition json_util.cpp:19
const void size_t len
Definition hal.h:68
uint16_t size
Definition helpers.cpp:34
const __FlashStringHelper * ProgmemStr
Definition progmem.h:31
void deallocate(void *ptr) override
Definition json_util.h:143
void * allocate(size_t size) override
Definition json_util.h:138
void * reallocate(void *ptr, size_t new_size) override
Definition json_util.h:153