Search code examples
c#redisprotobuf-netstackexchange.redis

Why redis with protobuf is saving empty arrays as null?


I'm maintaining an application using protobuf-net 2.3.3 on a redis server 2.8.2103 using StackExchange.Redis 1.2.6.

For objects like:

[ProtoContract(ImplicitFields = ImplicitFields.AllFields)]
public class Cachable { Foo[] Foos { get; set; } }

When I save using a simple:

using (var memoryStream = new MemoryStream())
{
    Serializer.Serialize(memoryStream, cachable);
    database.HashSetAsync("category", "key", memoryStream.ToArray());
}

And then retrieve with:

var response = database.HashGet("category", "key");
if (!response.HasValue) return null;
using (var memoryStream = new MemoryStream(response, false))
{
    return Serializer.Deserialize<Cachable>(memoryStream);
}

If the cached array Foos had an empty instance, as in new Foo[0], once Cachable is deserialized, the array become null. This is changing the behavior of some part of application and generating errors.
Is this behavior expected? Is there any way to change it?


Solution

  • Is the real problem here the fact that Foo[0] is null? If so:

    • protobuf has no concept of null; it cannot represent and store null, and so by default protobuf-net skips null values by default, making this essentially an empty array
    • with the slight caveat of "empty packed primitives", protobuf has no concept of an "empty" sequence; in .proto terms, you're talking about a repeated field that has zero elements, which means : it simply doesn't exist in the payload at all
    • and if it doesn't exist in the payload, it is never deserialized to anything - because there is never anything in the payload to tell it to deserialize

    so:

    • avoid null unless you mean for an optional sub-element; definitely avoid null in lists / arrays / etc
    • don't assume that empty lists/arrays/etc will be initialized to non-null values by the library

    IMO, the following would be reasonable and pragmatic for the second point:

    Foo[] Foos { get; set; } = Array.Empty<Foo>();
    

    (which avoids the issue of it initializing as null)