How to receive JSON data in controller with Drupal 8?
In case you want to do it more the D8 way here is a rough outline what you could do, which allows you to be flexible without the requirement to do everything in your own. A proper rest integration requires you to work with all the different HTTP status codes, header fields etc. Rest takes partly care of that

The rest module provides a generic abstraction for REST, not tight to entities in the first place. Entities are just one example.

First you should register your rest plugin, like every other plugin:

namespace Drupal\pinguin\Plugin\rest\resource;

use Drupal\rest\Plugin\ResourceBase;

/**
 * Provides a resource for database watchdog log entries.
 *
 * @RestResource(
 *   id = "pinguin",
 *   label = @Translation("Example rest plugin for pinguins"),
 *   uri_paths = {
 *     "canonical" = "/pinguin/{id}"
 *   },
 *   serialization_class = "Drupal\pinguin\PingInterface"
 * )
 */
class Pinguin extends ResourceBase {
}


If you do an updating request like PUSH or UPDATE, it uses the serializer component to convert your data into some php array/ object.
It uses two steps in order to do that. First the data incoming is decoded,
for example from JSON into a PHP array. The next step is the denormalization.
This step converts the denormalized data (PHP array) into a domain object,
for example the Ping (a single pinguin).

The first step is to specify the resulting class in the annotation (see above).
To register a new denormalization you add the following entry into your .services.yml:

services:
  pinguin.denormalizer.pinguin:
    class: Drupal\pinguin\normalizer\PinguinDenormalizer
    tags:
      - { name: normalizer }


This normalizer specifies which interface it can convert to:

&lt;?php

namespace Drupal\pinguin\normalizer;

use \Drupal\serialization\Normalizer\NormalizerBase;

class PinguinDenormalizer extends NormalizerBase {

   // IMPORTANT
   /**
    * The interface or class that this Normalizer supports.
    *
    * @var array
    */
    protected $supportedInterfaceOrClass = array('Drupal\pinguin\PingInterface');

    /**
    * {@inheritdoc}
    */
    public function denormalize($data, $class, $format = NULL, array $context  = array()) {     
      // Validate the data.
      if (empty($data['location']) || $data['location'] == 'northpole') {
        throw new \UnexpectedValueException("this can't be real pinguins");
      }

      // create the domain object.
      return new Ping($data['name'], $data['location'], $data['family']);
    }
 }


Once this serialization is done, it calls a method named after the HTTP method
on the plugin class, so for example "post". There you can do whatever you want.

&lt;?php
class Pinguin extends ResourceBase {
  public function post(PingInterface $ping) {
    // deal with the data, save it for example.
    $ping_storage-&gt;save($ping):
  }
}


The advantages of these abstractions are that you don't have to deal with the incoming format but on the other hand it needs a bunch of code to get started.