2

In the process of building a headless website and one of the first requirements it was to provide JSON schema of my data model (JSON data from my entities.)

Here will be an example of the user entity.

{
    "User": {
        "type": "Object",
        "fields": {
            "first_name": {"type": "String"},
            "last_name": {"type": "String"},
            "age": {"type": "Number"}
        },
    }
}

This does not provide any real values rather than extra information to front-end developers to have an understanding of API. This is also useful for making sure that the API is always valid because we could provide a middleware that tests the API before even gets to the front-end app.

There are more examples of these JSON schemas at - https://www.npmjs.com/package/web-service-api

Do you know if there is an away to get this type of information in Drupal 8 programmatically?

1
  • 1
    TypedData. You'll want to map the entity field definitions to matching JSON schema definitions. May 23, 2016 at 19:13

1 Answer 1

1

The following code exposes an API that returns the field and properties data of the user entity type. It uses a recursive method and the nature of typed_data to achieve such goal.

The post and get methods are called because they are the lowercased versions of GET and POST.

The below code relies on Drupal 8.2 because GET requests are problematic in Drupal 8.1 See this issue

<?php


namespace Drupal\sandbox\Plugin\rest\resource;

use Drupal\Core\Entity\EntityFieldManagerInterface;
use Drupal\Core\Field\FieldStorageDefinitionInterface;
use Drupal\Core\TypedData\ComplexDataDefinitionInterface;
use Drupal\Core\TypedData\DataDefinitionInterface;
use Drupal\Core\TypedData\DataReferenceDefinitionInterface;
use Drupal\Core\TypedData\ListDataDefinitionInterface;
use Drupal\Core\TypedData\PrimitiveInterface;
use Drupal\Core\TypedData\Type\BooleanInterface;
use Drupal\Core\TypedData\Type\FloatInterface;
use Drupal\Core\TypedData\Type\IntegerInterface;
use Drupal\Core\TypedData\Type\StringInterface;
use Drupal\rest\ModifiedResourceResponse;
use Drupal\rest\Plugin\ResourceBase;
use Symfony\Component\DependencyInjection\ContainerInterface;

/**
 * Represents entities as resources.
 *
 * @see \Drupal\rest\Plugin\Deriver\EntityDeriver
 *
 * @RestResource(
 *   id = "user_type",
 *   label = @Translation("User type"),
 *   uri_paths = {
 *     "canonical" = "/user_type",
 *   }
 * )
 */
class UserTypeResource extends ResourceBase {

  /**
   * @var \Drupal\Core\Entity\EntityFieldManagerInterface
   */
  protected $fieldManager;


  /**
   * UserTypeResource constructor.
   * @param array $configuration
   * @param string $plugin_id
   * @param mixed $plugin_definition
   * @param array $serializer_formats
   * @param \Psr\Log\LoggerInterface $logger
   * @param \Drupal\Core\Entity\EntityFieldManagerInterface $field_manager
   */
  public function __construct(array $configuration, $plugin_id, $plugin_definition, array $serializer_formats, \Psr\Log\LoggerInterface $logger, EntityFieldManagerInterface $field_manager) {
    parent::__construct($configuration, $plugin_id, $plugin_definition, $serializer_formats, $logger);
    $this->fieldManager = $field_manager;
  }

  /**
   * {@inheritdoc}
   */
  public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition) {
    return new static(
      $configuration,
      $plugin_id,
      $plugin_definition,
      $container->getParameter('serializer.formats'),
      $container->get('logger.factory')->get('rest'),
      $container->get('entity_field.manager')
    );
  }


  public function post() {
    return $this->resolve();
  }

  public function get() {
    return $this->resolve();
  }

  protected function resolve() {
    $field_definitions = $this->fieldManager->getFieldDefinitions('user', 'user');

    $data = [
      'type' => 'Object',
      'fields' => [],
    ];

    foreach ($field_definitions as $field_name => $field_definition) {
      /** @var \Drupal\Core\Field\FieldStorageDefinitionInterface $storage_definition */
      $storage_definition = $field_definition->getFieldStorageDefinition();
      $data['fields'][$field_name] = [
        'type' => 'Array',
        'element' => $this->transformFieldDefinition($storage_definition),
      ];
    }

    return new ModifiedResourceResponse($data);
  }

  protected function transformFieldDefinition(FieldStorageDefinitionInterface $storage_definition) {
    $fields = [];

    foreach ($storage_definition->getPropertyDefinitions() as $property_name => $property_definition) {
      $fields[$property_name] = $this->transformDataDefinition($property_definition);
    }

    return [
      'type' => 'Object',
      'fields' => $fields,
    ];
  }

  protected function transformDataDefinition(DataDefinitionInterface $definition) {
    if ($definition instanceof ComplexDataDefinitionInterface) {
      $fields = [];

      foreach ($definition->getPropertyDefinitions() as $property_name => $property_definition) {
        $fields[$property_name] = $this->transformDataDefinition($property_definition);
      }

      return [
        'type' => 'Object',
        'fields' => $fields,
      ];
    }
    elseif ($definition instanceof ListDataDefinitionInterface) {
      return [
        'type' => 'Array',
        'element' => $this->transformDataDefinition($definition->getItemDefinition()),
      ];
    }
    elseif ($definition instanceof DataReferenceDefinitionInterface) {
      return $this->transformDataDefinition($definition->getTargetDefinition());
    }
    else {
      $type = "Unknown ({$definition->getDataType()})";

      $class = new \ReflectionClass($definition->getClass());
      if ($class->isSubclassOf(PrimitiveInterface::class)) {

        if ($class->isSubclassOf(StringInterface::class)) {
          $type = 'String';
        }
        elseif ($class->isSubclassOf(IntegerInterface::class) || $class->isSubclassOf(FloatInterface::class)) {
          $type = 'Number';
        }
        elseif ($class->isSubclassOf(BooleanInterface::class)) {
          $type = 'Boolean';
        }
      }
      else {
        switch ($definition->getDataType()) {
          case 'language':
            $type = 'String';
            break;
        }
      }

      return [
        'type' => $type,
      ];
    }
  }


}

The definition of rest.resource.user_type configuration.

id: user_type
plugin_id: 'user_type'
granularity: method
configuration:
  GET:
    supported_formats:
      - json
    supported_auth:
      - cookie
  POST:
    supported_formats:
      - json
    supported_auth:
      - cookie
dependencies: {}

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.