Alex Boesen
Apr 25, 2020
  2079
(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
Searchable settings page

In my current project which has been actively developed for quite some time we have a big classig settings page. Unfortunately the placement and...

Per Nergård (MVP) | Apr 6, 2026

Forcing Lowercase URLs in Optimizely CMS During Auto-Translation

Learn how to fix uppercase and punctuation issues in Optimizely CMS 12 URL segments caused by LanguageManager auto-translation using a custom...

Stuart | Apr 2, 2026 |

Stott Robots Handler v7 for Optimizely CMS 13

Stott Robots Handler version 7 is now available for  Optimizely PaaS CMS 13 . This is includes updates to support the switch from a Site based...

Mark Stott | Apr 2, 2026

Automating Block Translations in Optimizely

Stop manual block-by-block translations. Learn how to use the TranslateOrCopyContentAreaChildrenBlockForTypes config to automate Optimizely CMS...

Stuart | Apr 1, 2026 |