A fast, header-only C++17 serialization library with human-readable text and compact binary formats.
Features • Quick Start • Format • API • Binary • Specification • License
- Header-only -- just
#include <mdf/mdf.h>, no build step required - Zero dependencies -- only the C++17 standard library
- Two formats -- human-readable
.mdftext and compact.mdfbbinary - 14 value types -- bool, int, float, string, vec2/3/4, quat, UUID, asset references, enums, arrays
- Constructor syntax --
vec3(0, 1, 0),rgb(0.5, 0.2, 0.1),hex(#FF6600),uuid("...") - Comments --
//line comments and/* */block comments - Binary format -- string deduplication, CRC32 integrity, optimal type sizing (f32/f64, i32/i64)
- Error reporting -- line:column diagnostics for parse errors
- Bidirectional conversion --
.mdf<->.mdfbwith a single function call
Option 1: Copy the headers
cp -r include/mdf /your/project/include/
Option 2: CMake subdirectory
add_subdirectory(mdf)
target_link_libraries(your_target PRIVATE mdf::mdf)Option 3: CMake FetchContent
include(FetchContent)
FetchContent_Declare(mdf
GIT_REPOSITORY https://github.com/MowoGames/mdf.git
GIT_TAG v1.0.0
)
FetchContent_MakeAvailable(mdf)
target_link_libraries(your_target PRIVATE mdf::mdf)#include <mdf/mdf.h>
#include <iostream>
int main() {
// ---- Read an MDF file ----
MDF::MdfDocument doc;
std::string error;
if (!MDF::MdfDocument::ParseFromFile("scene.mdf", doc, &error)) {
std::cerr << "Parse error: " << error << std::endl;
return 1;
}
auto& root = doc.Root();
std::cout << "Root type: " << root.GetType() << std::endl;
std::cout << "Root name: " << root.GetName() << std::endl;
// Navigate the tree
if (auto* player = root.FindChild("GameObject", "Player")) {
if (auto* transform = player->FindChild("Transform")) {
MDF::MdfVec3 pos = transform->GetProperty("position").AsVec3();
std::cout << "Player position: " << pos.x << ", " << pos.y << ", " << pos.z << std::endl;
}
}
// ---- Build a document ----
MDF::MdfDocument out;
auto& scene = out.CreateRoot("Scene", "MyScene");
scene.SetProperty("version", 1);
scene.SetProperty("ambient", MDF::MdfVec4(0.1f, 0.1f, 0.15f, 1.0f));
auto& obj = scene.AddChild("GameObject", "Player");
obj.SetProperty("active", true);
obj.SetProperty("tag", "Player");
auto& transform = obj.AddChild("Transform");
transform.SetProperty("position", MDF::MdfVec3(0, 1, 0));
transform.SetProperty("rotation", MDF::MdfVec3(0, 90, 0));
transform.SetProperty("scale", MDF::MdfVec3(1, 1, 1));
// Write text
out.WriteToFile("output.mdf");
// Write binary
MDF::MdfBinaryWriter::WriteToFile(out, "output.mdfb");
// Convert binary back to text
MDF::MdfConverter::BinaryToText("output.mdfb", "roundtrip.mdf");
}MDF uses a clean, hierarchical syntax inspired by configuration languages:
Scene "MainScene" {
version: 1
gravity: vec3(0, -9.81, 0)
// Environment settings
Environment {
ambient_color: rgb(0.1, 0.1, 0.15)
skybox: @"Assets/Skyboxes/BlueSky.hdr"
fog_density: 0.002
fog_color: hex(#B0C4DE)
}
GameObject "Player" {
tag: "Player"
active: true
layer: 1
Transform {
position: vec3(0, 1, 0)
rotation: vec3(0, 90, 0)
scale: vec3(1, 1, 1)
}
MeshRenderer {
mesh: @"Assets/Models/Character.fbx"
material: @"Assets/Materials/PlayerMat.mdf"
cast_shadows: true
}
RigidBody {
type: Dynamic
mass: 75.0
use_gravity: true
}
}
GameObject "MainCamera" {
Transform {
position: vec3(0, 5, -10)
rotation: vec3(25, 0, 0)
}
Camera {
projection: Perspective
fov: 60.0
near: 0.1
far: 1000.0
}
}
}
| Type | Syntax | Example |
|---|---|---|
| Null | null |
value: null |
| Bool | true / false |
active: true |
| Integer | number | count: 42 |
| Float | decimal / scientific | speed: 3.14, tiny: 1e-6 |
| String | "quoted" |
name: "Player" |
| Vec2 | vec2(x, y) |
uv: vec2(0.5, 1.0) |
| Vec3 | vec3(x, y, z) |
position: vec3(0, 1, 0) |
| Vec4 | vec4(x, y, z, w) |
color: vec4(1, 0, 0, 1) |
| Quat | quat(x, y, z, w) |
rotation: quat(0, 0, 0, 1) |
| Color | rgb(r, g, b) |
tint: rgb(0.5, 0.2, 0.1) |
| Color+A | rgba(r, g, b, a) |
overlay: rgba(0, 0, 0, 0.5) |
| Hex | hex(#RRGGBB) |
accent: hex(#FF6600) |
| UUID | uuid("...") |
id: uuid("550e8400-...") |
| Asset Ref | @"path" |
mesh: @"Models/Cube.fbx" |
| Enum | bare identifier | mode: Dynamic |
| Array | [a, b, c] |
layers: [1, 2, 3] |
MdfDocument Root container, parse/write entry point
MdfNode Named/typed block with properties and children
MdfValue Type-safe variant value (14 types)
MdfVec2/3/4 POD math types
MdfQuat POD quaternion type
MDF::MdfDocument doc;
std::string error;
// From file
MDF::MdfDocument::ParseFromFile("file.mdf", doc, &error);
// From string
MDF::MdfDocument::ParseFromString(source, doc, &error);
// Navigate
auto& root = doc.Root(); // first root node
auto* child = root.FindChild("Type"); // by type
auto* child = root.FindChild("Type", "Name"); // by type + name
auto children = root.FindChildren("Type"); // all of type
bool exists = root.HasProperty("key"); // property check
MDF::MdfValue val = root.GetProperty("key"); // get valueMDF::MdfDocument doc;
auto& root = doc.CreateRoot("Scene", "MyScene");
// Set properties
root.SetProperty("version", 1);
root.SetProperty("name", "Hello");
root.SetProperty("position", MDF::MdfVec3(1, 2, 3));
root.SetProperty("id", MDF::MdfValue::MakeUUID("550e8400-..."));
root.SetProperty("mesh", MDF::MdfValue::MakeAssetRef("Models/Cube.fbx"));
root.SetProperty("mode", MDF::MdfValue::MakeEnum("Dynamic"));
root["shorthand"] = MDF::MdfValue(42); // operator[]
// Add children
auto& child = root.AddChild("Transform");
child.SetProperty("position", MDF::MdfVec3(0, 0, 0));
// Serialize
std::string text = doc.WriteToString(); // to string
doc.WriteToFile("output.mdf"); // to file
doc.WriteToFile("output.mdf", 2); // custom indent// Write binary
auto bytes = MDF::MdfBinaryWriter::Write(doc); // to buffer
MDF::MdfBinaryWriter::WriteToFile(doc, "data.mdfb"); // to file
// Read binary
MDF::MdfDocument doc;
MDF::MdfBinaryReader::ReadFromFile("data.mdfb", doc); // from file
MDF::MdfBinaryReader::Read(bytes, doc); // from buffer
MDF::MdfBinaryReader::Read(ptr, size, doc); // from raw pointer
// Convert
MDF::MdfConverter::TextToBinary("in.mdf", "out.mdfb"); // .mdf -> .mdfb
MDF::MdfConverter::BinaryToText("in.mdfb", "out.mdf"); // .mdfb -> .mdfMDF::MdfValue val = node.GetProperty("key");
val.Type(); // MdfValueType enum
val.IsNull(); // type checks
val.IsInt();
val.IsFloat();
val.IsNumber(); // int or float
val.IsString();
val.IsVec3();
// ... etc
val.AsBool(); // typed getters (throw on mismatch)
val.AsInt(); // int64_t (auto-converts from float)
val.AsFloat(); // double (auto-converts from int)
val.AsString(); // works for String, UUID, AssetRef, Enum
val.AsVec3(); // MdfVec3
val.AsArray(); // std::vector<MdfValue>The .mdfb binary format provides:
- ~3-5x smaller files compared to text format
- ~10x faster parsing than text
- String deduplication -- each unique string stored once
- CRC32 integrity -- data corruption detection
- Optimal sizing -- int32 vs int64, float32 vs float64 chosen automatically
See docs/binary-format.md for the full specification.
include/mdf/
mdf.h All-in-one include (recommended)
mdf_types.h Vec2, Vec3, Vec4, Quat
mdf_value.h MdfValue (typed variant)
mdf_document.h MdfNode, MdfDocument
mdf_lexer.h Tokenizer
mdf_parser.h Text format parser
mdf_writer.h Text format writer
mdf_binary.h Binary format reader/writer/converter
You can include individual headers if you only need a subset:
#include <mdf/mdf.h> // everything (recommended)
#include <mdf/mdf_document.h> // just Document + Node + Value (no parse/write)
#include <mdf/mdf_types.h> // just Vec2/3/4, QuatMIT License. See LICENSE for details.