Properties Files for D

Written by Andrew Lalis at 10 January, 2022

Using properties files in D with the d-properties library.

As part of Gyrobian's ongoing efforts to further adopt the D programming language as part of our daily tech stack, this article will shed some light on the d-properties library developed by Andrew recently for reading and writing Java-style Properties files for config and simple data storage.

To preface, properties files are formatted text files used mainly by Java applications (but they're simple and human-readable, so it makes sense to support them elsewhere). A properties file consists of a series of key-value pairs, each beginning on a separate line. The following snippet shows a basic example of what such a file might look like.


            # My application's configuration properties
            app.port=33567
            app.enable-ssl=true
            db.user=root
            db.pass=123456
        

These simple key-value pair files are most often used for specifying external configuration settings for applications, like passwords or secrets that shouldn't be baked into the source code, or for internationalization files (for translating website contents, for example).

Using d-properties

It's very easy to begin using our library to read and write properties files in your D application. If you're using the dub project management tool, it's as easy as calling dub add d-properties from your command-line, or adding "d-properties": "~>1.0.2" to your dub.json file.

In other cases, the MIT license permits you to simply copy and paste the source D files directly from the GitHub repository. You'll need properties.d, reader.d, and writer.d.

Let's start by creating a simple D application which can read the properties we've defined in that snippet above, assuming it's stored in a my-config.properties file.


            import std.stdio;
            import std.conv;
            import d_properties;

            void main() {
                auto props = Properties("my-config.properties");
                writefln!"Starting app on port %d"(props["app.port"].to!ushort);
                if (props["app.enable-ssl"].to!bool) {
                    writeln("SSL is enabled.");
                }
            }
        
We start by reading the properties from my-config.properties, then print some debug information like we might in a real application. We can access properties using the index syntax (props["key"]), which will return a string value.

The above snippet should actually be enough for around 90% of use-cases, where you simply want to get config values at runtime. However, the library provides a number of utilities for more advanced use cases, including the following:

Additionally, the library also provides support for a number of different flavors, including the classic key=value format, the colon format key:value, and the space format key value. For a full overview of everything you can do with d-properties, check out the documentation.

How It Works

Internally, the Properties structure is nothing more than a fancy wrapper around an associative array of string[string] values;, with a bunch of methods added to offer the functionality described in the previous section, and some extra checks to ensure that the programmer isn't likely to run into unexpected situations at runtime.

Writing properties to a file is quite simple as well; in fact, the majority of the serialization algorithm can be condensed into this short snippet:


            auto f = File(filename, "w"); // File comment writing omitted for brevity
            auto keys = props.values.keys;
            keys.sort(); // Sort keys to make file writing deterministic.
            foreach (key; keys) {
                f.writefln("%s%s%s", sanitizeKey(key), separator, sanitizeValue(props[key]));
            }
        

Reading properties from a file is where things get a bit more difficult, however. We won't go into the gorey details which you can find by reading the source code directly, but in short, we read the properties in a linear fashion, treating the input as a stack that we pull characters from. First we start by consuming characters from the key's string, until we determine that we've found a separator, at which point we then begin parsing the value string. We continue doing this until all key-value pairs have been read.

For both input and output, time complexity is linear with the size of the properties file, and space complexity is also linear. While small performance improvements could possibly be made to the reading algorithm, in its current state, it's more than capable of managing the vast majority of files.