Alex Boesen
Apr 25, 2020
visibility 2288
star star star star star
(3 votes)

Creating your own NullCleaningContentSerializer using pattern matching

At Alm. Brand we are big users of the Content Delivery Api, using it to power most of our customer facing solutions with data be it SPA or mobile app and when we can reduce the amount of data we send over the wire is a big win.

So when the SetIncludeNullValues option was included to strip away null values we started using it right away, but we had to stop using it as the NullCleaningContentSerializer had a heavy impact on the Content Api performance in our setup especially after an deploy.

The reason for this performance impact is that NullCleaningContentSerializer uses a lot of reflection that gets the job done but with a cost(a high one in our case).

 
That got me to thinking, what if I created a version of the NullCleaningContentSerializer that instead of reflection only used pattern matching, could that give is the size reduction we wanted without the performance cost? Yes as it turns out! 

So first up is to make sure that IncludeNullValues is set to true and jsonSerializer settings NullValueHandling is set to ignore

public void ConfigureContainer(ServiceConfigurationContext context)
{
    context.Services.Configure<ContentApiConfiguration>(config =>
    {
        //config to content delivery api goes here
        config.Default()
            .SetFlattenPropertyModel(true)
            .SetIncludeNullValues(true);
    });

public void Initialize(InitializationEngine context)
{
    var jsonSerializer = context.Locate.Advanced.GetInstance<JsonSerializer>();
    jsonSerializer.Settings.NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore
}

 NullValueHandling only effect properties, not dictionaries so that where our new CleaningContentSerializer comes into the picture!

public class CleaningContentSerializer : IContentApiSerializer
{
    public CleaningContentSerializer(IContentApiSerializer defaultSerializer)
    {
        DefaultSerializer = defaultSerializer;
    }
    public string MediaType => DefaultSerializer.MediaType;
    public Encoding Encoding => DefaultSerializer.Encoding;
    private IContentApiSerializer DefaultSerializer { get; }
    
    public string Serialize(object value)
    {
        CleanObject(value);
        return DefaultSerializer.Serialize(value);
    }
...

we use the default IContentApiSerializer to do the actual serialize part, our magic is in the CleanObject method

private void CleanObject(object value)
{
    switch (value)
    {
        case ContentApiModel model:
            CleanContentApiModel(model);
            break;
        case IEnumerable<ContentApiModel> models:
            CleanContentApiModels(models);
            break;
    
    void CleanContentApiModel(ContentApiModel model)
    {
        //we have no need for these
        model.Changed = null;
        model.Created = null;
        model.ExistingLanguages = null;
        model.MasterLanguage = null;
        model.Saved = null;
        model.StartPublish = null;
        model.StopPublish = null;
        model.Status = null;
        model.ParentLink = null;
        var properties = model.Properties;
        //only used serverside
        RemoveProperty(properties, "PreScripts");
        RemoveProperty(properties, "PostScripts");
        RemoveProperty(properties, "BodyScripts");
        RemoveProperty(properties, "SiteSettings");
        RemoveProperty(properties, "PersonalizedSiteSettings");
        
        var removeList = new List<string>();
        foreach (var property in properties)
        {
            CheckProperty(property, removeList);
        }
        foreach (var removeKey in removeList)
        {
            properties.Remove(removeKey);
        }
    
    void CheckProperty(KeyValuePair<string, object> property, IList<string> removalList)
    {
        switch (property.Value)
        {
            case null:
                removalList.Add(property.Key);
                break;
            case string str when string.IsNullOrEmpty(str):
                removalList.Add(property.Key);
                break;
            case ICollection collection when collection.Count == 0:
                removalList.Add(property.Key);
                break;
            case IEnumerable<ContentApiModel> models when models.Any():
                CleanContentApiModels(models);
                break;
            case IEnumerable<ContentApiModel> models:
                removalList.Add(property.Key);
                break;
        }
    }
}

(full class here)

all that is left is to register the CleaningContentSerializer in the DI container:
services.Intercept<IContentApiSerializer>((locator, defaultApiSerializer) => new CleaningContentSerializer(defaultApiSerializer)) 

As you can see we not only remove null/empty properties but also uses this opportunity to do a litte spring clearing on the ContentApiModel where we remove properties that is not used by our spa or app which result in a size reduction of over 60% in some of our content api requests!

There are likely some cases that this will not handle that the default NullCleaningContentSerializer can but atleast for us this is a better compromise between getting the size reduction and performance

Apr 25, 2020

Comments

error Please login to comment.
Latest blogs
Parallel Development in Optimizely CMS SaaS: Shifting to a Schema Migration Mindset

  Part 3 of the Parallel Development series. The branch-scoped push script from Part 1 works. Run it on a feature branch where you've touched a...

Vipin Banka | Jul 25, 2026

From AI Agents to AI Workflow with Opal

Introduction In the first article in this series , we talked about AI agents in Optimizely Opal and walked through the process of creating a...

Igor Safonov | Jul 23, 2026

Building Your First Optimizely Opal AI Agent: a Hands-On Walkthrough

Introduction There's a common assumption that building an AI agent is something only engineers or developers can do. It isn't. At the Optimizely AN...

Igor Safonov | Jul 23, 2026

Advanced Task Manager Gets a Big Update

One of the things I keep coming back to with Optimizely's content approval workflow is that it's solid at the individual level - you know a piece o...

Adnan Zameer | Jul 21, 2026 |