| FlexBuffers {#flexbuffers} | |
| ========== | |
| FlatBuffers was designed around schemas, because when you want maximum | |
| performance and data consistency, strong typing is helpful. | |
| There are however times when you want to store data that doesn't fit a | |
| schema, because you can't know ahead of time what all needs to be stored. | |
| For this, FlatBuffers has a dedicated format, called FlexBuffers. | |
| This is a binary format that can be used in conjunction | |
| with FlatBuffers (by storing a part of a buffer in FlexBuffers | |
| format), or also as its own independent serialization format. | |
| While it loses the strong typing, you retain the most unique advantage | |
| FlatBuffers has over other serialization formats (schema-based or not): | |
| FlexBuffers can also be accessed without parsing / copying / object allocation. | |
| This is a huge win in efficiency / memory friendly-ness, and allows unique | |
| use cases such as mmap-ing large amounts of free-form data. | |
| FlexBuffers' design and implementation allows for a very compact encoding, | |
| combining automatic pooling of strings with automatic sizing of containers to | |
| their smallest possible representation (8/16/32/64 bits). Many values and | |
| offsets can be encoded in just 8 bits. While a schema-less representation is | |
| usually more bulky because of the need to be self-descriptive, FlexBuffers | |
| generates smaller binaries for many cases than regular FlatBuffers. | |
| FlexBuffers is still slower than regular FlatBuffers though, so we recommend to | |
| only use it if you need it. | |
| # Usage in C++ | |
| Include the header `flexbuffers.h`, which in turn depends on `flatbuffers.h` | |
| and `util.h`. | |
| To create a buffer: | |
| ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~{.cpp} | |
| flexbuffers::Builder fbb; | |
| fbb.Int(13); | |
| fbb.Finish(); | |
| ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ | |
| You create any value, followed by `Finish`. Unlike FlatBuffers which requires | |
| the root value to be a table, here any value can be the root, including a lonely | |
| int value. | |
| You can now access the `std::vector<uint8_t>` that contains the encoded value | |
| as `fbb.GetBuffer()`. Write it, send it, or store it in a parent FlatBuffer. In | |
| this case, the buffer is just 3 bytes in size. | |
| To read this value back, you could just say: | |
| ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~{.cpp} | |
| auto root = flexbuffers::GetRoot(my_buffer); | |
| int64_t i = root.AsInt64(); | |
| ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ | |
| FlexBuffers stores ints only as big as needed, so it doesn't differentiate | |
| between different sizes of ints. You can ask for the 64 bit version, | |
| regardless of what you put in. In fact, since you demand to read the root | |
| as an int, if you supply a buffer that actually contains a float, or a | |
| string with numbers in it, it will convert it for you on the fly as well, | |
| or return 0 if it can't. If instead you actually want to know what is inside | |
| the buffer before you access it, you can call `root.GetType()` or `root.IsInt()` | |
| etc. | |
| Here's a slightly more complex value you could write instead of `fbb.Int` above: | |
| ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~{.cpp} | |
| fbb.Map([&]() { | |
| fbb.Vector("vec", [&]() { | |
| fbb.Int(-100); | |
| fbb.String("Fred"); | |
| fbb.IndirectFloat(4.0f); | |
| }); | |
| fbb.UInt("foo", 100); | |
| }); | |
| ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ | |
| This stores the equivalent of the JSON value | |
| `{ vec: [ -100, "Fred", 4.0 ], foo: 100 }`. The root is a dictionary that has | |
| just two key-value pairs, with keys `vec` and `foo`. Unlike FlatBuffers, it | |
| actually has to store these keys in the buffer (which it does only once if | |
| you store multiple such objects, by pooling key values), but also unlike | |
| FlatBuffers it has no restriction on the keys (fields) that you use. | |
| The map constructor uses a C++11 Lambda to group its children, but you can | |
| also use more conventional start/end calls if you prefer. | |
| The first value in the map is a vector. You'll notice that unlike FlatBuffers, | |
| you can use mixed types. There is also a `TypedVector` variant that only | |
| allows a single type, and uses a bit less memory. | |
| `IndirectFloat` is an interesting feature that allows you to store values | |
| by offset rather than inline. Though that doesn't make any visible change | |
| to the user, the consequence is that large values (especially doubles or | |
| 64 bit ints) that occur more than once can be shared (see ReuseValue). | |
| Another use case is inside of vectors, where the largest element makes | |
| up the size of all elements (e.g. a single double forces all elements to | |
| 64bit), so storing a lot of small integers together with a double is more efficient if the double is indirect. | |
| Accessing it: | |
| ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~{.cpp} | |
| auto map = flexbuffers::GetRoot(my_buffer).AsMap(); | |
| map.size(); // 2 | |
| auto vec = map["vec"].AsVector(); | |
| vec.size(); // 3 | |
| vec[0].AsInt64(); // -100; | |
| vec[1].AsString().c_str(); // "Fred"; | |
| vec[1].AsInt64(); // 0 (Number parsing failed). | |
| vec[2].AsDouble(); // 4.0 | |
| vec[2].AsString().IsTheEmptyString(); // true (Wrong Type). | |
| vec[2].AsString().c_str(); // "" (This still works though). | |
| vec[2].ToString().c_str(); // "4" (Or have it converted). | |
| map["foo"].AsUInt8(); // 100 | |
| map["unknown"].IsNull(); // true | |
| ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ | |
| # Usage in Java | |
| Java implementation follows the C++ one, closely. | |
| For creating the equivalent of the same JSON `{ vec: [ -100, "Fred", 4.0 ], foo: 100 }`, | |
| one could use the following code: | |
| ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~{.java} | |
| FlexBuffersBuilder builder = new FlexBuffersBuilder(ByteBuffer.allocate(512), | |
| FlexBuffersBuilder.BUILDER_FLAG_SHARE_KEYS_AND_STRINGS); | |
| int smap = builder.startMap(); | |
| int svec = builder.startVector(); | |
| builder.putInt(-100); | |
| builder.putString("Fred"); | |
| builder.putFloat(4.0); | |
| builder.endVector("vec", svec, false, false); | |
| builder.putInt("foo", 100); | |
| builder.endMap(null, smap); | |
| ByteBuffer bb = builder.finish(); | |
| ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ | |
| Similarly, to read the data, just: | |
| ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~{.java} | |
| FlexBuffers.Map map = FlexBuffers.getRoot(bb).asMap(); | |
| map.size(); // 2 | |
| FlexBuffers.Vector vec = map.get("vec").asVector(); | |
| vec.size(); // 3 | |
| vec.get(0).asLong(); // -100; | |
| vec.get(1).asString(); // "Fred"; | |
| vec.get(1).asLong(); // 0 (Number parsing failed). | |
| vec.get(2).asFloat(); // 4.0 | |
| vec.get(2).asString().isEmpty(); // true (Wrong Type). | |
| vec.get(2).asString(); // "" (This still works though). | |
| vec.get(2).toString(); // "4.0" (Or have it converted). | |
| map.get("foo").asUInt(); // 100 | |
| map.get("unknown").isNull(); // true | |
| ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ | |
| # Binary encoding | |
| A description of how FlexBuffers are encoded is in the | |
| [internals](internals.md) document. | |
| # Nesting inside a FlatBuffer | |
| You can mark a field as containing a FlexBuffer, e.g. | |
| a:[ubyte] (flexbuffer); | |
| A special accessor will be generated that allows you to access the root value | |
| directly, e.g. `a_flexbuffer_root().AsInt64()`. | |
| # Efficiency tips | |
| * Vectors generally are a lot more efficient than maps, so prefer them over maps | |
| when possible for small objects. Instead of a map with keys `x`, `y` and `z`, | |
| use a vector. Better yet, use a typed vector. Or even better, use a fixed | |
| size typed vector. | |
| * Maps are backwards compatible with vectors, and can be iterated as such. | |
| You can iterate either just the values (`map.Values()`), or in parallel with | |
| the keys vector (`map.Keys()`). If you intend | |
| to access most or all elements, this is faster than looking up each element | |
| by key, since that involves a binary search of the key vector. | |
| * When possible, don't mix values that require a big bit width (such as double) | |
| in a large vector of smaller values, since all elements will take on this | |
| width. Use `IndirectDouble` when this is a possibility. Note that | |
| integers automatically use the smallest width possible, i.e. if you ask | |
| to serialize an int64_t whose value is actually small, you will use less | |
| bits. Doubles are represented as floats whenever possible losslessly, but | |
| this is only possible for few values. | |
| Since nested vectors/maps are stored over offsets, they typically don't | |
| affect the vector width. | |
| * To store large arrays of byte data, use a blob. If you'd use a typed | |
| vector, the bit width of the size field may make it use more space than | |
| expected, and may not be compatible with `memcpy`. | |
| Similarly, large arrays of (u)int16_t may be better off stored as a | |
| binary blob if their size could exceed 64k elements. | |
| Construction and use are otherwise similar to strings. |