bezzant.dev

Sitecore JSS exclude fields from serialization

Out of the box, Sitecore JSS will serialize everything except standard fields (double underscore fields). It's not uncommon to have a handful of fields on items that are purely for internal use or to complement something you're doing with a Content Resolver or the Layout Context - things that don't need to be in the layout service response.

Perhaps you want something exposed to graph but not the layout service, or vice versa. We have all these scenarios in our current build.

The easy solution is to rename fields we want to exclude __customField and be done with it. I'm not down with these sorts of magic names to change behaviour, not to mention mixing custom fields with standard field naming conventions.

I want to be more explicit than how I name a field - when I create my field, I want the option to opt out of serialization.


We'll tackle the layout service and graph independently, but they're broadly the same.

Setup

We're going to add a couple of checkbox fields to the system Template field template located at /sitecore/templates/System/Templates/Template field

  • ExcludeFromLayoutService
  • ExcludeFromGraphQL

I'm not usually keen on modifying system templates, but from a serialization perspective, it's not invasive, as we're just adding a fields so don't need to serialize the system items and commit them into our repository.

Layout Service

Sitecore provides us with a JssItemSerializer this inherits DefaultItemSerializer, the latter of which is where things get more interesting:

public class DefaultItemSerializer : BaseItemSerializer
{
	..
	protected override bool FieldFilter(Field field)
	{
		return !field.Name.StartsWith("__", StringComparison.Ordinal);
	}
	..
}

So that's how they take care of the double underscore fields and nicely exposes a method that we can hook our solution into.

Lets implement our own JssItemSerializer, override FieldFilter and plug our new field into the test to determine if it's included:

namespace Application.Foundation.JSS.Serializer
{
	public class BetterJssItemSerializer : JssItemSerializer
	{
		public BetterJssItemSerializer(IGetFieldSerializerPipeline getFieldSerializerPipeline)
			: base(getFieldSerializerPipeline)
		{
		}

		protected override bool FieldFilter(Field field)
		{
			CheckboxField excludeField = field?.InnerItem?.Fields["ExcludeFromLayoutService"];
			var exclude = excludeField?.Checked ?? false;

			// we want to return true for to include it in serialization
			// consider the base to handle `__Field`
			return base.FieldFilter(field) && !exclude;
		}
	}
}

We just need to patch config and replace the type with our new BetterJssItemSerializer the default can be found at this path:

configuration/sitecore/layoutService/config[@name='jss']/rendering/itemSerializer

GraphQL

The graph serialization is slightly different because of how the content schema generation works for items. They provide a template predicate which defines a fieldFilter node in the config for graph. This one if a bit more flexible as it allows for configuration of an exclusion list.

Here's an example of the default config;

<fieldFilter type="Sitecore.Services.GraphQL.Content.TemplateGeneration.Filters.StandardFieldFilter, Sitecore.Services.GraphQL.Content">
	<exclusions hint="raw:AddFilter">
		<!-- Remove system fields from the GraphQL types in the strongly typed API (e.g. __Layout) wildcards are allowed. -->
		<exclude name="__*" />
		<!-- You can also exclude fields from the schema specifically by field ID
		<exclude fieldId="{8FB875EB-3AD3-44FF-87E1-998370CC3199}" /> -->
	</exclusions>
</fieldFilter>

These exclusions give a bit more flexibility, but not enough, so lets implement our own.

A quick peak at this StandardFieldFilter type and we have the following method of interest:

namespace Sitecore.Services.GraphQL.Content.TemplateGeneration.Filters
{
	public class StandardFieldFilter : IFieldFilter
	{
		..
		public virtual bool Includes(TemplateFieldItem field)
		{
			// checks the field name against exclude name list
			// checks the field id against exclude fieldId list
			// returns `true` to include and `false` to exclude
		}
		..
  }
}

We can replicate the solution we used previously by implementing our own StandardFieldFilter and including a check against our custom checkbox field we added.

namespace Application.Foundation.JSS.GraphQL
{
	public class BetterFieldFilter : StandardFieldFilter
	{
		public BetterFieldFilter() : base()
		{
		}

		public override bool Includes(TemplateFieldItem field)
		{
			CheckboxField excludeField = field?.InnerItem?.Fields["ExcludeFromGraphQL"];
			var exclude = excludeField?.Checked ?? false;

			// we want to return true for to include it in serialization
			// consider the base to handle `__Field`
			return base.Includes(field) && !exclude;
		}
	}
}

You've likely got an api/GraphQL configuration node alongside your site node. You want to replace the type with our new BetterFieldFilter.

configuration/sitecore/api/GraphQL/defaults/content/schemaProviders/yourSchemaProvider/templates/fieldFilter

Conclusion

This now gives us the flexibility of opting every individual field we create out of serialization for both the layout service and graph independently. A useful tool for keeping the layout service responses lean and keeping internal data away from the client.

You can take this solution a step further and switch out the checkbox fields for a droplist/droplink and give some extra options. In our implementation, we have options to;

  • Include everywhere (the default)
  • Include CM (only only master db)
  • Exclude everywhere

This means we can allow somethings to surface in our CM environment where we might want something visible in the Experience Editor but over on Content Delivery we keep them firmly excluded.