Serialization is a critical technique in programming, enabling objects to be saved to disk or transmitted over a network. While there’s no universal, automatic way to serialize complex objects in Delphi (due to challenges like private fields), manual serialization is straightforward—and binary serialization, in particular, stands out as the fastest and most efficient method. In this post, I’ll explain why I exclusively use binary serialization for my objects and explore its advantages over text-based methods like XML or JSON.
Text of binary – Choose your poison!
Text-based serialization (e.g., XML, JSON, INI) is popular for its human readability, but binary files offer greater benefits:
Speed: Binary serialization skips the overhead of converting data to and from strings. Converting the number 7 to the string ‘7’, then writing that to a XML that is damn slow to parse, and then converting the string ‘7’ back to number 7 is madness.
Safety: Binary files are inherently less editable by users. Unlike text files, which can be opened and modified (sometimes incorrectly), binary formats discourage tampering, reducing the risk of corrupted data. When we store data in text format, we need to pray to 2-3 gods, hoping that the user didn’t edit the XML file to change ‘7’ to ‘7x’.
Size efficiency: Binary data is compact. A 32-bit integer takes exactly 4 bytes in binary, but its text representation (e.g., -2147483648 in JSON) could take up to 11 bytes (or 22 if the file is Unicode), plus the Json overhead. So, storing data in text files can take hundreds or even thousands of times more space than the binary files.
Overcoming the fear of binary
Twenty years ago, binary files intimidated me, despite my experience with microcontrollers (which use low level binary data a lot). They seemed complex and unforgiving. But over time, I realized they’re actually easy to master.
To simplify the process, I built a Delphi library (available under nonrestrictive license on GitHub) that handles binary serialization efficiently.
The files of interest are ccStreamFile.pas, ccStreamBuff.pas, ccStreamFile.pas, ccStreamMem.pas, and ccStreamMem2.pas.
Let’s look at a practical example.
Serializing a TSoldier object
Here’s how I serialize a simple TSoldier class to a binary file using my library:
type TSoldier = class(TObject) private const StreamSignature: AnsiString = 'TSoldier'; // Identifies the file type public Life : Integer; // Soldier's health Ammo : Integer; // Ammunition count Speed : Integer; // Movement speed Name : string; // Soldier's name Gun : string; // Equipped weapon procedure Load(Stream: TCubicBuffStream2); virtual; procedure Save(Stream: TCubicBuffStream2); virtual; end; implementation procedure TSoldier.Save(Stream: TCubicBuffStream2); begin Stream.WriteHeader(StreamSignature, 1); // Write file signature and version Stream.WriteInteger(Life); // Save health as 4-byte integer Stream.WriteInteger(Ammo); // Save ammo as 4-byte integer Stream.WriteInteger(Speed); // Save speed as 4-byte integer Stream.WriteString(Name); // Save name with length prefix Stream.WriteString(Gun); // Save gun with length prefix Stream.WritePadding(100); // Reserve 100 bytes for future fields end; procedure TSoldier.Load(Stream: TCubicBuffStream2); begin Stream.ReadHeader(StreamSignature, 1); // Verify signature and version Life := Stream.ReadInteger; // Load health Ammo := Stream.ReadInteger; // Load ammo Speed := Stream.ReadInteger; // Load speed Name := Stream.ReadString; // Load name Gun := Stream.ReadString; // Load gun Stream.ReadPadding(100); // Skip reserved bytes end;
The code above can be found in the Delphi Demos folder.
Code explanations
Signature: The StreamSignature (TSoldier) is written at the start of the file. It’s optional but helps confirm we’re loading the right type of file/data. If file size is really critical, you can skip it.
Versioning: The 1 in WriteHeader and ReadHeader tracks the file format version, making it easier to update the class later without breaking old files. Increment the number every time you change your file format.
Data Writing: Integers (Life, Ammo, Speed) are stored as raw 4-byte values, while strings (Name, Gun) include a length prefix followed by the characters—no wasted space or parsing required.
Padding: The WritePadding(100) reserves 100 bytes at the end. If I later add a field (e.g., Armor), older files can still be read by skipping this unused space, ensuring backward compatibility.
This example is simple but powerful. For a more complex case with multiple versions, check out cbLogLinesAbstract.pas in the LightSaber library.
When Text-Based Serialization Still Makes Sense
Binary isn’t always the answer. Text-based formats shine in these cases:
- Readability: Need to tweak a config file by hand? JSON or INI is ideal.
- Interoperability: Sharing data across languages or platforms? Text formats are more universal.
- Debugging: Text files let you see the data instantly in any editor (but it also let the user enter errors!)
Anyway, for serializing objects, none of the above apply. So for speed, safety, and efficiency—like in games, embedded systems, or large datasets—binary wins hands down.
Final thoughts
I don’t know how the programmers came up with such a screwed-up idea (I mean XML). I guess it is coming from that internet-scripting “cool kid” generation. JS and PHP were not so great with binary files, so the whole Internet was built around text files. We live in a sad sad world 🙁
Binary serialization isn’t as scary as it seems, and its benefits—speed, safety, and compactness—make it a game-changer for performance-critical projects. My library makes it accessible, and I’m excited to share more through upcoming video tutorials on my Delphi channel.
Don’t be a stranger. Let’s Talk!
Have you used binary serialization? What’s your take on text vs. binary?
Drop a comment below—I’d love to hear your experiences or questions!