Take the community feedback survey now.

Alex Boesen
Apr 25, 2020
  1810
(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
A day in the life of an Optimizely Developer - We Hacked the Future: Netcel's Opal Hackathon Adventure

Ever wondered what happens when you mix  AI ,  creativity , and a dash of competitive spirit? Welcome to the  Opal Hackathon 2025 —where we rolled ...

Graham Carr | Aug 31, 2025

Simple Feature Flags In Optimizely CMS

The Problem I was working with a CMS 11 client who wanted to introduce two new third party content sources. These would be synchronized into new...

Mark Stott | Aug 31, 2025

SQL addon for Optimizely CMS 12 updated with new features

First released in 2021, the SQL Studio addon for Optimizely CMS lets you query your database directly from the edit UI. The latest update adds...

Tomas Hensrud Gulla | Aug 29, 2025 |

🎓 New Certification Icons Are Now Live on World!

If you’ve passed an Optimizely Academy certification, you’ll now see something new beside your name on World —  fresh certification icons are live...

Satata Satez | Aug 29, 2025