JSON Schema of my entities 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?
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

&lt;?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-&gt;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-&gt;getParameter('serializer.formats'),
      $container-&gt;get('logger.factory')-&gt;get('rest'),
      $container-&gt;get('entity_field.manager')
    );
  }


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

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

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

    $data = [
      'type' =&gt; 'Object',
      'fields' =&gt; [],
    ];

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

    return new ModifiedResourceResponse($data);
  }

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

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

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

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

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

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

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

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

      return [
        'type' =&gt; $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: {}