JSON Schema of my entities
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: {}