Calling all developers! We invite you to provide your input on Feature Experimentation by completing this brief survey.

 

Alex Boesen
Apr 25, 2020
  1580
(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
Image Analyzer with AI Assistant for Optimizely

The Smart Image Analyzer is a new feature in the Epicweb AI Assistant for Optimizely CMS that automates the management of image metadata, such as...

Luc Gosso (MVP) | Jan 16, 2025 | Syndicated blog

How to: create Decimal metafield with custom precision

If you are using catalog system, the way of creating metafields are easy – in fact, you can forget about “metafields”, all you should be using is t...

Quan Mai | Jan 16, 2025 | Syndicated blog

Level Up with Optimizely's Newly Relaunched Certifications!

We're thrilled to announce the relaunch of our Optimizely Certifications—designed to help partners, customers, and developers redefine what it mean...

Satata Satez | Jan 14, 2025

Introducing AI Assistance for DBLocalizationProvider

The LocalizationProvider for Optimizely has long been a powerful tool for enhancing the localization capabilities of Optimizely CMS. Designed to ma...

Luc Gosso (MVP) | Jan 14, 2025 | Syndicated blog