Poor man's key/value store

Not reinventing the wheel is usually a pretty good idea. Unless… it is the plan!

Lately, for one of my pet projects, I needed to persist key/value pairs of data. The complexity and features of a full fledged database were not really needed for the functionality I had in mind; thus simple key/value store functionality is more than enough in my case. For fun, I decided to write something useful and simple. Here is what came out of it.

Wall of text crits you over 9000 - show me the code!
After some reading, I discovered that zip is designed very similar to RavenDB 4.0 blittable json format - it has collection of headers with local offsets to relevant data. Perfect! I thought. Enough to have fast lookups and have a nice api..
zip-64-layout.png


Using a ZipArchive class makes it almost too easy. To finish the puzzle, I’ve used Json.Net for serialization (it supports .Net Core!)

So, assuming _archive is an instance of ZipArchive, the following is the code for reading a value by key

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
public T Read<T>(string key)
{
var entry = _archive.GetEntry(key);
if (entry == null)
return default(T);

using (var stream = entry.Open())
using (var sr = new StreamReader(stream))
using (var tr = new JsonTextReader(sr))
{
var json = JObject.Load(tr);
JToken val;
//some code to support storing primitives as values
if (json.TryGetValue("PrimitiveValue", out val))
return val.ToObject<T>();

return json.ToObject<T>();
}
}

The code for writing a value by key is simple too..

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
public void Write<T>(string key, T value)
{
var entry = _archive.GetEntry(key);
if (entry == null)
entry = _archive.CreateEntry(key);

using (var stream = entry.Open())
using (var sw = new StreamWriter(stream))
{
string valueAsString;
var type = typeof(T);

//make sure that primitives are handled properly
if (type.GetTypeInfo().IsPrimitive ||
(type.Name.Equals("String") && type.Namespace.Equals("System")))
valueAsString = JObject.FromObject(new { PrimitiveValue = value }).ToString();
else
valueAsString = JObject.FromObject(value).ToString();

sw.Write(valueAsString);
sw.Flush();
}
}

As can be seen, using ZipArchive provides a simple and reasonable alternative for a flully fledged database. Thus, sometimes reinventing the wheel is a good thing, as long as it reduces complexity and increases maintanability of the code :)
Full code for the persistent storage that I am talking about can be seen here