How to alter JSON responses with Drupal 8's JSON:API and REST Web Service

Chris Geelhoed
5 min readOct 17, 2019

My team has built a lot of headless websites recently. Nuxt has typically been our front end framework of choice, but we have now worked with a number of different backend stacks including Laravel, Wordpress, and Drupal.

I’ve enjoyed all three of those options, but have found documentation to be easier to find with Laravel and Wordpress. This can be disappointing because Drupal has a lot to like, but I recognize that the CMS is free software and I’m thankful to their team for providing a great platform to the public.

One specific challenge I’ve tackled lately is altering the JSON response sent from the JSON:API and REST Web Service modules (both of which are core modules designed to support JSON payloads). A custom field I developed had used a hidden input field to save a collection of data in a way that was compatible with Drupal’s form system. This approach has turned out well so far, but came with a problem — the field appeared as a simple string when fetched.

Pre-normalization output

Additionally, this output structure was found to be a bit awkward to work with on the front end — instead of indexing each resource with a unique ID, it would be cleaner to provide the collection as a simple array. These weren’t critical problems, but it would be nice to fix them.

After some research, I found one solution candidate was creating a custom normalizer. I was fortunate enough to find a very good guide written by Edward Chan a few years ago, and it served as a great resource: https://www.mediacurrent.com/blog/using-normalizers-alter-rest-json-structure-drupal-8

Between that and Drupal’s docs, I was able to put together a working solution in a couple hours (which ultimately had some problems). Edward’s tutorial showed how TypedData and Nodes can be normalized. I wasn’t able to get the TypedData example working, but I found that I was able to normalize Nodes and tweaking that to work with FieldItems wasn’t too bad. I saw the data was formatted correctly when viewed through the REST Web Service and was happy to commit my code and call it a day.

Unfortunately, I soon learned that the front end was using JSON:API to fetch this data, not the REST module, and somehow the normalization wasn’t taking effect in that case. Turning back to the web for help, things began to become clear:

  1. The JSON:API module and the REST Web Service are different things
  2. The JSON:API has been discouraging normalization of custom fields for a while.
  3. As of JSON:API 2.X, custom field normalization isn’t supported at all.

More details can be read in this blog post by Mateu Aguiló Bosch — https://www.lullabot.com/articles/jsonapi-2

In that post, Mateu outlines 3 alternatives to alter JSON responses in the new release:

  1. Use a computed/virtual field. This derivative field can be saved or generated dynamically, but in my research I found the latter seemed to be more commonly use for this problem. One way to do this is with HOOK_entity_base_field_info . This allows you to attach an additional field to your payload. I didn’t like this option for my use case because it wasn’t clear what data definition to use, and I didn’t want my output to be messy.
  2. Write a normalizer at the Data Type level, not the field or entity level. I liked this option best because it was similar to the solution already in place. The issue was that I couldn’t find much in the way of documentation on how to actually do this. Edward’s post did cover something similar, but that implementation didn’t work for me.
  3. Install the JSON:API Extras module and create a field enhancer. While the base JSON:API module now appears in Drupal core, an additional module is available to provide extra functionality. Among this functionality is a way to alter how fields display in your JSON output.

The third option seemed to be the winner, but actually implementing it was a struggle. I identified a few threads that acknowledged this:

The consensus was that the best way to make this happen was to read the source code and figure it out. I gave this a shot, but it wasn’t entirely clear to me how this worked, and from what I understood it came with some of the issues that the computed property had.

After spinning my wheels for some time, I refocused on normalization and looked into Media Current’s tutorial again. With enough tinkering and documentation browsing I saw that a class specific to typed data existed called TypedDataNormalizer , but the tutorial was extending the base NormalizerBase class. Making this substitution finally gave me the result I was looking for! This makes sense, because TypedDataNormalizer was also using NormalizerBase as a base class. Maybe this change was introduced after the Media Current post was made in 2017.

All in all, this was what I found worked for both the JSON:API and REST Web Service module. I hope that it is valuable to someone.

First, create or edit your MY_MODULE/MY_MODULE.services.yml file. It’s important that the priority be set high enough to outrank the existing typed data normalizer.

services:
MY_MODULE.typed_data:
class: Drupal\kc_library\Normalizer\MyNormalizerClass
tags:
- { name: normalizer, priority: 10 }

Then create that class in MY_MODULE/src/Normalizer/MyNormalizerClass.php :

<?phpnamespace Drupal\MY_MODULE\Normalizer;use Drupal\serialization\Normalizer\TypedDataNormalizer;/**
* {@inheritdoc}
*/
class MyNormalizerClass extends TypedDataNormalizer {
/**
* {@inheritdoc}
*/
public function normalize($entity, $format = NULL, array $context = []) {
$data = parent::normalize($entity, $format, $context);
// transform your data here
// You'll likely need to run some checks on the $entity or $data
// variables and include conditionals so that only the items
// you are interested in are altered
return $data;
}
}

For my application, the transform included decoding the JSON String and running that output through array_values to drop the keys.

Post-normalization output

--

--