FastLED 3.9.15
Loading...
Searching...
No Matches
json.cpp
Go to the documentation of this file.
1
2#include "fl/json.h"
3#include "fl/string.h"
4#include "fl/vector.h"
5#include "fl/deque.h"
6#include "fl/function.h"
7#include "fl/sketch_macros.h"
8#include "fl/math.h" // For floor function
10#include "fl/thread_local.h"
11
12// Define INT16_MIN, INT16_MAX, and UINT8_MAX if not already defined
13#ifndef INT16_MIN
14#define INT16_MIN (-32768)
15#endif
16
17#ifndef INT16_MAX
18#define INT16_MAX 32767
19#endif
20
21#ifndef UINT8_MAX
22#define UINT8_MAX 255
23#endif
24
25
26#if FASTLED_ENABLE_JSON
27
32
33#endif // FASTLED_ENABLE_JSON
34
35namespace fl {
36
37
38
39// Helper function to check if a double can be reasonably represented as a float
40// Used for debug logging - may appear unused in release builds
43static bool canBeRepresentedAsFloat(double value) {
44
45
46 auto isnan = [](double value) -> bool {
47 return value != value;
48 };
49
50 // Check for special values
51 if (isnan(value)) {
52 return true; // These can be represented as float
53 }
54
55 // Check if the value is within reasonable float range
56 // Reject values that are clearly beyond float precision (beyond 2^24 for integers)
57 // or outside the float range
58 if (fl::fl_abs(value) > 16777216.0) { // 2^24 - beyond which floats lose integer precision
59 return false;
60 }
61
62 // For values within reasonable range, allow conversion even with minor precision loss
63 // This handles cases like 300000.14159 which should be convertible to float
64 // even though it loses some precision
65 return true;
66}
68
69
71 static ThreadLocal<JsonValue> null_value;
72 return null_value.access();
73}
74
76 // thread_local JsonObject empty_object;
77 static ThreadLocal<JsonObject> empty_object;
78 return empty_object.access();
79}
80
82 #if !FASTLED_ENABLE_JSON
84 #else
85 // Determine the size of the JsonDocument needed.
86 FLArduinoJson::JsonDocument doc;
87
88 FLArduinoJson::DeserializationError error = FLArduinoJson::deserializeJson(doc, txt.c_str());
89
90 if (error) {
91 FL_WARN("JSON parsing failed: " << error.c_str());
92 return fl::make_shared<JsonValue>(nullptr); // Return null on error
93 }
94
95 // Helper function to convert FLArduinoJson::JsonVariantConst to fl::Json::JsonValue
96 struct Converter {
97 static fl::shared_ptr<JsonValue> convert(const FLArduinoJson::JsonVariantConst& src) {
98 if (src.isNull()) {
99 return fl::make_shared<JsonValue>(nullptr);
100 } else if (src.is<bool>()) {
101 return fl::make_shared<JsonValue>(src.as<bool>());
102 } else if (src.is<int64_t>()) {
103 // Handle 64-bit integers
104 return fl::make_shared<JsonValue>(src.as<int64_t>());
105 } else if (src.is<int32_t>()) {
106 // Handle 32-bit integers explicitly for platform compatibility
107 return fl::make_shared<JsonValue>(static_cast<int64_t>(src.as<int32_t>()));
108 } else if (src.is<uint32_t>()) {
109 // Handle unsigned 32-bit integers
110 return fl::make_shared<JsonValue>(static_cast<int64_t>(src.as<uint32_t>()));
111 } else if (src.is<double>()) {
112 // Handle double precision floats - convert to float
113 return fl::make_shared<JsonValue>(static_cast<float>(src.as<double>()));
114 } else if (src.is<float>()) {
115 // Handle single precision floats explicitly
116 return fl::make_shared<JsonValue>(src.as<float>());
117 } else if (src.is<const char*>()) {
118 return fl::make_shared<JsonValue>(fl::string(src.as<const char*>()));
119 } else if (src.is<FLArduinoJson::JsonArrayConst>()) {
120 FLArduinoJson::JsonArrayConst arr = src.as<FLArduinoJson::JsonArrayConst>();
121
122 // Empty arrays should remain regular arrays
123 if (arr.size() == 0) {
125 }
126
127 // Enum to represent array optimization types
128 enum ArrayType {
129 ALL_UINT8,
130 ALL_INT16,
131 ALL_FLOATS,
132 GENERIC_ARRAY
133 };
134
135 // Helper struct to track array type info
136 struct ArrayTypeInfo {
137 bool isUint8 = true;
138 bool isInt16 = true;
139 bool isFloat = true;
140
141 void disableAll() {
142 isUint8 = false;
143 isInt16 = false;
144 isFloat = false;
145 }
146
147 void checkNumericValue(double val) {
148 // Check integer ranges in one pass
149 bool isInteger = val == floor(val);
150 if (!isInteger || val < 0 || val > UINT8_MAX) {
151 isUint8 = false;
152 }
153 if (!isInteger || val < INT16_MIN || val > INT16_MAX) {
154 isInt16 = false;
155 }
156 if (!canBeRepresentedAsFloat(val)) {
157 isFloat = false;
158 }
159 }
160
161 void checkIntegerValue(int64_t val) {
162 // Check all ranges in one pass
163 if (val < 0 || val > UINT8_MAX) {
164 isUint8 = false;
165 }
166 if (val < INT16_MIN || val > INT16_MAX) {
167 isInt16 = false;
168 }
169 if (val < -16777216 || val > 16777216) {
170 isFloat = false;
171 }
172 }
173
174 ArrayType getBestType() const {
175 if (isUint8) return ALL_UINT8;
176 if (isInt16) return ALL_INT16;
177 if (isFloat) return ALL_FLOATS;
178 return GENERIC_ARRAY;
179 }
180 };
181
182 ArrayTypeInfo typeInfo;
183
184 #if FASTLED_DEBUG_LEVEL >= 2
185 FASTLED_WARN("Array conversion: processing " << arr.size() << " items");
186 #endif
187
188 for (const auto& item : arr) {
189 // Check if all items are numeric
190 if (!item.is<int32_t>() && !item.is<int64_t>() && !item.is<double>()) {
191 typeInfo.disableAll();
192 #if FASTLED_DEBUG_LEVEL >= 2
193 FASTLED_WARN("Non-numeric value found, no optimization possible");
194 #endif
195 break;
196 }
197
198 // Update type flags based on item type
199 if (item.is<double>()) {
200 double val = item.as<double>();
201 typeInfo.checkNumericValue(val);
202 } else {
203 int64_t val = item.is<int32_t>() ? item.as<int32_t>() : item.as<int64_t>();
204 typeInfo.checkIntegerValue(val);
205 }
206 }
207
208 // Determine the optimal array type based on the flags
209 ArrayType arrayType = arr.size() > 0 ? typeInfo.getBestType() : GENERIC_ARRAY;
210
211 // Apply the most appropriate optimization based on determined type
212 switch (arrayType) {
213 case ALL_UINT8: {
214 // All values fit in uint8_t - most compact representation
215 fl::vector<uint8_t> byteData;
216 for (const auto& item : arr) {
217 if (item.is<double>()) {
218 byteData.push_back(static_cast<uint8_t>(item.as<double>()));
219 } else {
220 int64_t val = item.is<int32_t>() ? item.as<int32_t>() : item.as<int64_t>();
221 byteData.push_back(static_cast<uint8_t>(val));
222 }
223 }
224 return fl::make_shared<JsonValue>(fl::move(byteData));
225 }
226 case ALL_INT16: {
227 // All values fit in int16_t - good compression
228 fl::vector<int16_t> intData;
229 for (const auto& item : arr) {
230 if (item.is<double>()) {
231 intData.push_back(static_cast<int16_t>(item.as<double>()));
232 } else {
233 int64_t val = item.is<int32_t>() ? item.as<int32_t>() : item.as<int64_t>();
234 intData.push_back(static_cast<int16_t>(val));
235 }
236 }
237 return fl::make_shared<JsonValue>(fl::move(intData));
238 }
239 case ALL_FLOATS: {
240 // All values can be exactly represented as floats - use float vector
241 fl::vector<float> floatData;
242 for (const auto& item : arr) {
243 if (item.is<double>()) {
244 floatData.push_back(static_cast<float>(item.as<double>()));
245 } else {
246 int64_t val = item.is<int32_t>() ? item.as<int32_t>() : item.as<int64_t>();
247 floatData.push_back(static_cast<float>(val));
248 }
249 }
250 return fl::make_shared<JsonValue>(fl::move(floatData));
251 }
252 case GENERIC_ARRAY:
253 default: {
254 // No optimization possible - use regular array
255 JsonArray regularArr;
256 for (const auto& item : arr) {
257 regularArr.push_back(convert(item));
258 }
259 return fl::make_shared<JsonValue>(fl::move(regularArr));
260 }
261 }
262 } else if (src.is<FLArduinoJson::JsonObjectConst>()) {
263 JsonObject obj;
264 for (const auto& kv : src.as<FLArduinoJson::JsonObjectConst>()) {
265 obj[fl::string(kv.key().c_str())] = convert(kv.value());
266 }
268 }
269 return fl::make_shared<JsonValue>(nullptr); // Should not happen
270 }
271 };
272
273 return Converter::convert(doc.as<FLArduinoJson::JsonVariantConst>());
274 #endif
275}
276
278 // Parse the JSON value to a string, then parse it back to a Json object,
279 // and use the working to_string_native method
280 // This is a workaround to avoid reimplementing the serialization logic
281
282 // First, create a temporary Json document with this value
283 Json temp;
284 // Use the public method to set the value
286 // Use the working implementation
287 return temp.to_string_native();
288}
289
291 if (!m_value) {
292 return "null";
293 }
294
295 // 🚨 NEW APPROACH: Use fl::deque for memory-efficient JSON serialization
296 // This avoids memory fragmentation and bypasses broken ArduinoJson integration
297 fl::deque<char> json_chars;
298
299 // Helper lambda for appending strings to deque
300 auto append_string = [&json_chars](const char* str) {
301 while (*str) {
302 json_chars.push_back(*str);
303 ++str;
304 }
305 };
306
307 // Helper lambda for appending fl::string to deque
308 auto append_fl_string = [&json_chars](const fl::string& str) {
309 for (size_t i = 0; i < str.size(); ++i) {
310 json_chars.push_back(str[i]);
311 }
312 };
313
314 // Helper lambda for appending escaped strings
315 auto append_escaped_string = [&](const fl::string& str) {
316 json_chars.push_back('"');
317 for (size_t i = 0; i < str.size(); ++i) {
318 char c = str[i];
319 switch (c) {
320 case '"': append_string("\\\""); break;
321 case '\\': append_string("\\\\"); break;
322 case '\n': append_string("\\n"); break;
323 case '\r': append_string("\\r"); break;
324 case '\t': append_string("\\t"); break;
325 case '\b': append_string("\\b"); break;
326 case '\f': append_string("\\f"); break;
327 default:
328 json_chars.push_back(c);
329 break;
330 }
331 }
332 json_chars.push_back('"');
333 };
334
335 // Recursive function to serialize JsonValue to deque
336 fl::function<void(const JsonValue&)> serialize_value = [&](const JsonValue& value) {
337 if (value.is_null()) {
338 append_string("null");
339 } else if (value.is_bool()) {
340 auto opt = value.as_bool();
341 if (opt) {
342 append_string(*opt ? "true" : "false");
343 } else {
344 append_string("null");
345 }
346 } else if (value.is_int()) {
347 auto opt = value.as_int();
348 if (opt) {
349 fl::string num_str;
350 num_str.append(*opt);
351 append_fl_string(num_str);
352 } else {
353 append_string("null");
354 }
355 } else if (value.is_float()) {
356 auto opt = value.as_float();
357 if (opt) {
358 fl::string num_str;
359 // Use fl::string::append which already handles float formatting correctly
360 //num_str.append(*opt);
361 num_str.append(static_cast<float>(*opt), 3);
362 append_fl_string(num_str);
363 } else {
364 append_string("null");
365 }
366 } else if (value.is_string()) {
367 auto opt = value.as_string();
368 if (opt) {
369 append_escaped_string(*opt);
370 } else {
371 append_string("null");
372 }
373 } else if (value.is_array()) {
374 auto opt = value.as_array();
375 if (opt) {
376 json_chars.push_back('[');
377 bool first = true;
378 for (const auto& item : *opt) {
379 if (!first) {
380 json_chars.push_back(',');
381 }
382 first = false;
383 if (item) {
384 serialize_value(*item);
385 } else {
386 append_string("null");
387 }
388 }
389 json_chars.push_back(']');
390 } else {
391 append_string("null");
392 }
393 } else if (value.is_object()) {
394 auto opt = value.as_object();
395 if (opt) {
396 json_chars.push_back('{');
397 bool first = true;
398 for (const auto& kv : *opt) {
399 if (!first) {
400 json_chars.push_back(',');
401 }
402 first = false;
403 append_escaped_string(kv.first);
404 json_chars.push_back(':');
405 if (kv.second) {
406 serialize_value(*kv.second);
407 } else {
408 append_string("null");
409 }
410 }
411 json_chars.push_back('}');
412 } else {
413 append_string("null");
414 }
415 } else {
416 // Handle specialized array types
417 if (value.is_audio()) {
418 auto audioOpt = value.as_audio();
419 if (audioOpt) {
420 json_chars.push_back('[');
421 bool first = true;
422 for (const auto& item : *audioOpt) {
423 if (!first) {
424 json_chars.push_back(',');
425 }
426 first = false;
427 fl::string num_str;
428 num_str.append(static_cast<int>(item));
429 append_fl_string(num_str);
430 }
431 json_chars.push_back(']');
432 } else {
433 append_string("null");
434 }
435 } else if (value.is_bytes()) {
436 auto bytesOpt = value.as_bytes();
437 if (bytesOpt) {
438 json_chars.push_back('[');
439 bool first = true;
440 for (const auto& item : *bytesOpt) {
441 if (!first) {
442 json_chars.push_back(',');
443 }
444 first = false;
445 fl::string num_str;
446 num_str.append(static_cast<int>(item));
447 append_fl_string(num_str);
448 }
449 json_chars.push_back(']');
450 } else {
451 append_string("null");
452 }
453 } else if (value.is_floats()) {
454 auto floatsOpt = value.as_floats();
455 if (floatsOpt) {
456 json_chars.push_back('[');
457 bool first = true;
458 for (const auto& item : *floatsOpt) {
459 if (!first) {
460 json_chars.push_back(',');
461 }
462 first = false;
463 fl::string num_str;
464 num_str.append(static_cast<float>(item), 6);
465 append_fl_string(num_str);
466 }
467 json_chars.push_back(']');
468 } else {
469 append_string("null");
470 }
471 } else {
472 append_string("null");
473 }
474 }
475 };
476
477 // Serialize the root value
478 serialize_value(*m_value);
479
480 // Convert deque to fl::string efficiently
482 if (!json_chars.empty()) {
483 // Get pointer to the underlying data and construct string directly
484 result.assign(&json_chars[0], json_chars.size());
485 }
486
487 return result;
488}
489
490// Forward declaration for the serializeValue function
492
493
496 if (!jsonStr) {
497 return result;
498 }
499 size_t len = strlen(jsonStr);
500 for (size_t i = 0; i < len; ++i) {
501 char c = jsonStr[i];
502 if (c != ' ' && c != '\t' && c != '\n' && c != '\r') {
503 result += c;
504 }
505 }
506 return result;
507}
508
509} // namespace fl
void push_back(const T &value)
Definition vector.h:552
T value() const
Definition json.h:1802
void set_value(const fl::shared_ptr< JsonValue > &value)
Definition json.h:2112
fl::shared_ptr< JsonValue > m_value
Definition json.h:1622
static fl::string normalizeJsonString(const char *jsonStr)
Definition json.cpp:494
fl::string to_string_native() const
Definition json.cpp:290
const char * c_str() const
Definition str.h:326
fl::size size() const
Definition deque.h:283
bool empty() const
Definition deque.h:279
void push_back(const T &value)
Definition deque.h:298
Result type for promise operations.
string & append(const BitsetFixed< N > &bs)
Definition str.h:675
#define FL_DISABLE_WARNING(warning)
#define FL_DISABLE_WARNING_PUSH
#define FL_DISABLE_WARNING_NULL_DEREFERENCE
#define FL_DISABLE_WARNING_POP
FastLED's Elegant JSON Library: fl::Json
#define INT16_MAX
Definition json.cpp:18
#define UINT8_MAX
Definition json.cpp:22
constexpr remove_reference< T >::type && move(T &&t) noexcept
Definition move.h:27
fl::string serializeValue(const JsonValue &value)
JsonObject & get_empty_json_object()
Definition json.cpp:75
T fl_abs(T value)
Definition math_macros.h:11
ThreadLocalFake< T > ThreadLocal
shared_ptr< T > make_shared(Args &&... args)
Definition shared_ptr.h:348
fl::HashMap< fl::string, fl::shared_ptr< JsonValue > > JsonObject
Definition json.h:171
fl::vector< fl::shared_ptr< JsonValue > > JsonArray
Definition json.h:170
T floor(T value)
Definition math.h:32
HeapVector< T, Allocator > vector
Definition vector.h:1214
FL_DISABLE_WARNING_POP JsonValue & get_null_value()
Definition json.cpp:70
IMPORTANT!
Definition crgb.h:20
friend class Json
Definition json.h:658
static fl::shared_ptr< JsonValue > parse(const fl::string &txt)
Definition json.cpp:81
fl::string to_string() const
Definition json.cpp:277
#define FASTLED_WARN
Definition warn.h:7
#define FL_WARN
Definition warn.h:12