ESPHome 2026.10.2
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 <vector>
8
11
12#define ARDUINOJSON_ENABLE_STD_STRING 1 // NOLINT
13
14#define ARDUINOJSON_USE_LONG_LONG 1 // NOLINT
15
16#include <ArduinoJson.h>
17
18namespace esphome::json {
19
23template<size_t STACK_SIZE = 640> class SerializationBuffer {
24 public:
25 static constexpr size_t BUFFER_SIZE = STACK_SIZE;
26
28 explicit SerializationBuffer(size_t size) : size_(size) {
29 if (size + 1 <= STACK_SIZE) {
30 buffer_ = stack_buffer_;
31 } else {
32 heap_buffer_ = new char[size + 1];
33 buffer_ = heap_buffer_;
34 }
35 buffer_[0] = '\0';
36 }
37
38 ~SerializationBuffer() { delete[] heap_buffer_; }
39
40 // Move constructor - works with same template instantiation
41 SerializationBuffer(SerializationBuffer &&other) noexcept : heap_buffer_(other.heap_buffer_), size_(other.size_) {
42 if (other.buffer_ == other.stack_buffer_) {
43 // Stack buffer - must copy content
44 std::memcpy(stack_buffer_, other.stack_buffer_, size_ + 1);
45 buffer_ = stack_buffer_;
46 } else {
47 // Heap buffer - steal ownership
48 buffer_ = heap_buffer_;
49 other.heap_buffer_ = nullptr;
50 }
51 // Leave moved-from object in valid empty state
52 other.stack_buffer_[0] = '\0';
53 other.buffer_ = other.stack_buffer_;
54 other.size_ = 0;
55 }
56
57 // Move assignment
59 if (this != &other) {
60 delete[] heap_buffer_;
61 heap_buffer_ = other.heap_buffer_;
62 size_ = other.size_;
63 if (other.buffer_ == other.stack_buffer_) {
64 std::memcpy(stack_buffer_, other.stack_buffer_, size_ + 1);
65 buffer_ = stack_buffer_;
66 } else {
67 buffer_ = heap_buffer_;
68 other.heap_buffer_ = nullptr;
69 }
70 // Leave moved-from object in valid empty state
71 other.stack_buffer_[0] = '\0';
72 other.buffer_ = other.stack_buffer_;
73 other.size_ = 0;
74 }
75 return *this;
76 }
77
78 // Delete copy operations
81
83 const char *c_str() const { return buffer_; }
85 const char *data() const { return buffer_; }
87 size_t size() const { return size_; }
88
92 operator std::string() const { return std::string(buffer_, size_); } // NOLINT(google-explicit-constructor)
93
94 private:
95 friend class JsonBuilder;
96
98 char *data_writable_() { return buffer_; }
101 void set_size_(size_t size) {
102 size_ = size;
103 buffer_[size] = '\0';
104 }
105
108 void reallocate_heap_(size_t size) {
109 delete[] heap_buffer_;
110 heap_buffer_ = new char[size + 1];
111 buffer_ = heap_buffer_;
112 size_ = size;
113 buffer_[0] = '\0';
114 }
115
116 char stack_buffer_[STACK_SIZE];
117 char *heap_buffer_{nullptr};
118 char *buffer_;
119 size_t size_;
120};
121
122#ifdef USE_PSRAM
123// Build an allocator for the JSON Library using the RAMAllocator class
124// This is only compiled when PSRAM is enabled
125struct SpiRamAllocator : ArduinoJson::Allocator {
126 void *allocate(size_t size) override {
127 RAMAllocator<uint8_t> allocator;
128 return allocator.allocate(size);
129 }
130
131 void deallocate(void *ptr) override {
132 // ArduinoJson's Allocator interface doesn't provide the size parameter in deallocate.
133 // RAMAllocator::deallocate() requires the size, which we don't have access to here.
134 // RAMAllocator::deallocate implementation just calls free() regardless of whether
135 // the memory was allocated with heap_caps_malloc or malloc.
136 // This is safe because ESP-IDF's heap implementation internally tracks the memory region
137 // and routes free() to the appropriate heap.
138 free(ptr); // NOLINT(cppcoreguidelines-owning-memory,cppcoreguidelines-no-malloc)
139 }
140
141 void *reallocate(void *ptr, size_t new_size) override {
142 RAMAllocator<uint8_t> allocator;
143 return allocator.reallocate(static_cast<uint8_t *>(ptr), new_size);
144 }
145};
146#endif
147
149using json_parse_t = std::function<bool(JsonObject)>;
150
152using json_build_t = std::function<void(JsonObject)>;
153
157
159bool parse_json(const std::string &data, const json_parse_t &f);
161bool parse_json(const uint8_t *data, size_t len, const json_parse_t &f);
162
164JsonDocument parse_json(const uint8_t *data, size_t len);
166inline JsonDocument parse_json(const std::string &data) {
167 return parse_json(reinterpret_cast<const uint8_t *>(data.c_str()), data.size());
168}
169
171ArduinoJson::Allocator *heap_json_allocator();
172
173#ifdef USE_JSON_ARENA
175constexpr size_t JSON_POOL_BYTES = ARDUINOJSON_POOL_CAPACITY * sizeof(ArduinoJson::detail::VariantData);
177constexpr size_t JSON_ARENA_SIZE = JSON_POOL_BYTES + 1152;
178static_assert(sizeof(void *) != 4 || JSON_ARENA_SIZE == 2176, "the arena was sized for a 1 KB pool");
179
182template<size_t N> class JsonArena final : public ArduinoJson::Allocator {
183 public:
184 // Takes what the buffer cannot hold
185 explicit JsonArena(ArduinoJson::Allocator *fallback = heap_json_allocator()) : fallback_(fallback) {}
186 // The document points into buf_
187 JsonArena(const JsonArena &) = delete;
188 JsonArena &operator=(const JsonArena &) = delete;
189
190 void *allocate(size_t size) override {
191 if (size > N) {
192 return this->fallback_->allocate(size); // also keeps the rounding below from wrapping
193 }
194 size = (size + ALIGN - 1) & ~(ALIGN - 1);
195 if (size > N - this->used_) {
196 return this->fallback_->allocate(size);
197 }
198 this->last_ = this->used_;
199 this->used_ += size;
200 return this->buf_ + this->last_;
201 }
202 void deallocate(void *ptr) override {
203 if (!this->owns_(ptr)) {
204 this->fallback_->deallocate(ptr);
205 }
206 }
207 void *reallocate(void *ptr, size_t new_size) override {
208 if (!this->owns_(ptr)) {
209 return this->fallback_->reallocate(ptr, new_size);
210 }
211 const size_t off = static_cast<uint8_t *>(ptr) - this->buf_;
212 const size_t size = new_size > N ? N + ALIGN : (new_size + ALIGN - 1) & ~(ALIGN - 1);
213 const bool newest = off == this->last_;
214 if (newest && size <= N - off) {
215 this->used_ = off + size; // the newest block grows or shrinks in place
216 return ptr;
217 }
218 // An older block's size is unknown; copying to the end of the buffer stays in bounds
219 const size_t old_size = newest ? this->used_ - off : N - off;
220 void *moved = this->fallback_->allocate(new_size);
221 if (moved == nullptr) {
222 return nullptr; // the caller keeps ptr, so its arena space stays reserved
223 }
224 std::memcpy(moved, ptr, std::min(new_size, old_size));
225 if (newest) {
226 this->used_ = off; // it moved to the heap, so its arena space is free again
227 }
228 return moved;
229 }
231 size_t used() const { return this->used_; }
232
233 private:
234 static constexpr size_t ALIGN = alignof(std::max_align_t);
235 bool owns_(const void *ptr) const { return ptr >= this->buf_ && ptr < this->buf_ + N; }
236 ArduinoJson::Allocator *fallback_;
237 alignas(ALIGN) uint8_t buf_[N];
238 size_t used_{0};
239 size_t last_{0};
240};
241#endif // USE_JSON_ARENA
242
245 public:
246 // Out of line: inlining the JsonDocument constructor duplicates it at every call site
247 JsonBuilder();
248#ifdef USE_JSON_ARENA
249 // The builder must not outlive the allocator
250 explicit JsonBuilder(ArduinoJson::Allocator *allocator);
251#endif
252
253 JsonObject root() {
254 if (!root_created_) {
255 root_ = doc_.to<JsonObject>();
256 root_created_ = true;
257 }
258 return root_;
259 }
260
263 size_t serialize_to(char *buf, size_t cap);
264
268
269 private:
270 JsonDocument doc_;
271 JsonObject root_;
272 bool root_created_{false};
273};
274
275} // namespace esphome::json
An STL allocator that uses SPI or internal RAM.
Definition helpers.h:2231
T * reallocate(T *p, size_t n)
Definition helpers.h:2273
T * allocate(size_t n)
Definition helpers.h:2258
Bump allocator over a fixed buffer for a document built and serialized in one scope.
Definition json_util.h:182
JsonArena(ArduinoJson::Allocator *fallback=heap_json_allocator())
Definition json_util.h:185
void * allocate(size_t size) override
Definition json_util.h:190
void * reallocate(void *ptr, size_t new_size) override
Definition json_util.h:207
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:231
void deallocate(void *ptr) override
Definition json_util.h:202
Builder class for creating JSON documents without lambdas.
Definition json_util.h:244
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:23
static constexpr size_t BUFFER_SIZE
Stack buffer size for this instantiation.
Definition json_util.h:25
size_t size() const
Get string length (excluding null terminator)
Definition json_util.h:87
SerializationBuffer(size_t size)
Construct with known size (typically from measureJson)
Definition json_util.h:28
SerializationBuffer & operator=(const SerializationBuffer &)=delete
const char * data() const
Get data pointer.
Definition json_util.h:85
SerializationBuffer(const SerializationBuffer &)=delete
const char * c_str() const
Get null-terminated C string.
Definition json_util.h:83
SerializationBuffer & operator=(SerializationBuffer &&other) noexcept
Definition json_util.h:58
SerializationBuffer(SerializationBuffer &&other) noexcept
Definition json_util.h:41
std::function< void(JsonObject)> json_build_t
Callback function typedef for building JsonObjects.
Definition json_util.h:152
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:177
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:175
std::function< bool(JsonObject)> json_parse_t
Callback function typedef for parsing JsonObjects.
Definition json_util.h:149
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:64
uint16_t size
Definition helpers.cpp:33
void deallocate(void *ptr) override
Definition json_util.h:131
void * allocate(size_t size) override
Definition json_util.h:126
void * reallocate(void *ptr, size_t new_size) override
Definition json_util.h:141