A critical vulnerability was discovered in React Server Components (Next.js). Our systems remain protected but we advise to update packages to newest version. Learn More

Alex Boesen
Apr 25, 2020
  1946
(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

Please login to comment.
Latest blogs
Building simple Opal tools for product search and content creation

Optimizely Opal tools make it easy for AI agents to call your APIs – in this post we’ll build a small ASP.NET host that exposes two of them: one fo...

Pär Wissmark | Dec 13, 2025 |

CMS Audiences - check all usage

Sometimes you want to check if an Audience from your CMS (former Visitor Group) has been used by which page(and which version of that page) Then yo...

Tuan Anh Hoang | Dec 12, 2025

Data Imports in Optimizely: Part 2 - Query data efficiently

One of the more time consuming parts of an import is looking up data to update. Naively, it is possible to use the PageCriteriaQueryService to quer...

Matt FitzGerald-Chamberlain | Dec 11, 2025 |

Beginner's Guide for Optimizely Backend Developers

Developing with Optimizely (formerly Episerver) requires more than just technical know‑how. It’s about respecting the editor’s perspective, ensurin...

MilosR | Dec 10, 2025