Getting Started with TriePack
What is TriePack?
TriePack is a compressed trie-based dictionary format implemented as a C
library. It encodes key-value data into a compact, ROM-friendly binary
representation (.trp files) using prefix sharing and bit-level packing.
The encoded output supports direct read access without full decompression.
Quick Install
git clone https://github.com/deftio/triepack.git
cd triepack
cmake -B build -DBUILD_TESTS=ON -DBUILD_EXAMPLES=ON
cmake --build build
ctest --test-dir build --output-on-failure
To install system-wide:
cmake --install build --prefix /usr/local
Basic Usage (C API)
The typical workflow is: create an encoder, insert key-value pairs, build the compressed blob, then query it with the dictionary reader.
#include "triepack/triepack.h"
#include <stdio.h>
#include <stdlib.h>
int main(void) {
/* --- Encode --- */
tp_encoder *enc = NULL;
tp_encoder_create(&enc);
tp_value v;
v = tp_value_string("world");
tp_encoder_add(enc, "hello", &v);
v = tp_value_int(42);
tp_encoder_add(enc, "count", &v);
/* Keys-only entry (set membership) */
tp_encoder_add(enc, "flag", NULL);
uint8_t *buf = NULL;
size_t len = 0;
tp_encoder_build(enc, &buf, &len);
/* --- Decode / Query --- */
tp_dict *dict = NULL;
tp_dict_open(&dict, buf, len);
tp_value val;
if (tp_dict_lookup(dict, "hello", &val) == TP_OK) {
printf("hello => %.*s\n",
(int)val.data.string_val.str_len,
val.data.string_val.str);
}
if (tp_dict_lookup(dict, "count", &val) == TP_OK) {
printf("count => %lld\n", (long long)val.data.int_val);
}
bool found = false;
tp_dict_contains(dict, "flag", &found);
printf("flag exists: %s\n", found ? "yes" : "no");
tp_dict_close(&dict);
tp_encoder_destroy(&enc);
free(buf);
return 0;
}
Linking
With CMake’s find_package:
find_package(triepack REQUIRED)
target_link_libraries(myapp PRIVATE triepack::triepack)
Or link directly against the library targets:
target_link_libraries(myapp PRIVATE triepack_core)
Library Stack
triepack_json (JSON encode/decode)
|
triepack_core (trie codec: encoder, dictionary, iterator)
|
triepack_bitstream (bit-level I/O, VarInt, UTF-8)
Each layer can be used independently.
Next Steps
- How It Works – trie encoding, lookup, and value store explained
- Building – build options, cross-compilation, and testing
- API Reference – full function listing with usage examples
- Examples – six runnable example programs
Tutorial: JSON Round-Trip
This walkthrough encodes a JSON object into the .trp binary format,
decodes it back, and verifies the result. Choose your language:
What We Will Build
We will take this JSON:
{
"project": "triepack",
"version": 1,
"stable": true,
"license": "BSD-2-Clause"
}
and:
- Encode it into the
.trpbinary format - Decode it back to JSON
- Look up individual keys
- Verify the round-trip is lossless
Step-by-Step
C (using triepack_json)
1. Include and set up
#include "triepack/triepack_json.h"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
int main(void)
{
const char *json =
"{\"project\":\"triepack\",\"version\":1,"
"\"stable\":true,\"license\":\"BSD-2-Clause\"}";
size_t json_len = strlen(json);
2. Encode JSON to .trp
tp_json_encode parses the JSON string, builds a compressed trie, and
writes the binary blob into a malloc-allocated buffer:
uint8_t *buf = NULL;
size_t buf_len = 0;
tp_result rc = tp_json_encode(json, json_len, &buf, &buf_len);
if (rc != TP_OK) {
fprintf(stderr, "encode: %s\n", tp_result_str(rc));
return 1;
}
printf("Encoded %zu JSON bytes -> %zu .trp bytes\n", json_len, buf_len);
3. Decode .trp back to JSON
char *out_json = NULL;
size_t out_len = 0;
rc = tp_json_decode(buf, buf_len, &out_json, &out_len);
if (rc != TP_OK) {
fprintf(stderr, "decode: %s\n", tp_result_str(rc));
free(buf);
return 1;
}
printf("Decoded: %s\n", out_json);
4. DOM-style key lookup
Without decoding the entire blob, you can look up individual keys:
tp_json *j = NULL;
rc = tp_json_open(&j, buf, buf_len);
if (rc == TP_OK) {
tp_value val;
if (tp_json_lookup_path(j, "project", &val) == TP_OK)
printf("project = \"%.*s\"\n",
(int)val.data.string_val.str_len,
val.data.string_val.str);
if (tp_json_lookup_path(j, "version", &val) == TP_OK)
printf("version = %lld\n", (long long)val.data.int_val);
tp_json_close(&j);
}
5. Clean up
free(out_json);
free(buf);
printf("Done.\n");
return 0;
}
Build and run
cmake -B build -DBUILD_JSON=ON
cmake --build build
./build/examples/json_roundtrip # ships with triepack
Or compile your own file:
gcc -o my_demo my_demo.c \
-Iinclude -Lbuild/src/json -Lbuild/src/core -Lbuild/src/bitstream \
-ltriepack_json -ltriepack_core -ltriepack_bitstream
C++ (using triepack::Json)
The C++ wrapper provides RAII lifetime management. No manual free()
calls needed.
1. Include and set up
#include "triepack/json.hpp"
extern "C" {
#include "triepack/triepack_json.h" // tp_json_decode_pretty, tp_result_str
}
#include <cstdio>
#include <cstdlib>
#include <cstring>
int main()
{
const char *json =
"{\"project\":\"triepack\",\"version\":1,"
"\"stable\":true,\"license\":\"BSD-2-Clause\"}";
2. Encode
triepack::Json codec;
const uint8_t *buf = nullptr;
size_t buf_len = 0;
int rc = codec.encode(json, &buf, &buf_len);
if (rc != 0) {
std::fprintf(stderr, "encode: %s\n", tp_result_str(rc));
return 1;
}
std::printf("Encoded %zu JSON bytes -> %zu .trp bytes\n",
std::strlen(json), buf_len);
3. Decode
const char *out_json = nullptr;
rc = codec.decode(buf, buf_len, &out_json);
if (rc != 0) {
std::fprintf(stderr, "decode: %s\n", tp_result_str(rc));
return 1;
}
std::printf("Decoded: %s\n", out_json);
4. Pretty-print (via C API)
char *pretty = nullptr;
size_t pretty_len = 0;
rc = tp_json_decode_pretty(buf, buf_len, " ", &pretty, &pretty_len);
if (rc == 0) {
std::printf("Pretty:\n%s\n", pretty);
std::free(pretty);
}
5. Done
The triepack::Json destructor cleans up automatically when codec
goes out of scope. The out_json string returned by decode is owned
by the codec instance and freed with it.
Build
add_executable(my_demo my_demo.cpp)
target_link_libraries(my_demo PRIVATE triepack_wrapper triepack_json)
Python
The Python binding is a pure-Python implementation. No C extension or FFI required.
1. Install
cd bindings/python
pip install -e .
2. Encode
from triepack import encode, decode
data = {
"project": "triepack",
"version": 1,
"stable": True,
"license": "BSD-2-Clause"
}
buf = encode(data)
print(f"Encoded -> {len(buf)} bytes")
encode() takes a dict and returns bytes containing the .trp binary.
3. Decode
result = decode(buf)
print("Decoded:", result)
# {'license': 'BSD-2-Clause', 'project': 'triepack', 'stable': True, 'version': 1}
decode() takes bytes or bytearray and returns a dict. Keys are
sorted alphabetically (trie order).
4. Write to file and read back
# Save to disk
with open("demo.trp", "wb") as f:
f.write(buf)
# Read back
with open("demo.trp", "rb") as f:
loaded = decode(f.read())
print("From file:", loaded)
The .trp file is byte-for-byte identical to what the C and JavaScript
implementations produce.
5. Verify round-trip
assert result == data
print("Round-trip OK")
JavaScript (Node.js)
The JavaScript binding is a pure-JS implementation that reads and writes
the same .trp binary format. No native dependencies.
1. Install
cd bindings/javascript
npm install
2. Encode
const { encode, decode } = require('./src/index');
const data = {
project: "triepack",
version: 1,
stable: true,
license: "BSD-2-Clause"
};
const buf = encode(data);
console.log(`Encoded -> ${buf.length} bytes`);
encode() takes a plain object and returns a Uint8Array containing
the .trp binary.
3. Decode
const result = decode(buf);
console.log("Decoded:", result);
// { license: 'BSD-2-Clause', project: 'triepack', stable: true, version: 1 }
decode() takes a Uint8Array and returns a plain object. Keys are
sorted alphabetically (trie order).
4. Write to file and read back
const fs = require('fs');
// Save to disk
fs.writeFileSync('demo.trp', Buffer.from(buf));
// Read back
const loaded = decode(new Uint8Array(fs.readFileSync('demo.trp')));
console.log("From file:", loaded);
The .trp file is byte-for-byte identical to what the C library
produces. You can encode in JavaScript and decode in C (or Python), or
vice versa.
5. Verify round-trip
const assert = require('assert');
assert.deepStrictEqual(result, data);
console.log("Round-trip OK");
What Happened Under the Hood
- Parse – the JSON string is parsed into key-value pairs
- Sort – keys are sorted alphabetically
- Build trie – shared prefixes are collapsed into a compressed trie
- Encode values – each value is stored with a type tag (null, bool, int, float, string)
- Write header – magic bytes (
TRP\0), version, key count, offsets - CRC-32 – an integrity checksum is appended
The resulting .trp blob is typically 40–60% smaller than the
original JSON for structured data with shared key prefixes.
Key Differences from JSON
| Feature | JSON | TriePack (.trp) |
|---|---|---|
| Format | Text | Binary |
| Key order | Insertion order | Sorted (trie order) |
| Key lookup | O(n) scan | O(k) trie walk (k = key length) |
| Duplicate keys | Allowed (last wins) | Not allowed |
| Compression | None | Shared-prefix trie |
| Integrity | None | CRC-32 checksum |
| Human-readable | Yes | No (use trp decode) |