REST export for nested lists of content Ok here is my problem;

I have a list of boats. Each boat have a list of engines that can be fitted to it. Engine is not in anyway coupled with boat as a particular engine can be mounted to any kind of boat.

Boat is a content type, Engine is a content type. Boat has a field of multiple engines in it.

I want this to output in json like this:

[{
    "title":"boat_name",
    "engines":[{
                 "title":"engine_name",
                 "model":"model_nr"
               },{
                 "title":"engine_name2",
                 "model":"model_nr2"
               }]
}]


I've tried to accomplish this through the RESTful web service module by creating a REST export view, but there is no formatter that seem to give me json output. I've understood that you could accomplish this by using View Field View but in my case it would require the engine to have a reference to a specific boat, am I right? That is not really what I want.

Is there any other way I can accomplish this?
You can create a custom Normalizer to use and Views will respect that. Within that, you can create the output structure however you like.

Here is an example class, where "Collection" is the content type:

&lt;?php

declare(strict_types = 1);

namespace Drupal\mymodule\Normalizer;

use Drupal\Core\Entity\EntityTypeManagerInterface;
use Drupal\Core\Url;
use Drupal\image\Entity\ImageStyle;
use Drupal\serialization\Normalizer\ContentEntityNormalizer;
use Drupal\node\NodeInterface;

/**
 * Class CollectionNormalizer.
 *
 * @package Drupal\mymodule\Normalizer
 */
class CollectionNormalizer extends ContentEntityNormalizer {

  /**
   * The interface or class that this Normalizer supports.
   *
   * @var string
   */
  protected $supportedInterfaceOrClass = [
    'Drupal\node\NodeInterface',
  ];

  /**
   * The entity manager.
   *
   * @var \Drupal\Core\Entity\EntityTypeManagerInterface
   */
  protected $entityTypeManager;

  /**
   * {@inheritdoc}
   */
  public function __construct(EntityTypeManagerInterface $entity_type_manager) {
    $this-&gt;entityTypeManager = $entity_type_manager;
  }

  /**
   * {@inheritdoc}
   */
  public function supportsNormalization($data, $format = NULL) {
    if (!is_object($data) || !$this-&gt;checkFormat($format)) {
      return FALSE;
    }

    if ($data instanceof NodeInterface &amp;&amp; $data-&gt;getType() == 'collection') {
      return TRUE;
    }

    return FALSE;
  }

  /**
   * {@inheritdoc}
   */
  public function normalize($entity, $format = NULL, array $context = []) {
    $attributes = [];
    $attributes['id'] = $entity-&gt;id();
    $attributes['title'] = $entity-&gt;label();
    $attributes['summary'] = $entity-&gt;get('field_summary')-&gt;value;
    $attributes['card_type'] = $entity-&gt;get('field_directory_view')-&gt;value;
    $attributes['list_image'] = NULL;
    $attributes['url'] = Url::fromRoute('entity.node.canonical', ['node' =&gt; $attributes['id']])-&gt;toString();

    $media = $entity-&gt;get('field_featured_image')-&gt;getValue();

    if (!empty($media)) {
      $item = $this-&gt;entityTypeManager-&gt;getStorage('media')-&gt;load($media[0]['target_id']);
      $image = $item-&gt;get('image')-&gt;getValue();

      $attributes['list_image'] = [
        'src' =&gt; ImageStyle::load('collection_listing')
          -&gt;buildUrl($item-&gt;image-&gt;entity-&gt;getFileUri()),
        'alt' =&gt; $image[0]['alt'],
      ];
    }

    return $attributes;
  }

}


You also have to declare it in your modules services.yml file:

  mymodule.collection_entity:
    class: Drupal\mymodule\Normalizer\CollectionNormalizer
    arguments: ['@entity_type.manager']
    tags:
      - { name: normalizer, priority: 10 }


You can inject whatever other services you need, used entityTypeManager as an example.

Views will use this instead of the default, so long as the priority is higher. IMO this is far easier than trying to use the Views UI to get the output you want from a REST Export display. You are free to construct the output in whatever way you wish.

So in your case, you would replace "Collection" with "Boat" and check that the content type is 'boat', then build the response as you need it.