2026-06-17 23:40:35 +00:00
|
|
|
# iKvxx
|
|
|
|
|
|
2026-06-17 18:57:54 -05:00
|
|
|
iKvxx is a modern C++17 binding for [iKv](https://dock-it.dev/iDENTITY-Technology/iKv).
|
|
|
|
|
It provides automatic resource management, exceptions, type-safe accessors, and a
|
|
|
|
|
compact `Value` API modeled after JsonCpp while preserving iKv text and binary format
|
|
|
|
|
compatibility.
|
|
|
|
|
|
|
|
|
|
## What it does
|
|
|
|
|
|
|
|
|
|
- Parse iKv text from strings or files
|
|
|
|
|
- Parse iKv binary data from memory or files
|
|
|
|
|
- Build object and array trees with a JsonCpp-style API
|
|
|
|
|
- Read and write both `iKv1` and `iKv2`
|
|
|
|
|
- Write the current `iKv2` format by default
|
|
|
|
|
- Manage the underlying C tree automatically with RAII
|
|
|
|
|
- Report parse, type, bounds, allocation, and I/O failures with exceptions
|
|
|
|
|
- Provide a reusable `ikvxx::ikvxx` CMake target and installable package
|
|
|
|
|
|
|
|
|
|
## Syntax preview
|
|
|
|
|
|
|
|
|
|
```ikv
|
|
|
|
|
ikv2 "player_save"
|
|
|
|
|
{
|
|
|
|
|
"title" "iKvxx demo"
|
|
|
|
|
"version" 2
|
|
|
|
|
"player" {
|
|
|
|
|
"name" "jondoe"
|
|
|
|
|
"alive" true
|
|
|
|
|
"speed" 12.5
|
|
|
|
|
}
|
|
|
|
|
"inventory" [
|
|
|
|
|
"wrench"
|
|
|
|
|
"battery"
|
|
|
|
|
"map"
|
|
|
|
|
]
|
|
|
|
|
}
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
## Library example
|
|
|
|
|
|
|
|
|
|
```cpp
|
|
|
|
|
#include <ikvxx/ikvxx.hpp>
|
|
|
|
|
|
|
|
|
|
int main()
|
|
|
|
|
{
|
|
|
|
|
ikv::Value root(ikv::objectValue, "player_save");
|
|
|
|
|
root["title"] = "iKvxx demo";
|
|
|
|
|
root["version"] = 2;
|
|
|
|
|
|
|
|
|
|
auto player = root.makeObject("player");
|
|
|
|
|
player["name"] = "jondoe";
|
|
|
|
|
player["alive"] = true;
|
|
|
|
|
player["speed"] = 12.5;
|
|
|
|
|
|
|
|
|
|
auto inventory = root.makeArray("inventory", ikv::stringValue);
|
|
|
|
|
inventory.append("wrench");
|
|
|
|
|
inventory.append("battery");
|
|
|
|
|
inventory.append("map");
|
|
|
|
|
|
|
|
|
|
root.write("player_save.ikv");
|
|
|
|
|
}
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
The root and all child handles release their underlying iKv tree automatically. No
|
|
|
|
|
manual `ikv_free` call is required.
|
|
|
|
|
|
|
|
|
|
## Reading values
|
|
|
|
|
|
|
|
|
|
Use `load` for text files and the familiar `operator[]` syntax for object and array
|
|
|
|
|
access:
|
|
|
|
|
|
|
|
|
|
```cpp
|
|
|
|
|
#include <ikvxx/ikvxx.hpp>
|
|
|
|
|
#include <iostream>
|
|
|
|
|
|
|
|
|
|
int main()
|
|
|
|
|
{
|
|
|
|
|
auto root = ikv::Value::load("player_save.ikv");
|
|
|
|
|
|
|
|
|
|
std::cout << root["title"].asString() << '\n';
|
|
|
|
|
std::cout << root["version"].asInt() << '\n';
|
|
|
|
|
std::cout << root["player"]["speed"].asDouble() << '\n';
|
|
|
|
|
|
|
|
|
|
auto inventory = root["inventory"];
|
|
|
|
|
for (ikv::Value::ArrayIndex i = 0; i < inventory.size(); ++i)
|
|
|
|
|
std::cout << inventory[i].asString() << '\n';
|
|
|
|
|
}
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
Missing object members produce a null `Value`. Invalid conversions and out-of-range
|
|
|
|
|
array indexes throw a standard exception:
|
|
|
|
|
|
|
|
|
|
```cpp
|
|
|
|
|
if (!root.isMember("difficulty"))
|
|
|
|
|
root["difficulty"] = "normal";
|
|
|
|
|
|
|
|
|
|
if (root["optional"].isNull())
|
|
|
|
|
root["optional"] = false;
|
|
|
|
|
|
|
|
|
|
try
|
|
|
|
|
{
|
|
|
|
|
int value = root["title"].asInt();
|
|
|
|
|
}
|
|
|
|
|
catch (const ikv::Error& error)
|
|
|
|
|
{
|
|
|
|
|
// "title" is a string, not an integer.
|
|
|
|
|
}
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
## Parsing text
|
|
|
|
|
|
|
|
|
|
`Value::parse` auto-detects `iKv1` and `iKv2` input:
|
|
|
|
|
|
|
|
|
|
```cpp
|
|
|
|
|
const std::string text = R"(
|
|
|
|
|
ikv2 "settings"
|
|
|
|
|
{
|
|
|
|
|
"fullscreen" true
|
|
|
|
|
"width" 1920
|
|
|
|
|
"height" 1080
|
|
|
|
|
}
|
|
|
|
|
)";
|
|
|
|
|
|
|
|
|
|
auto settings = ikv::Value::parse(text);
|
|
|
|
|
bool fullscreen = settings["fullscreen"].asBool();
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
## Arrays
|
|
|
|
|
|
|
|
|
|
Arrays may be untyped or restricted to one iKv value type. Typed arrays reject values
|
|
|
|
|
of the wrong type:
|
|
|
|
|
|
|
|
|
|
```cpp
|
|
|
|
|
ikv::Value root;
|
|
|
|
|
|
|
|
|
|
auto scores = root.makeArray("scores", ikv::intValue);
|
|
|
|
|
scores.append(10);
|
|
|
|
|
scores.append(25);
|
|
|
|
|
scores.append(100);
|
|
|
|
|
|
|
|
|
|
auto mixed = root.makeArray("mixed");
|
|
|
|
|
mixed.append("ready");
|
|
|
|
|
mixed.append(42);
|
|
|
|
|
mixed.append(true);
|
|
|
|
|
|
|
|
|
|
auto players = root.makeArray("players", ikv::objectValue);
|
|
|
|
|
auto first = players.appendObject();
|
|
|
|
|
first["name"] = "Ada";
|
|
|
|
|
first["score"] = 100;
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
## Binary data
|
|
|
|
|
|
|
|
|
|
Binary iKv can be written to a file or encoded directly into a byte vector:
|
|
|
|
|
|
|
|
|
|
```cpp
|
|
|
|
|
ikv::Value root;
|
|
|
|
|
root["answer"] = 42;
|
|
|
|
|
|
|
|
|
|
root.writeBinary("data.ikvb");
|
|
|
|
|
auto from_file = ikv::Value::loadBinary("data.ikvb");
|
|
|
|
|
|
|
|
|
|
std::vector<std::uint8_t> bytes = root.toBinary();
|
|
|
|
|
auto from_memory = ikv::Value::fromBinary(bytes.data(), bytes.size());
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
Pass an explicit version when compatibility with older data is required:
|
|
|
|
|
|
|
|
|
|
```cpp
|
|
|
|
|
root.write("legacy.ikv", ikv::Version::v1);
|
|
|
|
|
root.writeBinary("legacy.ikvb", ikv::Version::v1);
|
|
|
|
|
auto legacy_bytes = root.toBinary(ikv::Version::v1);
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
## Refreshing a tree
|
|
|
|
|
|
|
|
|
|
An existing root can be updated in place from a text or binary file. Other `Value`
|
|
|
|
|
handles that share that root observe the refreshed data:
|
|
|
|
|
|
|
|
|
|
```cpp
|
|
|
|
|
auto settings = ikv::Value::load("settings.ikv");
|
|
|
|
|
auto shared = settings;
|
|
|
|
|
|
|
|
|
|
settings.refresh("updated-settings.ikv");
|
|
|
|
|
// shared now refers to the refreshed tree as well.
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
`refresh` is only valid on a mutable root value. If refresh fails, the existing tree
|
|
|
|
|
is preserved.
|
|
|
|
|
|
|
|
|
|
## Build
|
|
|
|
|
|
|
|
|
|
Clone with the iKv submodule and build with CMake:
|
|
|
|
|
|
|
|
|
|
```sh
|
|
|
|
|
git clone --recurse-submodules https://dock-it.dev/iDENTITY-Technology/iKvxx.git
|
|
|
|
|
cd iKvxx
|
|
|
|
|
cmake -S . -B build
|
|
|
|
|
cmake --build build
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
If the repository was cloned without submodules:
|
|
|
|
|
|
|
|
|
|
```sh
|
|
|
|
|
git submodule update --init --recursive
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
This produces the reusable static library targets:
|
|
|
|
|
|
|
|
|
|
- `ikvxx`
|
|
|
|
|
- `ikvxx::ikvxx` for CMake consumers
|
|
|
|
|
|
|
|
|
|
### CMake consumer usage
|
|
|
|
|
|
|
|
|
|
When iKvxx is included directly in another source tree:
|
|
|
|
|
|
|
|
|
|
```cmake
|
|
|
|
|
add_subdirectory(path/to/iKvxx)
|
|
|
|
|
target_link_libraries(your_target PRIVATE ikvxx::ikvxx)
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
The library can also be installed and consumed with `find_package`:
|
|
|
|
|
|
|
|
|
|
```sh
|
|
|
|
|
cmake --install build --prefix /path/to/ikvxx-install
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
```cmake
|
|
|
|
|
find_package(ikvxx 0.1 REQUIRED CONFIG)
|
|
|
|
|
target_link_libraries(your_target PRIVATE ikvxx::ikvxx)
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
Point `CMAKE_PREFIX_PATH` at the selected installation prefix when it is not in a
|
|
|
|
|
standard system location.
|
|
|
|
|
|
|
|
|
|
## Tests
|
|
|
|
|
|
|
|
|
|
Build and run the binding tests with CTest:
|
|
|
|
|
|
|
|
|
|
```sh
|
|
|
|
|
cmake -S . -B build -DIKVXX_BUILD_TESTS=ON
|
|
|
|
|
cmake --build build
|
|
|
|
|
ctest --test-dir build --output-on-failure
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
The test suite covers construction, every value type, object and array access,
|
|
|
|
|
assignment and append overloads, conversion failures, bounds checks, text and binary
|
|
|
|
|
I/O, format versions, refresh behavior, and shared ownership. Gitea Actions runs the
|
|
|
|
|
suite on Ubuntu with GCC and Clang in both Debug and Release configurations.
|
|
|
|
|
|
|
|
|
|
On Windows, the complete configure, build, and test sequence can be run with:
|
|
|
|
|
|
|
|
|
|
```bat
|
|
|
|
|
tests\run.bat
|
|
|
|
|
tests\run.bat Debug
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
## Project layout
|
|
|
|
|
|
|
|
|
|
- `include/ikvxx/ikvxx.hpp`: public C++ API
|
|
|
|
|
- `src/value.cpp`: C++ binding implementation
|
|
|
|
|
- `tests/value_tests.cpp`: unit and integration tests
|
|
|
|
|
- `third_party/iKv`: pinned iKv Git submodule
|
|
|
|
|
- `cmake/ikvxxConfig.cmake.in`: installed CMake package configuration
|
|
|
|
|
- `.gitea/workflows/bindings.yml`: Ubuntu compiler and build matrix
|
|
|
|
|
|
|
|
|
|
## Format behavior
|
|
|
|
|
|
|
|
|
|
- Generic parse APIs auto-detect `iKv1` and `iKv2`
|
|
|
|
|
- Generic write APIs default to `iKv2`
|
|
|
|
|
- Text and binary files remain compatible with the underlying iKv C library
|
|
|
|
|
- Explicit `ikv::Version::v1` and `ikv::Version::v2` output is supported
|
|
|
|
|
- `iKv2` binary input retains iKv's indexed, lazy root lookup behavior
|
|
|
|
|
|
|
|
|
|
## Public API
|
|
|
|
|
|
|
|
|
|
The public interface is declared in
|
|
|
|
|
[`include/ikvxx/ikvxx.hpp`](./include/ikvxx/ikvxx.hpp). Key areas include:
|
|
|
|
|
|
|
|
|
|
- `ikv::Value` construction and automatic lifetime management
|
|
|
|
|
- JsonCpp-style object lookup with `operator[]`
|
|
|
|
|
- Array indexing and append operations
|
|
|
|
|
- Runtime type inspection with `isString`, `isInt`, `isObject`, and related methods
|
|
|
|
|
- Typed scalar access with `asString`, `asInt`, `asInt64`, `asDouble`, and `asBool`
|
|
|
|
|
- Text parsing, loading, writing, and in-place refresh
|
|
|
|
|
- Binary memory and file I/O
|
|
|
|
|
- Explicit format-version selection
|
|
|
|
|
|
|
|
|
|
## API constraints
|
|
|
|
|
|
|
|
|
|
iKvxx intentionally exposes errors where the underlying iKv model cannot safely
|
|
|
|
|
provide JsonCpp behavior:
|
|
|
|
|
|
|
|
|
|
- Roots must be objects or arrays; scalar roots are not supported by iKv
|
|
|
|
|
- Arrays of arrays are not supported by the current iKv C API
|
|
|
|
|
- Existing array elements cannot be replaced in place
|
|
|
|
|
- Copied `Value` handles share ownership of the same tree
|
|
|
|
|
- Structural assignment between two `Value` objects is disabled because the public C
|
|
|
|
|
API cannot clone arbitrary subtrees
|
|
|
|
|
- A child handle must not be retained across replacement of that child or a root
|
|
|
|
|
refresh; acquire the child again after modifying the structure
|
|
|
|
|
|
|
|
|
|
## License
|
|
|
|
|
|
|
|
|
|
This repository ships with the license text in [`LICENSE`](./LICENSE):
|
|
|
|
|
|
|
|
|
|
- Creative Commons Attribution-ShareAlike 4.0 International (`CC BY-SA 4.0`)
|
|
|
|
|
|
|
|
|
|
The iKv submodule includes its own license file at
|
|
|
|
|
[`third_party/iKv/LICENSE`](./third_party/iKv/LICENSE).
|