You can create a module and extend ContentEntityNormalizer to provide a custom structure.
Example:
<?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;
/**
* Converts the Drupal entity object structures to a normalized array.
*/
class NodeEntityNormalizer 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->entityTypeManager = $entity_type_manager;
}
/**
* {@inheritdoc}
*/
public function normalize($entity, $format = NULL, array $context = []) {
$type = $entity->getType();
$methods = [
'library' => 'normalizeLibrary',
'collection' => 'normalizeCollection',
];
if (isset($methods[$type])) {
return $this->{$methods[$type]}($entity, $format, $context);
}
return parent::normalize($entity, $format, $context);
}
/**
* Normalize a library content type.
*/
protected function normalizeLibrary($entity) {
$attributes['id'] = $entity->id();
$attributes['featured'] = !empty($entity->get('field_is_coming_soon')->value);
$attributes['external'] = !empty($entity->get('field_is_stub')->value) && !empty($entity->get('field_website_link')[0]);
$attributes['name'] = $entity->label();
$attributes['subTitle'] = !empty($entity->get('field_subtitle')->value) ? $entity->get('field_subtitle')->value : '';
return $attributes;
}
/**
* Normalize a collection content type.
*
* @param \Drupal\node\NodeInterface $entity
* The node entity.
*
* @return array
* The item attributes.
*/
protected function normalizeCollection(NodeInterface $entity) : array {
$attributes = [];
$attributes['id'] = $entity->id();
$attributes['title'] = $entity->label();
$attributes['subtitle'] = $entity->get('field_subtitle')->value;
$attributes['summary'] = $entity->get('field_summary')->value;
$attributes['card_type'] = $entity->get('field_directory_view')->value;
$attributes['list_image'] = NULL;
$attributes['card_image'] = NULL;
$attributes['url'] = Url::fromRoute('entity.node.canonical', ['node' => $attributes['id']])->toString();
$media = $entity->get('field_featured_image')->getValue();
if (!empty($media)) {
$item = $this->entityTypeManager->getStorage('media')->load($media[0]['target_id']);
$image = $item->get('image')->getValue();
$attributes['list_image'] = [
'src' => ImageStyle::load('collection_listing')
->buildUrl($item->image->entity->getFileUri()),
'alt' => $image[0]['alt'],
];
switch ($attributes['card_type']) {
case 'wide':
$attributes['card_image'] = [
'src' => ImageStyle::load('collection_listing_wide')
->buildUrl($item->image->entity->getFileUri()),
'alt' => $image[0]['alt'],
];
break;
case 'tall':
$attributes['card_image'] = [
'src' => ImageStyle::load('collection_listing_tall')
->buildUrl($item->image->entity->getFileUri()),
'alt' => $image[0]['alt'],
];
break;
case 'standard':
$attributes['card_image'] = $attributes['list_image'];
break;
default:
break;
}
}
return $attributes;
}
}
This code only applies to Node entities, and only for two content types (you will want to specify them, since this will override anything matching the $supportedInterfaceOrClass
property.
You will need to add it to your custom modules services.yml file:
services:
mymodule.node_entity:
class: Drupal\mymodule\Normalizer\NodeEntityNormalizer
arguments: ['@entity_type.manager']
tags:
- { name: normalizer, priority: 8 }
The View will now use this normalizer if the conditions match and you can structure the output however you like. This gives you the power say, to reach into entity references and pull in values or do conditional things (like the custom image size I am passing in normalizeCollection
depending on a field value).
Note that my example is custom and does not do things like check field level access for the user, since we do not have any restricted fields.