Search form

You are here

Using Normalizers to Alter REST JSON Structure in Drupal 8

Edward

Drupal Developer

Mar

22

2017

Using Normalizers to Alter REST JSON Structure in Drupal 8

Overview

Drupal 8 core provides for solid REST capabilities out-of-the-box, which is great for integrating with a web service or allowing a third-party application to consume content. However, the REST output provided by Drupal core is in a certain structure that may not necessarily satisfy the requirements as per the structure the consuming application expects.

In comes normalizers that will help us alter the REST response to our liking. For this example, we will be looking at altering the JSON response for node entities.

Getting Started

First, let’s install and enable the latest stable version of the “REST UI” module:

composer require drupal/restui;drush en restui -y;

Go to the REST UI page (/admin/config/services/rest) and enable the “Content” resource:

Create a test node and fill in one or all of the fields. We will be requesting and altering the structure of the core node REST resource for this node output.

After you’ve created your node, append ?_format=json to the end of your node’s URL (so it looks something like /node/1?_format=json) and access that page. You should see a JSON dump with the field names and the values for the node entity similar to:

We set our $supportedInterfaceOrClass protected property to Drupal\Core\TypedData\TypedDataInterface (so we can make some low-level modifications with the values for the entity). This means that this normalizer supports any object that is an instance of Drupal\Core\TypedData\TypedDataInterface. In the normalize() method, we check if the value contains the [0][‘value’] elements, and if so, just return the plain value stored in there. This will effectively remove the “value” keys from the output.

example_normalizer.services.ymlWe need to allow Drupal to detect this normalizer, so we put it in our *services.yml and tag the service with “normalizer”:

In order to have our custom normalizer get picked up first, we need to set a priority higher than the one that already exists that supports the same interface/class. When the serializer requests the normalize operation, it will process each normalizer sequentially until it finds one that applies. This is the “Chain-of-Responsibility” (COR) pattern used by Drupal 8 where each service processes the objects it supports and the rest are passed to the next processing service in the chain.

Make sure to clear the cache so the new normalizer service is detected.

If we go to our JSON output for the node again, we can see that the output has changed a bit. We no longer see the nested “values” keys being displayed (and looks much cleaner, as well):

Similar to before, we have to define our supported interface or class. For this normalizer, we only want to support node entities, so we set it to Drupal\node\NodeInterface (which means any object that implementsDrupal\node\NodeInterface).

Our custom node normalizer extends the Drupal\serialization\Normalizer\ContentEntityNormalizer class that is provided by the “serialization” module. The only thing we want to do is append 2 new values to the output to what is already provided -- the “link” and “changed_iso8601” values.

To format our timestamp, we get the timestamp from the entity object, create a DrupalDateTime object, and then format it using the PHP date format “c” character for ISO 8601. This value will be assigned to our new key on the$attributes array that holds all the values.

We will create the “link” value by using the toUrl()and toString()methods to get the URL which gets assigned to the “link” key of the $attributes array.

After clearing cache and visiting the node output again, we do indeed see our new additions:

Altering a specific node entity type JSON output

So, we were able to alter the output for all nodes, but there may be cases where we would only want to alter the JSON output of specific node types. Fortunately, there isn’t much more to do than what we already learned. We will create a normalizer that contains a custom “changed” timestamp format that will only apply to “article” nodes.

In this normalizer, you will notice that we’re defining a new method. public function supportsNormalization($data, $format = null) {} allows for performing more granular checks for the objects that are instances of the class/interface defined in $supportedInterfaceOrClass. In our supportsNormalization(), we check if the type of the node is an “article” using the getType()function. If so, it returns TRUE - indicating that this normalizer supports the node. Otherwise, it will return FALSE - and using the COR pattern, it will process the next normalizer in sequence until it finds one that applies to the object.

Let’s take a look at the JSON output of a test “article” node. We do indeed see our custom attribute “article_changed_format” from our custom “article” normalizer:

When we look at the REST response of another content type (like a “page”), we do not see this custom attribute because it is not an “article” and the normalizer did not apply to it. It does, however, pick up the next normalizer in sequence, which happens to be the custom node normalizer we created earlier:

Notes

In determining how to implement a normalizer for your complex data, the normalizers in the “serialization” module serve as a great guide in learning how normalizers work and how different types of data are normalized.