Files
fahnen_esp32/.pio/libdeps/esp01_1m/FastLED/src/fl/json.h

2209 lines
75 KiB
C++

#pragma once
/**
* @file fl/json.h
* @brief FastLED's Elegant JSON Library: `fl::Json`
*
* @details
*
* The `fl::Json` library provides a lightweight, type-safe, and highly ergonomic
* interface for both parsing and generating JSON data within the FastLED ecosystem.
*
* Key Features & Design Principles:
* ------------------------------------
* - **Fluid Chaining**: Effortlessly navigate nested JSON structures using
* `json["key"]["nested_key"]` or `json["array_key"][index]`.
* - **Default Values (`operator|`)**: The cornerstone of robust parsing. Safely
* extract values with a fallback, preventing crashes from missing keys or
* type mismatches: `int value = json["path"]["to"]["key"] | 123;`
* - **Type Safety**: Methods return `fl::optional<T>` for explicit handling of
* potential absence or type errors, ensuring predictable behavior.
* - **Unified API**: A consistent and intuitive interface for both reading
* and writing JSON data.
* - **Explicit Creation**: Clearly define JSON objects and arrays using
* `fl::Json::object()` and `fl::Json::array()`.
*
* Parsing JSON Data - The Clean Way:
* ------------------------------------
* Parse a JSON string and extract values with graceful defaults.
*
* @code
* #include "fl/json.h"
* #include "fl/warn.h" // For FL_WARN
*
* const char* jsonStr = R"({
* "config": {
* "brightness": 128,
* "enabled": true,
* "name": "my_device"
* },
* "status": "active"
* })";
*
* fl::Json jsonDoc = fl::Json::parse(jsonStr);
*
* // Accessing an integer with a default value
* int brightness = jsonDoc["config"]["brightness"] | 255; // Result: 128
* FL_WARN("Brightness: " << brightness);
*
* // Accessing a boolean with a default value
* bool enabled = jsonDoc["config"]["enabled"] | false; // Result: true
* FL_WARN("Enabled: " << enabled);
*
* // Accessing a string with a default value
* fl::string deviceName = jsonDoc["config"]["name"] | fl::string("unknown"); // Result: "my_device"
* FL_WARN("Device Name: " << deviceName);
*
* // Accessing a non-existent key with a default value
* int nonExistent = jsonDoc["config"]["non_existent_key"] | 0; // Result: 0
* FL_WARN("Non-existent: " << nonExistent);
* @endcode
*
* Generating JSON Data - Build with Ease:
* -----------------------------------------
* Construct complex JSON objects and arrays programmatically.
*
* @code
* #include "fl/json.h"
* #include "fl/string.h"
* #include "fl/vector.h"
* #include "fl/warn.h"
*
* // Create a root JSON object
* fl::Json newJson = fl::Json::object();
*
* // Set primitive values
* newJson.set("version", 1.0);
* newJson.set("isActive", true);
* newJson.set("message", "Hello, FastLED!");
*
* // Create and set a nested object
* fl::Json settings = fl::Json::object();
* settings.set("mode", "dynamic");
* settings.set("speed", 50);
* newJson.set("settings", settings);
*
* // Create and set a nested array
* fl::Json colors = fl::Json::array();
* colors.push_back(fl::Json("red"));
* colors.push_back(fl::Json("green"));
* colors.push_back(fl::Json("blue"));
* newJson.set("colors", colors);
*
* // Convert the entire JSON object to a string
* fl::string jsonString = newJson.to_string();
* FL_WARN("Generated JSON:\n" << jsonString);
* // Expected output (formatting may vary):
* // {"version":1.0,"isActive":true,"message":"Hello, FastLED!","settings":{"mode":"dynamic","speed":50},"colors":["red","green","blue"]}
* @endcode
*
* Important Considerations:
* ---------------------------
* - **Error Handling**: While `operator|` is powerful, for critical parsing
* steps (e.g., validating the root object), always use `has_value()` and
* `is_object()`/`is_array()` checks.
* - **Memory Management**: `fl::Json` leverages `fl::shared_ptr` internally,
* simplifying memory management. You typically won't need manual `new`/`delete`.
* - **`fl::` Namespace**: Adhere to FastLED's convention; always use the `fl::`
* prefix for library components (e.g., `fl::Json`, `fl::string`, `fl::vector`).
* Avoid `std::` equivalents.
*
* HIGH LEVEL: For platforms with a lot of memory, this parsing library (ArduinoJson) will automatically be included.
* Othweise you'll just get Json -> str encoding (and no parsing). You can check if you haee
* the full library by detecting if FASTLED_ENABLE_JSON is defined.
*
* It's important to note that ArduinoJson only is used for parsing. We use a custom serializer for
* output to string. But this is usually easy to do. For help serializing out, look at fl/sstream.h
* for converting a collection of values to a string.
*
* It's entirely possible that our json string output serializer is NOT 100% correct with respect to
* complex string encoding (for example an HTML document). If you see bugs, then file an issue at
* https://github.com/fastled/FastLED/issues
*
* for string parsing, we should be full featured when FASTLED_ENABLE_JSON is defined (automiatic for SKETCH_HAS_LOTS_OF_MEMORY).
* If there is some string that doesn't correctly parse, use b64 encoding. For example, you might get better luck b64 encoding
* 1024 elements of small ints then manually deserializing to a fl::vector<u8>. Infact, it's pretty much assured.
*
* That being said...
*
* This api is designed specifically to be fast for input <--> output of arrays of numbers in {u8, i16, float}.
* If you have a different encodings scheme, for example an array of tuples, then this library will be MUCH MUCH
* slower than Arduino Json. If you stick to the scheme we have optimized for (flat arrays of numbers
* and few dictionaries) then this api will be blazing fast. Otherwise? Expect pain: slowdowns and lots of memory consumption.
*
* Why?
*
* ArduinoJson only has a nice interface if you agree to bring in std::-everything. Ever bring in std::sstream?
* The amount of code that will be pulled for <sstream> will blow your mind. To keep things strict and tiny
* we have to take ArduinoJson and strip all the optional components out then seal the remaining api in cement and
* bolt on a nice fluid interface ontop. That's what fl/json.h is.
*
* And the good news is - it works great!! And if it doesn't? File a bug and we'll have it fixed in the next release.
*/
#include "fl/string.h"
#include "fl/vector.h"
#include "fl/hash_map.h"
#include "fl/variant.h"
#include "fl/optional.h"
#include "fl/unique_ptr.h"
#include "fl/shared_ptr.h"
#include "fl/functional.h"
#include "fl/str.h" // For StringFormatter
#include "fl/promise.h" // For Error type
#include "fl/warn.h" // For FL_WARN
#include "fl/sketch_macros.h"
#ifndef FASTLED_ENABLE_JSON
#define FASTLED_ENABLE_JSON SKETCH_HAS_LOTS_OF_MEMORY
#endif
namespace fl {
// Forward declarations
struct JsonValue;
// Define Array and Object as pointers to avoid incomplete type issues
// We'll use heap-allocated containers for these to avoid alignment issues
using JsonArray = fl::vector<fl::shared_ptr<JsonValue>>;
using JsonObject = fl::HashMap<fl::string, fl::shared_ptr<JsonValue>>;
// ParseResult struct to replace variant<T, Error>
template<typename T>
struct ParseResult {
T value;
Error error;
ParseResult(const T& val) : value(val), error() {}
ParseResult(const Error& err) : value(), error(err) {}
bool has_error() const { return !error.is_empty(); }
const T& get_value() const { return value; }
const Error& get_error() const { return error; }
// Implicit conversion operator to allow using ParseResult as T directly
operator const T&() const {
if (has_error()) {
// This should ideally trigger some kind of error handling
// For now, we'll just return the value (which might be default-initialized)
}
return value;
}
};
// Function to get a reference to a static null JsonValue
JsonValue& get_null_value();
// Function to get a reference to a static empty JsonObject
JsonObject& get_empty_json_object();
// AI - pay attention to this - implementing visitor pattern
template<typename T>
struct DefaultValueVisitor {
const T& fallback;
const T* result = nullptr;
T storage; // Use instance storage instead of static
DefaultValueVisitor(const T& fb) : fallback(fb) {}
// This is the method that fl::Variant expects
template<typename U>
void accept(const U& value) {
// Dispatch to the correct operator() overload
(*this)(value);
}
// Specific overload for the type T
void operator()(const T& value) {
result = &value;
}
// Special handling for integer conversions
template<typename U>
typename fl::enable_if<fl::is_integral<T>::value && fl::is_integral<U>::value, void>::type
operator()(const U& value) {
// Convert between integer types
storage = static_cast<T>(value);
result = &storage;
}
// Special handling for floating point to integer conversion
template<typename U>
typename fl::enable_if<fl::is_integral<T>::value && fl::is_floating_point<U>::value, void>::type
operator()(const U& value) {
// Convert float to integer
storage = static_cast<T>(value);
result = &storage;
}
// Special handling for integer to floating point conversion
template<typename U>
typename fl::enable_if<fl::is_floating_point<T>::value && fl::is_integral<U>::value, void>::type
operator()(const U& value) {
// Convert integer to float
storage = static_cast<T>(value);
result = &storage;
}
// Special handling for floating point to floating point conversion
template<typename U>
typename fl::enable_if<fl::is_floating_point<T>::value && fl::is_floating_point<U>::value && !fl::is_same<T, U>::value, void>::type
operator()(const U& value) {
// Convert between floating point types (e.g., double to float)
storage = static_cast<T>(value);
result = &storage;
}
// Generic overload for all other types
template<typename U>
typename fl::enable_if<
!(fl::is_integral<T>::value && fl::is_integral<U>::value) &&
!(fl::is_integral<T>::value && fl::is_floating_point<U>::value) &&
!(fl::is_floating_point<T>::value && fl::is_integral<U>::value) &&
!(fl::is_floating_point<T>::value && fl::is_floating_point<U>::value && !fl::is_same<T, U>::value),
void>::type
operator()(const U&) {
// Do nothing for other types
}
// Special handling for nullptr_t
void operator()(const fl::nullptr_t&) {
// Do nothing - will return fallback
}
};
// Visitor for converting values to int
template<typename IntType = int64_t>
struct IntConversionVisitor {
fl::optional<IntType> result;
template<typename U>
void accept(const U& value) {
// Dispatch to the correct operator() overload
(*this)(value);
}
// Special handling to avoid conflict when IntType is int64_t
template<typename T = IntType>
typename fl::enable_if<!fl::is_same<T, int64_t>::value && !fl::is_same<T, double>::value, void>::type
operator()(const IntType& value) {
result = value;
}
// Special handling for int64_t case
template<typename T = IntType>
typename fl::enable_if<fl::is_same<T, int64_t>::value, void>::type
operator()(const int64_t& value) {
result = value;
}
// Special handling for double case (when IntType is double)
template<typename T = IntType>
typename fl::enable_if<fl::is_same<T, double>::value, void>::type
operator()(const double& value) {
result = value;
}
// Special handling to avoid conflict when IntType is int64_t
template<typename T = IntType>
typename fl::enable_if<!fl::is_same<T, int64_t>::value, void>::type
operator()(const int64_t& value) {
result = static_cast<IntType>(value);
}
void operator()(const bool& value) {
result = static_cast<IntType>(value ? 1 : 0);
}
// Special handling for double to int conversion
template<typename T = IntType>
typename fl::enable_if<!fl::is_same<T, double>::value, void>::type
operator()(const double& value) {
// NEW INSTRUCTIONS: AUTO CONVERT FLOAT TO INT
result = static_cast<IntType>(value);
}
void operator()(const fl::string& str) {
// NEW INSTRUCTIONS: AUTO CONVERT STRING TO INT
// Try to parse the string as an integer using FastLED's StringFormatter
// Validate by checking if string contains only digits (and optional +/- sign)
bool isValidInt = true;
fl::size startPos = 0;
// Check for sign
if (str.length() > 0 && (str[0] == '+' || str[0] == '-')) {
startPos = 1;
}
// Check that all remaining characters are digits
for (fl::size i = startPos; i < str.length(); i++) {
if (!StringFormatter::isDigit(str[i])) {
isValidInt = false;
break;
}
}
// If it looks like a valid integer, try to parse it
if (isValidInt && str.length() > 0) {
int parsed = StringFormatter::parseInt(str.c_str(), str.length());
result = static_cast<IntType>(parsed);
}
}
template<typename T>
void operator()(const T&) {
// Do nothing for other types
}
};
// Specialization for int64_t to avoid template conflicts
template<>
struct IntConversionVisitor<int64_t> {
fl::optional<int64_t> result;
template<typename U>
void accept(const U& value) {
// Dispatch to the correct operator() overload
(*this)(value);
}
void operator()(const int64_t& value) {
result = value;
}
void operator()(const bool& value) {
result = value ? 1 : 0;
}
void operator()(const double& value) {
// NEW INSTRUCTIONS: AUTO CONVERT FLOAT TO INT
result = static_cast<int64_t>(value);
}
void operator()(const fl::string& str) {
// NEW INSTRUCTIONS: AUTO CONVERT STRING TO INT
// Try to parse the string as an integer using FastLED's StringFormatter
// Validate by checking if string contains only digits (and optional +/- sign)
bool isValidInt = true;
fl::size startPos = 0;
// Check for sign
if (str.length() > 0 && (str[0] == '+' || str[0] == '-')) {
startPos = 1;
}
// Check that all remaining characters are digits
for (fl::size i = startPos; i < str.length(); i++) {
if (!StringFormatter::isDigit(str[i])) {
isValidInt = false;
break;
}
}
// If it looks like a valid integer, try to parse it
if (isValidInt && str.length() > 0) {
int parsed = StringFormatter::parseInt(str.c_str(), str.length());
result = static_cast<int64_t>(parsed);
}
}
template<typename T>
void operator()(const T&) {
// Do nothing for other types
}
};
// Visitor for converting values to float
template<typename FloatType = double>
struct FloatConversionVisitor {
fl::optional<FloatType> result;
template<typename U>
void accept(const U& value) {
// Dispatch to the correct operator() overload
(*this)(value);
}
void operator()(const FloatType& value) {
result = value;
}
// Special handling to avoid conflict when FloatType is double
template<typename T = FloatType>
typename fl::enable_if<!fl::is_same<T, double>::value, void>::type
operator()(const double& value) {
result = static_cast<FloatType>(value);
}
// Special handling to avoid conflict when FloatType is float
template<typename T = FloatType>
typename fl::enable_if<!fl::is_same<T, float>::value, void>::type
operator()(const float& value) {
result = static_cast<FloatType>(value);
}
void operator()(const int64_t& value) {
// NEW INSTRUCTIONS: AUTO CONVERT INT TO FLOAT
result = static_cast<FloatType>(value);
}
void operator()(const bool& value) {
result = static_cast<FloatType>(value ? 1.0 : 0.0);
}
void operator()(const fl::string& str) {
// NEW INSTRUCTIONS: AUTO CONVERT STRING TO FLOAT
// Try to parse the string as a float using FastLED's StringFormatter
// Validate by checking if string contains valid float characters
bool isValidFloat = true;
bool hasDecimal = false;
fl::size startPos = 0;
// Check for sign
if (str.length() > 0 && (str[0] == '+' || str[0] == '-')) {
startPos = 1;
}
// Check that all remaining characters are valid for a float
for (fl::size i = startPos; i < str.length(); i++) {
char c = str[i];
if (c == '.') {
if (hasDecimal) {
// Multiple decimal points
isValidFloat = false;
break;
}
hasDecimal = true;
} else if (!StringFormatter::isDigit(c) && c != 'e' && c != 'E') {
isValidFloat = false;
break;
}
}
// If it looks like a valid float, try to parse it
if (isValidFloat && str.length() > 0) {
// For simple cases, we can use a more precise approach
// Check if it's a simple decimal number
bool isSimpleDecimal = true;
for (fl::size i = startPos; i < str.length(); i++) {
char c = str[i];
if (c != '.' && !StringFormatter::isDigit(c)) {
isSimpleDecimal = false;
break;
}
}
if (isSimpleDecimal) {
// For simple decimals, we can do a more direct conversion
float parsed = StringFormatter::parseFloat(str.c_str(), str.length());
result = static_cast<FloatType>(parsed);
} else {
// For complex floats (with exponents), use the standard approach
float parsed = StringFormatter::parseFloat(str.c_str(), str.length());
result = static_cast<FloatType>(parsed);
}
}
}
template<typename T>
void operator()(const T&) {
// Do nothing for other types
}
};
// Specialization for double to avoid template conflicts
template<>
struct FloatConversionVisitor<double> {
fl::optional<double> result;
template<typename U>
void accept(const U& value) {
// Dispatch to the correct operator() overload
(*this)(value);
}
void operator()(const double& value) {
result = value;
}
void operator()(const float& value) {
result = static_cast<double>(value);
}
void operator()(const int64_t& value) {
// NEW INSTRUCTIONS: AUTO CONVERT INT TO FLOAT
result = static_cast<double>(value);
}
void operator()(const bool& value) {
result = value ? 1.0 : 0.0;
}
void operator()(const fl::string& str) {
// NEW INSTRUCTIONS: AUTO CONVERT STRING TO FLOAT
// Try to parse the string as a float using FastLED's StringFormatter
// Validate by checking if string contains valid float characters
bool isValidFloat = true;
bool hasDecimal = false;
fl::size startPos = 0;
// Check for sign
if (str.length() > 0 && (str[0] == '+' || str[0] == '-')) {
startPos = 1;
}
// Check that all remaining characters are valid for a float
for (fl::size i = startPos; i < str.length(); i++) {
char c = str[i];
if (c == '.') {
if (hasDecimal) {
// Multiple decimal points
isValidFloat = false;
break;
}
hasDecimal = true;
} else if (!StringFormatter::isDigit(c) && c != 'e' && c != 'E') {
isValidFloat = false;
break;
}
}
// If it looks like a valid float, try to parse it
if (isValidFloat && str.length() > 0) {
// For simple cases, we can use a more precise approach
// Check if it's a simple decimal number
bool isSimpleDecimal = true;
for (fl::size i = startPos; i < str.length(); i++) {
char c = str[i];
if (c != '.' && !StringFormatter::isDigit(c)) {
isSimpleDecimal = false;
break;
}
}
if (isSimpleDecimal) {
// For simple decimals, we can do a more direct conversion
float parsed = StringFormatter::parseFloat(str.c_str(), str.length());
result = static_cast<double>(parsed);
} else {
// For complex floats (with exponents), use the standard approach
float parsed = StringFormatter::parseFloat(str.c_str(), str.length());
result = static_cast<double>(parsed);
}
}
}
template<typename T>
void operator()(const T&) {
// Do nothing for other types
}
};
// Visitor for converting values to string
struct StringConversionVisitor {
fl::optional<fl::string> result;
template<typename U>
void accept(const U& value) {
// Dispatch to the correct operator() overload
(*this)(value);
}
void operator()(const fl::string& value) {
result = value;
}
void operator()(const int64_t& value) {
// Convert integer to string
result = fl::to_string(value);
}
void operator()(const double& value) {
// Convert double to string with higher precision for JSON representation
result = fl::to_string(static_cast<float>(value), 6);
}
void operator()(const float& value) {
// Convert float to string with higher precision for JSON representation
result = fl::to_string(value, 6);
}
void operator()(const bool& value) {
// Convert bool to string
result = value ? "true" : "false";
}
void operator()(const fl::nullptr_t&) {
// Convert null to string
result = "null";
}
template<typename T>
void operator()(const T&) {
// Do nothing for other types (arrays, objects)
}
};
// The JSON node
struct JsonValue {
// Forward declarations for nested iterator classes
class iterator;
class const_iterator;
// Friend declarations
friend class Json;
// The variant holds exactly one of these alternatives
using variant_t = fl::Variant<
fl::nullptr_t, // null
bool, // true/false
int64_t, // integer
float, // floating-point (changed from double to float)
fl::string, // string
JsonArray, // array
JsonObject, // object
fl::vector<int16_t>, // audio data (specialized array of int16_t)
fl::vector<uint8_t>, // byte data (specialized array of uint8_t)
fl::vector<float> // float data (specialized array of float)
>;
typedef JsonValue::iterator iterator;
typedef JsonValue::const_iterator const_iterator;
variant_t data;
// Constructors
JsonValue() noexcept : data(nullptr) {}
JsonValue(fl::nullptr_t) noexcept : data(nullptr) {}
JsonValue(bool b) noexcept : data(b) {}
JsonValue(int64_t i) noexcept : data(i) {}
JsonValue(float f) noexcept : data(f) {} // Changed from double to float
JsonValue(const fl::string& s) : data(s) {
}
JsonValue(const JsonArray& a) : data(a) {
//FASTLED_WARN("Created JsonValue with array");
}
JsonValue(const JsonObject& o) : data(o) {
//FASTLED_WARN("Created JsonValue with object");
}
JsonValue(const fl::vector<int16_t>& audio) : data(audio) {
//FASTLED_WARN("Created JsonValue with audio data");
}
JsonValue(fl::vector<int16_t>&& audio) : data(fl::move(audio)) {
//FASTLED_WARN("Created JsonValue with moved audio data");
}
JsonValue(const fl::vector<uint8_t>& bytes) : data(bytes) {
//FASTLED_WARN("Created JsonValue with byte data");
}
JsonValue(fl::vector<uint8_t>&& bytes) : data(fl::move(bytes)) {
//FASTLED_WARN("Created JsonValue with moved byte data");
}
JsonValue(const fl::vector<float>& floats) : data(floats) {
//FASTLED_WARN("Created JsonValue with float data");
}
JsonValue(fl::vector<float>&& floats) : data(fl::move(floats)) {
//FASTLED_WARN("Created JsonValue with moved float data");
}
// Copy constructor
JsonValue(const JsonValue& other) : data(other.data) {}
JsonValue& operator=(const JsonValue& other) {
data = other.data;
return *this;
}
JsonValue& operator=(JsonValue&& other) {
data = fl::move(other.data);
return *this;
}
template<typename T>
typename fl::enable_if<!fl::is_same<typename fl::remove_cv<typename fl::remove_reference<T>::type>::type, JsonValue>::value, JsonValue&>::type
operator=(T&& value) {
data = fl::forward<T>(value);
return *this;
}
JsonValue& operator=(fl::nullptr_t) {
data = nullptr;
return *this;
}
JsonValue& operator=(bool b) {
data = b;
return *this;
}
JsonValue& operator=(int64_t i) {
data = i;
return *this;
}
JsonValue& operator=(double d) {
data = static_cast<float>(d);
return *this;
}
JsonValue& operator=(float f) {
data = f;
return *this;
}
JsonValue& operator=(fl::string s) {
data = fl::move(s);
return *this;
}
JsonValue& operator=(JsonArray a) {
data = fl::move(a);
return *this;
}
JsonValue& operator=(fl::vector<int16_t> audio) {
data = fl::move(audio);
return *this;
}
JsonValue& operator=(fl::vector<uint8_t> bytes) {
data = fl::move(bytes);
return *this;
}
JsonValue& operator=(fl::vector<float> floats) {
data = fl::move(floats);
return *this;
}
// Special constructor for char values
static fl::shared_ptr<JsonValue> from_char(char c) {
return fl::make_shared<JsonValue>(fl::string(1, c));
}
// Visitor pattern implementation
template<typename Visitor>
auto visit(Visitor&& visitor) -> decltype(visitor(fl::nullptr_t{})) {
return data.visit(fl::forward<Visitor>(visitor));
}
template<typename Visitor>
auto visit(Visitor&& visitor) const -> decltype(visitor(fl::nullptr_t{})) {
return data.visit(fl::forward<Visitor>(visitor));
}
// Type queries - using is<T>() instead of index() for fl::Variant
bool is_null() const noexcept {
//FASTLED_WARN("is_null called, tag=" << data.tag());
return data.is<fl::nullptr_t>();
}
bool is_bool() const noexcept {
//FASTLED_WARN("is_bool called, tag=" << data.tag());
return data.is<bool>();
}
bool is_int() const noexcept {
//FASTLED_WARN("is_int called, tag=" << data.tag());
return data.is<int64_t>() || data.is<bool>();
}
bool is_double() const noexcept {
//FASTLED_WARN("is_double called, tag=" << data.tag());
return data.is<float>();
}
bool is_float() const noexcept {
return data.is<float>();
}
bool is_string() const noexcept {
//FASTLED_WARN("is_string called, tag=" << data.tag());
return data.is<fl::string>();
}
// Visitor for array type checking
struct IsArrayVisitor {
bool result = false;
template<typename T>
void accept(const T& value) {
// Dispatch to the correct operator() overload
(*this)(value);
}
// JsonArray is an array
void operator()(const JsonArray&) {
result = true;
}
// Specialized array types ARE arrays
void operator()(const fl::vector<int16_t>&) {
result = true; // Audio data is still an array
}
void operator()(const fl::vector<uint8_t>&) {
result = true; // Byte data is still an array
}
void operator()(const fl::vector<float>&) {
result = true; // Float data is still an array
}
// Generic handler for all other types
template<typename T>
void operator()(const T&) {
result = false;
}
};
bool is_array() const noexcept {
//FASTLED_WARN("is_array called, tag=" << data.tag());
IsArrayVisitor visitor;
data.visit(visitor);
return visitor.result;
}
// Returns true only for JsonArray (not specialized array types)
bool is_generic_array() const noexcept {
return data.is<JsonArray>();
}
bool is_object() const noexcept {
//FASTLED_WARN("is_object called, tag=" << data.tag());
return data.is<JsonObject>();
}
bool is_audio() const noexcept {
//FASTLED_WARN("is_audio called, tag=" << data.tag());
return data.is<fl::vector<int16_t>>();
}
bool is_bytes() const noexcept {
//FASTLED_WARN("is_bytes called, tag=" << data.tag());
return data.is<fl::vector<uint8_t>>();
}
bool is_floats() const noexcept {
//FASTLED_WARN("is_floats called, tag=" << data.tag());
return data.is<fl::vector<float>>();
}
// Safe extractors (return optional values, not references)
fl::optional<bool> as_bool() {
auto ptr = data.ptr<bool>();
return ptr ? fl::optional<bool>(*ptr) : fl::nullopt;
}
fl::optional<int64_t> as_int() {
// Check if we have a valid value first
if (data.empty()) {
return fl::nullopt;
}
IntConversionVisitor<int64_t> visitor;
data.visit(visitor);
return visitor.result;
}
template<typename IntType>
fl::optional<IntType> as_int() {
// Check if we have a valid value first
if (data.empty()) {
return fl::nullopt;
}
IntConversionVisitor<IntType> visitor;
data.visit(visitor);
return visitor.result;
}
fl::optional<double> as_double() const {
// Check if we have a valid value first
if (data.empty()) {
return fl::nullopt;
}
FloatConversionVisitor<double> visitor;
data.visit(visitor);
return visitor.result;
}
fl::optional<float> as_float() {
return as_float<float>();
}
template<typename FloatType>
fl::optional<FloatType> as_float() {
// Check if we have a valid value first
if (data.empty()) {
return fl::nullopt;
}
FloatConversionVisitor<FloatType> visitor;
data.visit(visitor);
return visitor.result;
}
fl::optional<fl::string> as_string() {
// Check if we have a valid value first
if (data.empty()) {
return fl::nullopt;
}
StringConversionVisitor visitor;
data.visit(visitor);
return visitor.result;
}
fl::optional<JsonArray> as_array() {
auto ptr = data.ptr<JsonArray>();
if (ptr) return fl::optional<JsonArray>(*ptr);
// Handle specialized array types by converting them to regular JsonArray
if (data.is<fl::vector<int16_t>>()) {
auto audioPtr = data.ptr<fl::vector<int16_t>>();
JsonArray result;
for (const auto& item : *audioPtr) {
result.push_back(fl::make_shared<JsonValue>(static_cast<int64_t>(item)));
}
return fl::optional<JsonArray>(result);
}
if (data.is<fl::vector<uint8_t>>()) {
auto bytePtr = data.ptr<fl::vector<uint8_t>>();
JsonArray result;
for (const auto& item : *bytePtr) {
result.push_back(fl::make_shared<JsonValue>(static_cast<int64_t>(item)));
}
return fl::optional<JsonArray>(result);
}
if (data.is<fl::vector<float>>()) {
auto floatPtr = data.ptr<fl::vector<float>>();
JsonArray result;
for (const auto& item : *floatPtr) {
result.push_back(fl::make_shared<JsonValue>(item)); // Use float directly
}
return fl::optional<JsonArray>(result);
}
return fl::nullopt;
}
fl::optional<JsonObject> as_object() {
auto ptr = data.ptr<JsonObject>();
return ptr ? fl::optional<JsonObject>(*ptr) : fl::nullopt;
}
fl::optional<fl::vector<int16_t>> as_audio() {
auto ptr = data.ptr<fl::vector<int16_t>>();
return ptr ? fl::optional<fl::vector<int16_t>>(*ptr) : fl::nullopt;
}
fl::optional<fl::vector<uint8_t>> as_bytes() {
auto ptr = data.ptr<fl::vector<uint8_t>>();
return ptr ? fl::optional<fl::vector<uint8_t>>(*ptr) : fl::nullopt;
}
fl::optional<fl::vector<float>> as_floats() {
auto ptr = data.ptr<fl::vector<float>>();
return ptr ? fl::optional<fl::vector<float>>(*ptr) : fl::nullopt;
}
// Const overloads
fl::optional<bool> as_bool() const {
auto ptr = data.ptr<bool>();
return ptr ? fl::optional<bool>(*ptr) : fl::nullopt;
}
fl::optional<int64_t> as_int() const {
// Check if we have a valid value first
if (data.empty()) {
return fl::nullopt;
}
IntConversionVisitor<int64_t> visitor;
data.visit(visitor);
return visitor.result;
}
template<typename IntType>
fl::optional<IntType> as_int() const {
// Check if we have a valid value first
if (data.empty()) {
return fl::nullopt;
}
IntConversionVisitor<IntType> visitor;
data.visit(visitor);
return visitor.result;
}
fl::optional<float> as_float() const {
return as_float<float>();
}
template<typename FloatType>
fl::optional<FloatType> as_float() const {
// Check if we have a valid value first
if (data.empty()) {
return fl::nullopt;
}
FloatConversionVisitor<FloatType> visitor;
data.visit(visitor);
return visitor.result;
}
fl::optional<fl::string> as_string() const {
// Check if we have a valid value first
if (data.empty()) {
return fl::nullopt;
}
StringConversionVisitor visitor;
data.visit(visitor);
return visitor.result;
}
fl::optional<JsonArray> as_array() const {
auto ptr = data.ptr<JsonArray>();
if (ptr) return fl::optional<JsonArray>(*ptr);
// Handle specialized array types by converting them to regular JsonArray
if (data.is<fl::vector<int16_t>>()) {
auto audioPtr = data.ptr<fl::vector<int16_t>>();
JsonArray result;
for (const auto& item : *audioPtr) {
result.push_back(fl::make_shared<JsonValue>(static_cast<int64_t>(item)));
}
return fl::optional<JsonArray>(result);
}
if (data.is<fl::vector<uint8_t>>()) {
auto bytePtr = data.ptr<fl::vector<uint8_t>>();
JsonArray result;
for (const auto& item : *bytePtr) {
result.push_back(fl::make_shared<JsonValue>(static_cast<int64_t>(item)));
}
return fl::optional<JsonArray>(result);
}
if (data.is<fl::vector<float>>()) {
auto floatPtr = data.ptr<fl::vector<float>>();
JsonArray result;
for (const auto& item : *floatPtr) {
result.push_back(fl::make_shared<JsonValue>(item)); // Use float directly
}
return fl::optional<JsonArray>(result);
}
return fl::nullopt;
}
fl::optional<JsonObject> as_object() const {
auto ptr = data.ptr<JsonObject>();
return ptr ? fl::optional<JsonObject>(*ptr) : fl::nullopt;
}
fl::optional<fl::vector<int16_t>> as_audio() const {
auto ptr = data.ptr<fl::vector<int16_t>>();
return ptr ? fl::optional<fl::vector<int16_t>>(*ptr) : fl::nullopt;
}
fl::optional<fl::vector<uint8_t>> as_bytes() const {
auto ptr = data.ptr<fl::vector<uint8_t>>();
return ptr ? fl::optional<fl::vector<uint8_t>>(*ptr) : fl::nullopt;
}
fl::optional<fl::vector<float>> as_floats() const {
auto ptr = data.ptr<fl::vector<float>>();
return ptr ? fl::optional<fl::vector<float>>(*ptr) : fl::nullopt;
}
// Generic getter template method
template<typename T>
fl::optional<T> get() const {
auto ptr = data.ptr<T>();
return ptr ? fl::optional<T>(*ptr) : fl::nullopt;
}
template<typename T>
fl::optional<T> get() {
auto ptr = data.ptr<T>();
return ptr ? fl::optional<T>(*ptr) : fl::nullopt;
}
// Iterator support for objects and arrays
iterator begin() {
if (is_object()) {
auto ptr = data.ptr<JsonObject>();
return iterator(ptr->begin());
}
// Use temporary empty object to avoid static initialization conflicts with Teensy
return iterator(JsonObject().begin());
}
iterator end() {
if (is_object()) {
auto ptr = data.ptr<JsonObject>();
return iterator(ptr->end());
}
// Use temporary empty object to avoid static initialization conflicts with Teensy
return iterator(JsonObject().end());
}
const_iterator begin() const {
if (is_object()) {
auto ptr = data.ptr<const JsonObject>();
if (!ptr) return const_iterator::from_iterator(JsonObject().begin());
return const_iterator::from_iterator(ptr->begin());
}
// Use temporary empty object to avoid static initialization conflicts with Teensy
return const_iterator::from_iterator(JsonObject().begin());
}
const_iterator end() const {
if (is_object()) {
auto ptr = data.ptr<const JsonObject>();
if (!ptr) return const_iterator::from_iterator(JsonObject().end());
return const_iterator::from_iterator(ptr->end());
}
// Use temporary empty object to avoid static initialization conflicts with Teensy
return const_iterator::from_iterator(JsonObject().end());
}
// Iterator support for packed arrays
template<typename T>
class array_iterator {
private:
using variant_t = typename JsonValue::variant_t;
variant_t* m_variant;
size_t m_index;
// Helper to get the size of the array regardless of its type
size_t get_size() const {
if (!m_variant) return 0;
if (m_variant->is<JsonArray>()) {
auto ptr = m_variant->ptr<JsonArray>();
return ptr ? ptr->size() : 0;
}
if (m_variant->is<fl::vector<int16_t>>()) {
auto ptr = m_variant->ptr<fl::vector<int16_t>>();
return ptr ? ptr->size() : 0;
}
if (m_variant->is<fl::vector<uint8_t>>()) {
auto ptr = m_variant->ptr<fl::vector<uint8_t>>();
return ptr ? ptr->size() : 0;
}
if (m_variant->is<fl::vector<float>>()) {
auto ptr = m_variant->ptr<fl::vector<float>>();
return ptr ? ptr->size() : 0;
}
return 0;
}
// Helper to convert current element to target type T
ParseResult<T> get_value() const {
if (!m_variant || m_index >= get_size()) {
return ParseResult<T>(Error("Index out of bounds"));
}
if (m_variant->is<JsonArray>()) {
auto ptr = m_variant->ptr<JsonArray>();
if (ptr && m_index < ptr->size() && (*ptr)[m_index]) {
auto& val = *((*ptr)[m_index]);
// Try to convert to T using the JsonValue conversion methods
// Using FastLED type traits instead of std:: ones
if (fl::is_same<T, bool>::value) {
auto opt = val.as_bool();
if (opt) {
return ParseResult<T>(*opt);
} else {
return ParseResult<T>(Error("Cannot convert to bool"));
}
} else if (fl::is_integral<T>::value && fl::is_signed<T>::value) {
auto opt = val.template as_int<T>();
if (opt) {
return ParseResult<T>(*opt);
} else {
return ParseResult<T>(Error("Cannot convert to signed integer"));
}
} else if (fl::is_integral<T>::value && !fl::is_signed<T>::value) {
// For unsigned types, we check that it's integral but not signed
auto opt = val.template as_int<T>();
if (opt) {
return ParseResult<T>(*opt);
} else {
return ParseResult<T>(Error("Cannot convert to unsigned integer"));
}
} else if (fl::is_floating_point<T>::value) {
auto opt = val.template as_float<T>();
if (opt) {
return ParseResult<T>(*opt);
} else {
return ParseResult<T>(Error("Cannot convert to floating point"));
}
}
} else {
return ParseResult<T>(Error("Invalid array access"));
}
}
if (m_variant->is<fl::vector<int16_t>>()) {
auto ptr = m_variant->ptr<fl::vector<int16_t>>();
if (ptr && m_index < ptr->size()) {
return ParseResult<T>(static_cast<T>((*ptr)[m_index]));
} else {
return ParseResult<T>(Error("Index out of bounds in int16_t array"));
}
}
if (m_variant->is<fl::vector<uint8_t>>()) {
auto ptr = m_variant->ptr<fl::vector<uint8_t>>();
if (ptr && m_index < ptr->size()) {
return ParseResult<T>(static_cast<T>((*ptr)[m_index]));
} else {
return ParseResult<T>(Error("Index out of bounds in uint8_t array"));
}
}
if (m_variant->is<fl::vector<float>>()) {
auto ptr = m_variant->ptr<fl::vector<float>>();
if (ptr && m_index < ptr->size()) {
return ParseResult<T>(static_cast<T>((*ptr)[m_index]));
} else {
return ParseResult<T>(Error("Index out of bounds in float array"));
}
}
return ParseResult<T>(Error("Unknown array type"));
}
public:
array_iterator() : m_variant(nullptr), m_index(0) {}
array_iterator(variant_t* variant, size_t index) : m_variant(variant), m_index(index) {}
ParseResult<T> operator*() const {
return get_value();
}
array_iterator& operator++() {
++m_index;
return *this;
}
array_iterator operator++(int) {
array_iterator tmp(*this);
++(*this);
return tmp;
}
bool operator!=(const array_iterator& other) const {
return m_index != other.m_index || m_variant != other.m_variant;
}
bool operator==(const array_iterator& other) const {
return m_index == other.m_index && m_variant == other.m_variant;
}
};
// Begin/end methods for array iteration
template<typename T>
array_iterator<T> begin_array() {
if (is_array()) {
return array_iterator<T>(&data, 0);
}
return array_iterator<T>();
}
template<typename T>
array_iterator<T> end_array() {
if (is_array()) {
return array_iterator<T>(&data, size());
}
return array_iterator<T>();
}
template<typename T>
array_iterator<T> begin_array() const {
if (is_array()) {
return array_iterator<T>(const_cast<variant_t*>(&data), 0);
}
return array_iterator<T>();
}
template<typename T>
array_iterator<T> end_array() const {
if (is_array()) {
return array_iterator<T>(const_cast<variant_t*>(&data), size());
}
return array_iterator<T>();
}
// Free functions for range-based for loops
friend iterator begin(JsonValue& v) { return v.begin(); }
friend iterator end(JsonValue& v) { return v.end(); }
friend const_iterator begin(const JsonValue& v) { return v.begin(); }
friend const_iterator end(const JsonValue& v) { return v.end(); }
// Indexing for fluid chaining
JsonValue& operator[](size_t idx) {
if (!is_array()) data = JsonArray{};
// Handle regular JsonArray
if (data.is<JsonArray>()) {
auto ptr = data.ptr<JsonArray>();
if (!ptr) return get_null_value(); // Handle error case
auto &arr = *ptr;
if (idx >= arr.size()) {
// Resize array and fill with null values
for (size_t i = arr.size(); i <= idx; i++) {
arr.push_back(fl::make_shared<JsonValue>());
}
}
if (idx >= arr.size()) return get_null_value(); // Handle error case
return *arr[idx];
}
// For packed arrays, we need to convert them to regular arrays first
// This is needed for compatibility with existing code that expects JsonArray
if (data.is<fl::vector<int16_t>>() ||
data.is<fl::vector<uint8_t>>() ||
data.is<fl::vector<float>>()) {
// Convert to regular JsonArray
auto arr = as_array();
if (arr) {
data = fl::move(*arr);
auto ptr = data.ptr<JsonArray>();
if (!ptr) return get_null_value();
auto &jsonArr = *ptr;
if (idx >= jsonArr.size()) {
// Resize array and fill with null values
for (size_t i = jsonArr.size(); i <= idx; i++) {
jsonArr.push_back(fl::make_shared<JsonValue>());
}
}
if (idx >= jsonArr.size()) return get_null_value();
return *jsonArr[idx];
}
}
return get_null_value();
}
JsonValue& operator[](const fl::string &key) {
if (!is_object()) data = JsonObject{};
auto ptr = data.ptr<JsonObject>();
if (!ptr) return get_null_value(); // Handle error case
auto &obj = *ptr;
if (obj.find(key) == obj.end()) {
// Create a new entry if key doesn't exist
obj[key] = fl::make_shared<JsonValue>();
}
return *obj[key];
}
// Default-value operator (pipe)
template<typename T>
T operator|(const T& fallback) const {
DefaultValueVisitor<T> visitor(fallback);
data.visit(visitor);
return visitor.result ? *visitor.result : fallback;
}
// Explicit method for default values (alternative to operator|)
template<typename T>
T as_or(const T& fallback) const {
DefaultValueVisitor<T> visitor(fallback);
data.visit(visitor);
return visitor.result ? *visitor.result : fallback;
}
// Contains methods for checking existence
bool contains(size_t idx) const {
// Handle regular JsonArray first
if (data.is<JsonArray>()) {
auto ptr = data.ptr<JsonArray>();
return ptr && idx < ptr->size();
}
// Handle specialized array types
if (data.is<fl::vector<int16_t>>()) {
auto ptr = data.ptr<fl::vector<int16_t>>();
return ptr && idx < ptr->size();
}
if (data.is<fl::vector<uint8_t>>()) {
auto ptr = data.ptr<fl::vector<uint8_t>>();
return ptr && idx < ptr->size();
}
if (data.is<fl::vector<float>>()) {
auto ptr = data.ptr<fl::vector<float>>();
return ptr && idx < ptr->size();
}
return false;
}
bool contains(const fl::string &key) const {
if (!is_object()) return false;
auto ptr = data.ptr<JsonObject>();
return ptr && ptr->find(key) != ptr->end();
}
// Object iteration support (needed for screenmap conversion)
fl::vector<fl::string> keys() const {
fl::vector<fl::string> result;
if (is_object()) {
for (auto it = begin(); it != end(); ++it) {
auto keyValue = *it;
result.push_back(keyValue.first);
}
}
return result;
}
// Backward compatibility method
fl::vector<fl::string> getObjectKeys() const { return keys(); }
// Size methods
size_t size() const {
// Handle regular JsonArray first
if (data.is<JsonArray>()) {
auto ptr = data.ptr<JsonArray>();
return ptr ? ptr->size() : 0;
}
// Handle specialized array types
if (data.is<fl::vector<int16_t>>()) {
auto ptr = data.ptr<fl::vector<int16_t>>();
return ptr ? ptr->size() : 0;
}
if (data.is<fl::vector<uint8_t>>()) {
auto ptr = data.ptr<fl::vector<uint8_t>>();
return ptr ? ptr->size() : 0;
}
if (data.is<fl::vector<float>>()) {
auto ptr = data.ptr<fl::vector<float>>();
return ptr ? ptr->size() : 0;
}
if (is_object()) {
auto ptr = data.ptr<JsonObject>();
return ptr ? ptr->size() : 0;
}
return 0;
}
// Serialization
fl::string to_string() const;
// Visitor-based serialization helper
friend class SerializerVisitor;
// Parsing factory (FLArduinoJson implementation)
static fl::shared_ptr<JsonValue> parse(const fl::string &txt);
// Iterator support for objects
class iterator {
private:
JsonObject::iterator m_iter;
public:
iterator() = default;
iterator(JsonObject::iterator iter) : m_iter(iter) {}
// Getter for const iterator conversion
JsonObject::iterator get_iter() const { return m_iter; }
iterator& operator++() {
++m_iter;
return *this;
}
iterator operator++(int) {
iterator tmp(*this);
++(*this);
return tmp;
}
bool operator!=(const iterator& other) const {
return m_iter != other.m_iter;
}
bool operator==(const iterator& other) const {
return m_iter == other.m_iter;
}
struct KeyValue {
fl::string first;
JsonValue& second;
KeyValue(const fl::string& key, const fl::shared_ptr<JsonValue>& value_ptr)
: first(key), second(value_ptr ? *value_ptr : get_null_value()) {}
};
KeyValue operator*() const {
return KeyValue(m_iter->first, m_iter->second);
}
// Remove operator-> to avoid static variable issues
};
// Iterator for JSON objects (const version)
class const_iterator {
private:
JsonObject::const_iterator m_iter;
public:
const_iterator() = default;
const_iterator(JsonObject::const_iterator iter) : m_iter(iter) {}
// Factory method for conversion from iterator
static const_iterator from_object_iterator(const iterator& other) {
JsonObject::const_iterator const_iter(other.get_iter());
return const_iterator(const_iter);
}
// Factory method for conversion from Object::iterator
static const_iterator from_iterator(JsonObject::const_iterator iter) {
return const_iterator(iter);
}
const_iterator& operator++() {
++m_iter;
return *this;
}
const_iterator operator++(int) {
const_iterator tmp(*this);
++(*this);
return tmp;
}
bool operator!=(const const_iterator& other) const {
return m_iter != other.m_iter;
}
bool operator==(const const_iterator& other) const {
return m_iter == other.m_iter;
}
struct KeyValue {
fl::string first;
const JsonValue& second;
KeyValue(const fl::string& key, const fl::shared_ptr<JsonValue>& value_ptr)
: first(key), second(value_ptr ? *value_ptr : get_null_value()) {}
};
KeyValue operator*() const {
return KeyValue(m_iter->first, m_iter->second);
}
// Remove operator-> to avoid static variable issues
};
};
// Function to get a reference to a static null JsonValue
JsonValue& get_null_value();
// Main Json class that provides a more fluid and user-friendly interface
class Json {
private:
fl::shared_ptr<JsonValue> m_value;
public:
// Constructors
Json() : m_value() {} // Default initialize to nullptr
Json(fl::nullptr_t) : m_value(fl::make_shared<JsonValue>(nullptr)) {}
Json(bool b) : m_value(fl::make_shared<JsonValue>(b)) {}
Json(int i) : m_value(fl::make_shared<JsonValue>(static_cast<int64_t>(i))) {}
Json(int64_t i) : m_value(fl::make_shared<JsonValue>(i)) {}
Json(float f) : m_value(fl::make_shared<JsonValue>(f)) {} // Use float directly
Json(double d) : m_value(fl::make_shared<JsonValue>(static_cast<float>(d))) {} // Convert double to float
Json(const fl::string& s) : m_value(fl::make_shared<JsonValue>(s)) {}
Json(const char* s): Json(fl::string(s)) {}
Json(JsonArray a) : m_value(fl::make_shared<JsonValue>(fl::move(a))) {}
Json(JsonObject o) : m_value(fl::make_shared<JsonValue>(fl::move(o))) {}
// Constructor from shared_ptr<JsonValue>
Json(const fl::shared_ptr<JsonValue>& value) : m_value(value) {}
// Factory method to create a Json from a JsonValue
static Json from_value(const JsonValue& value) {
Json result;
result.m_value = fl::make_shared<JsonValue>(value);
return result;
}
// Constructor for fl::vector<float> - converts to JSON array
Json(const fl::vector<float>& vec) : m_value(fl::make_shared<JsonValue>(JsonArray{})) {
auto ptr = m_value->data.ptr<JsonArray>();
if (ptr) {
for (const auto& item : vec) {
ptr->push_back(fl::make_shared<JsonValue>(item)); // Use float directly
}
}
}
// Special constructor for char values
static Json from_char(char c) {
Json result;
auto value = fl::make_shared<JsonValue>(fl::string(1, c));
//FASTLED_WARN("Created JsonValue with string: " << value->is_string() << ", int: " << value->is_int());
result.m_value = value;
//FASTLED_WARN("Json has string: " << result.is_string() << ", int: " << result.is_int());
return result;
}
// Copy constructor
Json(const Json& other) : m_value(other.m_value) {}
// Assignment operator
Json& operator=(const Json& other) {
//FL_WARN("Json& operator=(const Json& other): " << (other.m_value ? other.m_value.get() : 0));
if (this != &other) {
m_value = other.m_value;
}
return *this;
}
Json& operator=(Json&& other) {
if (this != &other) {
m_value = fl::move(other.m_value);
}
return *this;
}
// Assignment operators for primitive types to avoid ambiguity
Json& operator=(bool value) {
m_value = fl::make_shared<JsonValue>(value);
return *this;
}
Json& operator=(int value) {
m_value = fl::make_shared<JsonValue>(static_cast<int64_t>(value));
return *this;
}
Json& operator=(float value) {
m_value = fl::make_shared<JsonValue>(value);
return *this;
}
Json& operator=(double value) {
m_value = fl::make_shared<JsonValue>(static_cast<float>(value));
return *this;
}
Json& operator=(const fl::string& value) {
m_value = fl::make_shared<JsonValue>(value);
return *this;
}
Json& operator=(const char* value) {
m_value = fl::make_shared<JsonValue>(fl::string(value));
return *this;
}
// Assignment operator for fl::vector<float>
Json& operator=(fl::vector<float> vec) {
m_value = fl::make_shared<JsonValue>(JsonArray{});
auto ptr = m_value->data.ptr<JsonArray>();
if (ptr) {
for (const auto& item : vec) {
ptr->push_back(fl::make_shared<JsonValue>(item)); // Use float directly
}
}
return *this;
}
// Type queries
bool is_null() const { return m_value ? m_value->is_null() : true; }
bool is_bool() const { return m_value && m_value->is_bool(); }
bool is_int() const { return m_value && (m_value->is_int() || m_value->is_bool()); }
bool is_float() const { return m_value && m_value->is_float(); }
bool is_double() const { return m_value && m_value->is_double(); }
bool is_string() const { return m_value && m_value->is_string(); }
bool is_array() const { return m_value && m_value->is_array(); }
bool is_generic_array() const { return m_value && m_value->is_generic_array(); }
bool is_object() const { return m_value && m_value->is_object(); }
bool is_audio() const { return m_value && m_value->is_audio(); }
bool is_bytes() const { return m_value && m_value->is_bytes(); }
bool is_floats() const { return m_value && m_value->is_floats(); }
// Safe extractors
fl::optional<bool> as_bool() const { return m_value ? m_value->as_bool() : fl::nullopt; }
fl::optional<int64_t> as_int() const {
if (!m_value) return fl::nullopt;
return m_value->as_int();
}
template<typename IntType>
fl::optional<IntType> as_int() const {
if (!m_value) return fl::nullopt;
return m_value->template as_int<IntType>();
}
fl::optional<float> as_float() const {
if (!m_value) return fl::nullopt;
return m_value->as_float();
}
fl::optional<double> as_double() const {
if (!m_value) return fl::nullopt;
return m_value->as_double();
}
template<typename FloatType>
fl::optional<FloatType> as_float() const {
if (!m_value) return fl::nullopt;
return m_value->template as_float<FloatType>();
}
fl::optional<fl::string> as_string() const {
if (!m_value) return fl::nullopt;
return m_value->as_string();
}
fl::optional<JsonArray> as_array() const { return m_value ? m_value->as_array() : fl::nullopt; }
fl::optional<JsonObject> as_object() const { return m_value ? m_value->as_object() : fl::nullopt; }
fl::optional<fl::vector<int16_t>> as_audio() const { return m_value ? m_value->as_audio() : fl::nullopt; }
fl::optional<fl::vector<uint8_t>> as_bytes() const { return m_value ? m_value->as_bytes() : fl::nullopt; }
fl::optional<fl::vector<float>> as_floats() const { return m_value ? m_value->as_floats() : fl::nullopt; }
// NEW ERGONOMIC API: try_as<T>() - Explicit optional handling
// Use when you need to explicitly handle conversion failure
template<typename T>
fl::optional<T> try_as() const {
if (!m_value) {
return fl::nullopt;
}
return as_impl<T>();
}
// BACKWARD COMPATIBILITY: Keep existing as<T>() that returns fl::optional<T>
// This maintains compatibility with existing code
template<typename T>
fl::optional<T> as() const {
return try_as<T>();
}
// NEW ERGONOMIC API: value<T>() - Direct conversion with sensible defaults
// Use when you want a value immediately with reasonable defaults on failure
template<typename T>
T value() const {
auto result = try_as<T>();
return result.has_value() ? *result : get_default_value<T>();
}
private:
// Integer types (excluding bool)
template<typename T>
typename fl::enable_if<fl::is_integral<T>::value && !fl::is_same<T, bool>::value, fl::optional<T>>::type
as_impl() const {
return m_value->template as_int<T>();
}
// Boolean type
template<typename T>
typename fl::enable_if<fl::is_same<T, bool>::value, fl::optional<T>>::type
as_impl() const {
return m_value->as_bool();
}
// Floating point types
template<typename T>
typename fl::enable_if<fl::is_floating_point<T>::value, fl::optional<T>>::type
as_impl() const {
// Force template call by explicitly using the templated method
return m_value->template as_float<T>();
}
// String type
template<typename T>
typename fl::enable_if<fl::is_same<T, fl::string>::value, fl::optional<T>>::type
as_impl() const {
return m_value->as_string();
}
// Array type
template<typename T>
typename fl::enable_if<fl::is_same<T, JsonArray>::value, fl::optional<T>>::type
as_impl() const {
return m_value->as_array();
}
// Object type
template<typename T>
typename fl::enable_if<fl::is_same<T, JsonObject>::value, fl::optional<T>>::type
as_impl() const {
return m_value->as_object();
}
// Specialized vector types
template<typename T>
typename fl::enable_if<fl::is_same<T, fl::vector<int16_t>>::value, fl::optional<T>>::type
as_impl() const {
return m_value->as_audio();
}
template<typename T>
typename fl::enable_if<fl::is_same<T, fl::vector<uint8_t>>::value, fl::optional<T>>::type
as_impl() const {
return m_value->as_bytes();
}
template<typename T>
typename fl::enable_if<fl::is_same<T, fl::vector<float>>::value, fl::optional<T>>::type
as_impl() const {
return m_value->as_floats();
}
// Helper methods for getting default values for each type
template<typename T>
typename fl::enable_if<fl::is_integral<T>::value && !fl::is_same<T, bool>::value, T>::type
get_default_value() const {
return T(0); // All integer types default to 0
}
template<typename T>
typename fl::enable_if<fl::is_same<T, bool>::value, T>::type
get_default_value() const {
return false; // Boolean defaults to false
}
template<typename T>
typename fl::enable_if<fl::is_floating_point<T>::value, T>::type
get_default_value() const {
return T(0.0); // Floating point types default to 0.0
}
template<typename T>
typename fl::enable_if<fl::is_same<T, fl::string>::value, T>::type
get_default_value() const {
return fl::string(); // String defaults to empty string
}
template<typename T>
typename fl::enable_if<fl::is_same<T, JsonArray>::value, T>::type
get_default_value() const {
return JsonArray(); // Array defaults to empty array
}
template<typename T>
typename fl::enable_if<fl::is_same<T, JsonObject>::value, T>::type
get_default_value() const {
return JsonObject(); // Object defaults to empty object
}
template<typename T>
typename fl::enable_if<fl::is_same<T, fl::vector<int16_t>>::value, T>::type
get_default_value() const {
return fl::vector<int16_t>(); // Audio vector defaults to empty
}
template<typename T>
typename fl::enable_if<fl::is_same<T, fl::vector<uint8_t>>::value, T>::type
get_default_value() const {
return fl::vector<uint8_t>(); // Bytes vector defaults to empty
}
template<typename T>
typename fl::enable_if<fl::is_same<T, fl::vector<float>>::value, T>::type
get_default_value() const {
return fl::vector<float>(); // Float vector defaults to empty
}
public:
// Iterator support for objects
JsonValue::iterator begin() {
if (!m_value) return JsonValue::iterator(JsonObject().begin());
return m_value->begin();
}
JsonValue::iterator end() {
if (!m_value) return JsonValue::iterator(JsonObject().end());
return m_value->end();
}
JsonValue::const_iterator begin() const {
if (!m_value) return JsonValue::const_iterator::from_iterator(JsonObject().begin());
return JsonValue::const_iterator::from_object_iterator(m_value->begin());
}
JsonValue::const_iterator end() const {
if (!m_value) return JsonValue::const_iterator::from_iterator(JsonObject().end());
return JsonValue::const_iterator::from_object_iterator(m_value->end());
}
// Iterator support for arrays with type conversion
template<typename T>
typename JsonValue::template array_iterator<T> begin_array() {
if (!m_value) return typename JsonValue::template array_iterator<T>();
return m_value->template begin_array<T>();
}
template<typename T>
typename JsonValue::template array_iterator<T> end_array() {
if (!m_value) return typename JsonValue::template array_iterator<T>();
return m_value->template end_array<T>();
}
template<typename T>
typename JsonValue::template array_iterator<T> begin_array() const {
if (!m_value) return typename JsonValue::template array_iterator<T>();
return m_value->template begin_array<T>();
}
template<typename T>
typename JsonValue::template array_iterator<T> end_array() const {
if (!m_value) return typename JsonValue::template array_iterator<T>();
return m_value->template end_array<T>();
}
// Free functions for range-based for loops
friend JsonValue::iterator begin(Json& j) { return j.begin(); }
friend JsonValue::iterator end(Json& j) { return j.end(); }
friend JsonValue::const_iterator begin(const Json& j) { return j.begin(); }
friend JsonValue::const_iterator end(const Json& j) { return j.end(); }
// Object iteration support (needed for screenmap conversion)
fl::vector<fl::string> keys() const {
fl::vector<fl::string> result;
if (m_value && m_value->is_object()) {
for (auto it = begin(); it != end(); ++it) {
auto keyValue = *it;
result.push_back(keyValue.first);
}
}
return result;
}
// Backward compatibility method
fl::vector<fl::string> getObjectKeys() const { return keys(); }
// Indexing for fluid chaining
Json operator[](size_t idx) {
if (!m_value) {
m_value = fl::make_shared<JsonValue>(JsonArray{});
}
// If we're indexing into a specialized array, convert it to regular JsonArray first
if (m_value->data.is<fl::vector<int16_t>>() ||
m_value->data.is<fl::vector<uint8_t>>() ||
m_value->data.is<fl::vector<float>>()) {
// Convert to regular JsonArray
auto arr = m_value->as_array();
if (arr) {
m_value = fl::make_shared<JsonValue>(fl::move(*arr));
}
}
// Get the shared_ptr directly from the JsonArray to maintain reference semantics
if (m_value->data.is<JsonArray>()) {
auto arr = m_value->as_array();
if (arr) {
// Ensure the array is large enough
if (idx >= arr->size()) {
for (size_t i = arr->size(); i <= idx; i++) {
arr->push_back(fl::make_shared<JsonValue>(nullptr));
}
}
return Json((*arr)[idx]);
}
}
return Json(nullptr);
}
const Json operator[](size_t idx) const {
if (!m_value) {
return Json(nullptr);
}
// Handle regular JsonArray
if (m_value->data.is<JsonArray>()) {
auto arr = m_value->as_array();
if (arr && idx < arr->size()) {
return Json((*arr)[idx]);
}
}
// For specialized arrays, we need to convert them to regular arrays first
// This is needed for compatibility with existing code that expects JsonArray
if (m_value->data.is<fl::vector<int16_t>>() ||
m_value->data.is<fl::vector<uint8_t>>() ||
m_value->data.is<fl::vector<float>>()) {
// Convert to regular JsonArray
auto arr = m_value->as_array();
if (arr && idx < arr->size()) {
return Json((*arr)[idx]);
}
}
return Json(nullptr);
}
Json operator[](const fl::string &key) {
if (!m_value || !m_value->is_object()) {
m_value = fl::make_shared<JsonValue>(JsonObject{});
}
// Get reference to the JsonValue
auto objPtr = m_value->data.ptr<JsonObject>();
if (objPtr) {
// If key doesn't exist, create a new JsonValue and insert it
if (objPtr->find(key) == objPtr->end()) {
(*objPtr)[key] = fl::make_shared<JsonValue>(nullptr);
}
// Return a new Json object that wraps the shared_ptr to the JsonValue
return Json((*objPtr)[key]);
}
// Should not happen if m_value is properly initialized as an object
//return *reinterpret_cast<Json*>(&get_null_value());
return Json(nullptr);
}
const Json operator[](const fl::string &key) const {
if (!m_value || !m_value->is_object()) {
return Json(nullptr);
}
auto obj = m_value->as_object();
if (obj && obj->find(key) != obj->end()) {
return Json((*obj)[key]);
}
return Json(nullptr);
}
// Contains methods for checking existence
bool contains(size_t idx) const {
return m_value && m_value->contains(idx);
}
bool contains(const fl::string &key) const {
return m_value && m_value->contains(key);
}
// Size method
size_t size() const {
return m_value ? m_value->size() : 0;
}
// Default-value operator (pipe)
template<typename T>
T operator|(const T& fallback) const {
if (!m_value) return fallback;
return (*m_value) | fallback;
}
// NEW ERGONOMIC API: as_or<T>(default) - Conversion with custom defaults
// Use when you want to specify your own default value
// This method uses try_as<T>() for proper string-to-number conversion
template<typename T>
T as_or(const T& fallback) const {
auto result = try_as<T>();
return result.has_value() ? *result : fallback;
}
// has_value method for compatibility
bool has_value() const {
return m_value && !m_value->is_null();
}
// Method to set the internal value (for JsonValue::to_string())
void set_value(const fl::shared_ptr<JsonValue>& value) {
m_value = value;
}
// Public method to access to_string_native for JsonValue::to_string()
fl::string to_string_native_public() const { return to_string_native(); }
// Serialization - now delegates to native implementation
fl::string to_string() const { return to_string_native(); }
// Native serialization (without external libraries)
fl::string to_string_native() const;
// Parsing factory method
static Json parse(const fl::string &txt) {
auto parsed = JsonValue::parse(txt);
if (parsed) {
Json result;
result.m_value = parsed;
return result;
}
return Json(nullptr);
}
// Convenience methods for creating arrays and objects
static Json array() {
return Json(JsonArray{});
}
static Json object() {
return Json(JsonObject{});
}
// Compatibility with existing API for array/object access
size_t getSize() const { return size(); }
// Set methods for building objects
void set(const fl::string& key, const Json& value) {
if (!m_value || !m_value->is_object()) {
m_value = fl::make_shared<JsonValue>(JsonObject{});
}
// Directly assign the value to the object without going through Json::operator[]
auto objPtr = m_value->data.ptr<JsonObject>();
if (objPtr) {
// Create or update the entry directly
(*objPtr)[key] = value.m_value;
}
}
void set(const fl::string& key, bool value) { set(key, Json(value)); }
void set(const fl::string& key, int value) { set(key, Json(value)); }
void set(const fl::string& key, int64_t value) { set(key, Json(value)); }
void set(const fl::string& key, float value) { set(key, Json(value)); }
void set(const fl::string& key, double value) { set(key, Json(value)); }
void set(const fl::string& key, const fl::string& value) { set(key, Json(value)); }
void set(const fl::string& key, const char* value) { set(key, Json(value)); }
template<typename T, typename = fl::enable_if_t<fl::is_same<T, char>::value>>
void set(const fl::string& key, T value) { set(key, Json(value)); }
// Array push_back methods
void push_back(const Json& value) {
if (!m_value || !m_value->is_array()) {
m_value = fl::make_shared<JsonValue>(JsonArray{});
}
// If we're pushing to a packed array, convert it to regular JsonArray first
if (m_value->is_array() &&
(m_value->data.is<fl::vector<int16_t>>() ||
m_value->data.is<fl::vector<uint8_t>>() ||
m_value->data.is<fl::vector<float>>())) {
// Convert to regular JsonArray
auto arr = m_value->as_array();
if (arr) {
m_value = fl::make_shared<JsonValue>(fl::move(*arr));
}
}
// For arrays, we need to manually handle the insertion since our indexing
// mechanism auto-creates elements
auto ptr = m_value->data.ptr<JsonArray>();
if (ptr) {
ptr->push_back(value.m_value);
}
}
// Create methods for compatibility
static Json createArray() { return Json::array(); }
static Json createObject() { return Json::object(); }
// Serialize method for compatibility
fl::string serialize() const { return to_string(); }
// Helper function to normalize JSON string (remove whitespace)
static fl::string normalizeJsonString(const char* jsonStr);
};
} // namespace fl