Schema-less variant of FlatBuffers for dynamic data structures where the schema is not known at compile time. FlexBuffers provides JSON-like flexibility with FlatBuffers efficiency, making it ideal for configuration files, dynamic APIs, and mixed-type data storage.
FlexBuffers stores self-describing data without requiring a predefined schema, similar to JSON but with better performance and smaller size.
namespace flexbuffers {
/**
* Type enumeration for FlexBuffer values
*/
enum Type : uint8_t {
FBT_NULL = 0, // Null value
FBT_INT = 1, // Signed integer (variable size)
FBT_UINT = 2, // Unsigned integer (variable size)
FBT_FLOAT = 3, // Floating point (variable precision)
// Container types
FBT_KEY = 4, // String key in maps
FBT_STRING = 5, // String value
FBT_INDIRECT_INT = 6, // Indirect integer reference
FBT_INDIRECT_UINT = 7, // Indirect unsigned integer reference
FBT_INDIRECT_FLOAT = 8, // Indirect float reference
// Vector types (homogeneous)
FBT_MAP = 9, // Key-value map (like JSON object)
FBT_VECTOR = 10, // Mixed-type vector (like JSON array)
FBT_VECTOR_INT = 11, // Vector of integers
FBT_VECTOR_UINT = 12, // Vector of unsigned integers
FBT_VECTOR_FLOAT = 13, // Vector of floats
FBT_VECTOR_KEY = 14, // Vector of keys (internal use)
FBT_VECTOR_STRING = 15, // Vector of strings
// Fixed-size vector types (more compact)
FBT_VECTOR_INT2 = 16, // Vector of 2 integers
FBT_VECTOR_UINT2 = 17, // Vector of 2 unsigned integers
FBT_VECTOR_FLOAT2 = 18, // Vector of 2 floats
FBT_VECTOR_INT3 = 19, // Vector of 3 integers
FBT_VECTOR_UINT3 = 20, // Vector of 3 unsigned integers
FBT_VECTOR_FLOAT3 = 21, // Vector of 3 floats
FBT_VECTOR_INT4 = 22, // Vector of 4 integers
FBT_VECTOR_UINT4 = 23, // Vector of 4 unsigned integers
FBT_VECTOR_FLOAT4 = 24, // Vector of 4 floats
FBT_BLOB = 25, // Binary blob
FBT_BOOL = 26, // Boolean value
FBT_VECTOR_BOOL = 36 // Vector of booleans
};
/**
* Bit width enumeration for variable-size encoding
*/
enum BitWidth : uint8_t {
BIT_WIDTH_8 = 0, // 8-bit values
BIT_WIDTH_16 = 1, // 16-bit values
BIT_WIDTH_32 = 2, // 32-bit values
BIT_WIDTH_64 = 3 // 64-bit values
};
/**
* Utility functions for type checking
*/
inline bool IsInline(Type t) { return t <= FBT_FLOAT || t == FBT_BOOL; }
inline bool IsTypedVector(Type t) { return (t >= FBT_VECTOR_INT && t <= FBT_VECTOR_STRING) || t == FBT_VECTOR_BOOL; }
inline bool IsFixedTypedVector(Type t) { return t >= FBT_VECTOR_INT2 && t <= FBT_VECTOR_FLOAT4; }
inline bool IsAVector(Type t) { return t >= FBT_VECTOR && t <= FBT_VECTOR_BOOL; }
} // namespace flexbuffersBuilder for constructing FlexBuffer data with a fluent API supporting dynamic typing.
namespace flexbuffers {
class Builder {
public:
/**
* Create FlexBuffer builder
* @param initial_size Initial buffer size (default: 256)
* @param dedup_strings Whether to deduplicate strings (default: true)
* @param dedup_keys Whether to deduplicate keys (default: true)
* @param dedup_key_vectors Whether to deduplicate key vectors (default: true)
*/
explicit Builder(
size_t initial_size = 256,
bool dedup_strings = true,
bool dedup_keys = true,
bool dedup_key_vectors = true
);
/// Scalar value methods
/**
* Add null value
*/
void Null();
/**
* Add signed integer
* @param i Integer value
*/
void Int(int64_t i);
/**
* Add unsigned integer
* @param u Unsigned integer value
*/
void UInt(uint64_t u);
/**
* Add floating point value
* @param f Float value
*/
void Float(double f);
/**
* Add boolean value
* @param b Boolean value
*/
void Bool(bool b);
/**
* Add string value
* @param str String value
*/
void String(const char *str);
void String(const std::string &str);
void String(const char *str, size_t len);
/**
* Add binary blob
* @param data Pointer to binary data
* @param len Length of data
*/
void Blob(const void *data, size_t len);
void Blob(const std::vector<uint8_t> &v);
/// Container methods
/**
* Start building a vector (JSON array equivalent)
*/
size_t StartVector();
/**
* Start building a vector with known size
* @param len Expected number of elements
*/
size_t StartVector(size_t len);
/**
* End vector construction
* @param start Return value from StartVector
* @param typed Whether to create typed vector if possible
* @param fixed Whether to create fixed-size vector if possible
*/
void EndVector(size_t start, bool typed = true, bool fixed = true);
/**
* Start building a map (JSON object equivalent)
*/
size_t StartMap();
/**
* Start building a map with known size
* @param len Expected number of key-value pairs
*/
size_t StartMap(size_t len);
/**
* End map construction
* @param start Return value from StartMap
*/
void EndMap(size_t start);
/**
* Add key for next value in map
* @param key Key string
*/
void Key(const char *key);
void Key(const std::string &key);
/// Typed vector methods (more efficient)
/**
* Create vector of integers
* @param v Vector of integers
*/
void Vector(const std::vector<int64_t> &v);
/**
* Create vector of unsigned integers
* @param v Vector of unsigned integers
*/
void Vector(const std::vector<uint64_t> &v);
/**
* Create vector of floats
* @param v Vector of floats
*/
void Vector(const std::vector<double> &v);
/**
* Create vector of strings
* @param v Vector of strings
*/
void Vector(const std::vector<std::string> &v);
/// Fixed-size vector methods (most efficient)
/**
* Create fixed-size vector from array
* @param v Array of values
* @param len Number of elements (2, 3, or 4)
*/
void FixedTypedVector(const int64_t *v, size_t len);
void FixedTypedVector(const uint64_t *v, size_t len);
void FixedTypedVector(const double *v, size_t len);
/// Buffer management
/**
* Finish building and get buffer
* @return Vector containing FlexBuffer data
*/
std::vector<uint8_t> GetBuffer();
/**
* Clear builder for reuse
*/
void Clear();
/**
* Get current buffer size
* @return Size in bytes
*/
size_t GetSize() const;
};
} // namespace flexbuffersBuilder Usage Example:
#include "flatbuffers/flexbuffers.h"
// Create JSON-like data structure
void CreateFlexBuffer() {
flexbuffers::Builder fbb;
// Create nested structure equivalent to:
// {
// "name": "John",
// "age": 30,
// "scores": [95, 87, 92],
// "active": true,
// "address": {
// "street": "123 Main St",
// "city": "Anytown"
// }
// }
fbb.Map([&]() {
fbb.Key("name");
fbb.String("John");
fbb.Key("age");
fbb.Int(30);
fbb.Key("scores");
fbb.Vector([&]() {
fbb.Int(95);
fbb.Int(87);
fbb.Int(92);
});
fbb.Key("active");
fbb.Bool(true);
fbb.Key("address");
fbb.Map([&]() {
fbb.Key("street");
fbb.String("123 Main St");
fbb.Key("city");
fbb.String("Anytown");
});
});
// Get buffer
auto buffer = fbb.GetBuffer();
// Buffer now contains the FlexBuffer data
std::cout << "FlexBuffer size: " << buffer.size() << " bytes" << std::endl;
}Reference class for reading FlexBuffer data with type-safe access methods.
namespace flexbuffers {
class Reference {
public:
/**
* Create reference from buffer
* @param buf Buffer containing FlexBuffer data
* @param offset Offset to reference in buffer
*/
Reference(const uint8_t *buf, size_t offset);
/**
* Create reference from vector
* @param buffer Vector containing FlexBuffer data
* @param offset Offset to reference in buffer
*/
Reference(const std::vector<uint8_t> &buffer, size_t offset = 0);
/// Type checking
/**
* Get the type of this reference
* @return Type enum value
*/
Type GetType() const;
/**
* Check if reference is null
* @return True if null
*/
bool IsNull() const;
/**
* Check if reference is boolean
* @return True if boolean
*/
bool IsBool() const;
/**
* Check if reference is numeric (int, uint, float)
* @return True if numeric
*/
bool IsNumeric() const;
/**
* Check if reference is integer (signed or unsigned)
* @return True if integer
*/
bool IsIntOrUint() const;
/**
* Check if reference is float
* @return True if float
*/
bool IsFloat() const;
/**
* Check if reference is string
* @return True if string
*/
bool IsString() const;
/**
* Check if reference is vector (any type)
* @return True if vector
*/
bool IsVector() const;
/**
* Check if reference is typed vector
* @return True if typed vector
*/
bool IsTypedVector() const;
/**
* Check if reference is fixed-size vector
* @return True if fixed-size vector
*/
bool IsFixedTypedVector() const;
/**
* Check if reference is map
* @return True if map
*/
bool IsMap() const;
/**
* Check if reference is blob
* @return True if blob
*/
bool IsBlob() const;
/// Value access
/**
* Get value as boolean
* @return Boolean value (false if not boolean)
*/
bool AsBool() const;
/**
* Get value as signed integer
* @return Integer value (0 if not integer)
*/
int64_t AsInt64() const;
int32_t AsInt32() const;
int16_t AsInt16() const;
int8_t AsInt8() const;
/**
* Get value as unsigned integer
* @return Unsigned integer value (0 if not integer)
*/
uint64_t AsUInt64() const;
uint32_t AsUInt32() const;
uint16_t AsUInt16() const;
uint8_t AsUInt8() const;
/**
* Get value as floating point
* @return Float value (0.0 if not float)
*/
double AsDouble() const;
float AsFloat() const;
/**
* Get value as string
* @return String value (empty if not string)
*/
const char *AsString() const;
std::string AsString() const;
/**
* Get value as blob
* @return Blob data (null if not blob)
*/
const uint8_t *AsBlob() const;
/// Container access
/**
* Get vector/map size
* @return Number of elements (0 if not container)
*/
size_t size() const;
/**
* Check if container is empty
* @return True if empty or not a container
*/
bool empty() const;
/**
* Access vector element by index
* @param i Element index
* @return Reference to element
*/
Reference operator[](size_t i) const;
/**
* Access map element by key
* @param key Key string
* @return Reference to element
*/
Reference operator[](const char *key) const;
Reference operator[](const std::string &key) const;
/// Iteration support
/**
* Get keys for map iteration
* @return Vector reference containing keys
*/
Reference Keys() const;
/**
* Get values for map iteration
* @return Vector reference containing values
*/
Reference Values() const;
/**
* Iterator support for C++11 range-based for loops
*/
class iterator {
// Implementation details
public:
Reference operator*() const;
iterator& operator++();
bool operator!=(const iterator& other) const;
};
iterator begin() const;
iterator end() const;
/// Conversion and output
/**
* Convert to string representation (JSON-like)
* @return String representation
*/
std::string ToString() const;
/**
* Convert to pretty-printed string
* @param indent Indentation string
* @return Formatted string
*/
std::string ToPrettyString(const std::string &indent = " ") const;
};
/**
* Get root reference from FlexBuffer
* @param buffer FlexBuffer data
* @return Root reference
*/
Reference GetRoot(const std::vector<uint8_t> &buffer);
Reference GetRoot(const uint8_t *buffer, size_t size);
} // namespace flexbuffersReference Usage Example:
#include "flatbuffers/flexbuffers.h"
#include <iostream>
void ReadFlexBuffer(const std::vector<uint8_t> &buffer) {
// Get root reference
auto root = flexbuffers::GetRoot(buffer);
// Check if root is a map
if (root.IsMap()) {
std::cout << "Root is a map with " << root.size() << " entries" << std::endl;
// Access values by key
auto name = root["name"];
if (name.IsString()) {
std::cout << "Name: " << name.AsString() << std::endl;
}
auto age = root["age"];
if (age.IsIntOrUint()) {
std::cout << "Age: " << age.AsInt64() << std::endl;
}
auto scores = root["scores"];
if (scores.IsVector()) {
std::cout << "Scores: ";
for (size_t i = 0; i < scores.size(); i++) {
std::cout << scores[i].AsInt64() << " ";
}
std::cout << std::endl;
}
auto active = root["active"];
if (active.IsBool()) {
std::cout << "Active: " << (active.AsBool() ? "true" : "false") << std::endl;
}
// Access nested map
auto address = root["address"];
if (address.IsMap()) {
std::cout << "Address: " << address["street"].AsString()
<< ", " << address["city"].AsString() << std::endl;
}
}
// Print entire structure
std::cout << "JSON representation:" << std::endl;
std::cout << root.ToPrettyString() << std::endl;
}FlexBuffers implementations adapted for different programming languages while maintaining API consistency.
JavaScript/TypeScript:
import { flexbuffers } from 'flatbuffers';
// Builder usage
const builder = new flexbuffers.Builder();
builder.startMap();
builder.key('name');
builder.string('John');
builder.key('age');
builder.int(30);
builder.endMap();
const buffer = builder.buffer;
// Reader usage
const root = flexbuffers.getRoot(buffer);
console.log(root.get('name').stringValue());
console.log(root.get('age').intValue());Python:
import flatbuffers.flexbuffers as flexbuffers
# Builder usage
builder = flexbuffers.Builder()
with builder.Map():
builder.Key("name")
builder.String("John")
builder.Key("age")
builder.Int(30)
buffer = builder.Output()
# Reader usage
root = flexbuffers.GetRoot(buffer)
print(root["name"].AsString())
print(root["age"].AsInt())Java:
import com.google.flatbuffers.flexbuffers.*;
// Builder usage
FlexBuffersBuilder builder = new FlexBuffersBuilder();
int map = builder.startMap();
builder.key("name");
builder.string("John");
builder.key("age");
builder.int(30);
builder.endMap(map);
ByteBuffer buffer = builder.finish();
// Reader usage
Reference root = FlexBuffers.getRoot(buffer);
System.out.println(root.get("name").asString());
System.out.println(root.get("age").asLong());Common patterns for using FlexBuffers in real-world applications.
// Configuration file handling
class ConfigManager {
private:
flexbuffers::Reference config_;
std::vector<uint8_t> buffer_;
public:
bool LoadFromFile(const std::string &filename) {
std::ifstream file(filename, std::ios::binary);
if (!file) return false;
buffer_.assign(std::istreambuf_iterator<char>(file),
std::istreambuf_iterator<char>());
config_ = flexbuffers::GetRoot(buffer_);
return config_.IsMap();
}
template<typename T>
T GetValue(const std::string &key, const T &default_value) const {
auto ref = config_[key];
if constexpr (std::is_same_v<T, std::string>) {
return ref.IsString() ? ref.AsString() : default_value;
} else if constexpr (std::is_integral_v<T>) {
return ref.IsIntOrUint() ? static_cast<T>(ref.AsInt64()) : default_value;
} else if constexpr (std::is_floating_point_v<T>) {
return ref.IsFloat() ? static_cast<T>(ref.AsDouble()) : default_value;
} else if constexpr (std::is_same_v<T, bool>) {
return ref.IsBool() ? ref.AsBool() : default_value;
}
return default_value;
}
std::vector<std::string> GetStringArray(const std::string &key) const {
std::vector<std::string> result;
auto ref = config_[key];
if (ref.IsVector()) {
for (size_t i = 0; i < ref.size(); i++) {
if (ref[i].IsString()) {
result.push_back(ref[i].AsString());
}
}
}
return result;
}
};
// Dynamic API response handling
class APIResponse {
private:
flexbuffers::Reference root_;
public:
APIResponse(const std::vector<uint8_t> &data)
: root_(flexbuffers::GetRoot(data)) {}
bool IsSuccess() const {
auto status = root_["status"];
return status.IsString() && status.AsString() == "success";
}
std::string GetErrorMessage() const {
auto error = root_["error"];
return error.IsString() ? error.AsString() : "Unknown error";
}
template<typename T>
std::optional<T> GetData(const std::string &field) const {
auto data = root_["data"];
if (!data.IsMap()) return std::nullopt;
auto field_ref = data[field];
if constexpr (std::is_same_v<T, std::string>) {
return field_ref.IsString() ?
std::make_optional(field_ref.AsString()) : std::nullopt;
} else if constexpr (std::is_integral_v<T>) {
return field_ref.IsIntOrUint() ?
std::make_optional(static_cast<T>(field_ref.AsInt64())) : std::nullopt;
}
return std::nullopt;
}
};
// Schema evolution example
void HandleVersionedData(const std::vector<uint8_t> &buffer) {
auto root = flexbuffers::GetRoot(buffer);
// Check version for backward compatibility
auto version = root["version"];
int ver = version.IsIntOrUint() ? version.AsInt32() : 1;
switch (ver) {
case 1:
// Handle v1 format
ProcessV1Data(root);
break;
case 2:
// Handle v2 format with new fields
ProcessV2Data(root);
break;
default:
// Unknown version - try to handle gracefully
ProcessGenericData(root);
break;
}
}Complete FlexBuffers Example:
#include "flatbuffers/flexbuffers.h"
#include <iostream>
#include <fstream>
int main() {
// Create complex nested structure
flexbuffers::Builder fbb;
fbb.Map([&]() {
fbb.Key("user_id");
fbb.Int(12345);
fbb.Key("username");
fbb.String("johndoe");
fbb.Key("profile");
fbb.Map([&]() {
fbb.Key("display_name");
fbb.String("John Doe");
fbb.Key("email");
fbb.String("john@example.com");
fbb.Key("preferences");
fbb.Map([&]() {
fbb.Key("theme");
fbb.String("dark");
fbb.Key("notifications");
fbb.Bool(true);
fbb.Key("languages");
fbb.Vector([&]() {
fbb.String("en");
fbb.String("es");
fbb.String("fr");
});
});
});
fbb.Key("scores");
fbb.Vector(std::vector<int64_t>{95, 87, 92, 88, 96});
fbb.Key("location");
fbb.FixedTypedVector(new double[2]{37.7749, -122.4194}, 2); // lat, lng
});
// Get buffer
auto buffer = fbb.GetBuffer();
// Save to file
std::ofstream file("user_data.flexbuf", std::ios::binary);
file.write(reinterpret_cast<const char*>(buffer.data()), buffer.size());
file.close();
// Read and parse
auto root = flexbuffers::GetRoot(buffer);
std::cout << "User ID: " << root["user_id"].AsInt64() << std::endl;
std::cout << "Username: " << root["username"].AsString() << std::endl;
std::cout << "Display Name: " << root["profile"]["display_name"].AsString() << std::endl;
std::cout << "Theme: " << root["profile"]["preferences"]["theme"].AsString() << std::endl;
auto languages = root["profile"]["preferences"]["languages"];
std::cout << "Languages: ";
for (size_t i = 0; i < languages.size(); i++) {
std::cout << languages[i].AsString() << " ";
}
std::cout << std::endl;
auto scores = root["scores"];
std::cout << "Scores: ";
for (size_t i = 0; i < scores.size(); i++) {
std::cout << scores[i].AsInt64() << " ";
}
std::cout << std::endl;
auto location = root["location"];
std::cout << "Location: " << location[0].AsDouble() << ", " << location[1].AsDouble() << std::endl;
// Pretty print entire structure
std::cout << "\nComplete structure:" << std::endl;
std::cout << root.ToPrettyString() << std::endl;
return 0;
}