Q:

Cakephp3: How can I return json data? 

I am having a ajax post call to a cakePhp Controller:
$.ajax({
                type: "POST",
                url: 'locations/add',
                data: {
                  abbreviation: $(jqInputs[0]).val(),
                  description: $(jqInputs[1]).val()
                },
                success: function (response) {
                    if(response.status === "success") {
                        // do something with response.message or whatever other data on success
                        console.log('success');
                    } else if(response.status === "error") {
                        // do something with response.message or whatever other data on error
                        console.log('error');
                    }
                }
            });

When I try this I get the following error message:

Controller actions can only return Cake\Network\Response or null.  

Within the AppController I have this
$this->loadComponent('RequestHandler');

enabled. 
the Controller function looks  like this:
public function add()
{
    $this->autoRender = false; // avoid to render view

    $location = $this->Locations->newEntity();
    if ($this->request->is('post')) {
        $location = $this->Locations->patchEntity($location, $this->request->data);
        if ($this->Locations->save($location)) {
            //$this->Flash->success(__('The location has been saved.')); 
            //return $this->redirect(['action' => 'index']);
            return json_encode(array('result' => 'success'));
        } else {
            //$this->Flash->error(__('The location could not be saved.  Please, try again.')); 
            return json_encode(array('result' => 'error'));
        }
    }
    $this->set(compact('location'));
    $this->set('_serialize', ['location']);
}

What do I miss here?  Is there any additional settings needed? 

A:

Instead of returning the json_encode result, set the response body with that result and return it back. 
public function add()
{
    $this->autoRender = false; // avoid to render view

    $location = $this->Locations->newEntity();
    if ($this->request->is('post')) {
        $location = $this->Locations->patchEntity($location, $this->request->data);
        if ($this->Locations->save($location)) {
            //$this->Flash->success(__('The location has been saved.')); 
            //return $this->redirect(['action' => 'index']);
            $resultJ = json_encode(array('result' => 'success'));
            $this->response->type('json');
            $this->response->body($resultJ);
            return $this->response;
        } else {
            //$this->Flash->error(__('The location could not be saved.  Please, try again.')); 
            $resultJ = json_encode(array('result' => 'error', 'errors' => $location->errors()));

            $this->response->type('json');
            $this->response->body($resultJ);
            return $this->response;
        }
    }
    $this->set(compact('location'));
    $this->set('_serialize', ['location']);
}

Edit (credit to @Warren Sergent)
Since CakePHP 3.4, we should use 
return $this->response->withType("application/json")->withStringBody(json_encode($result));

Instead of :
$this->response->type('json');
$this->response->body($resultJ);
return $this->response;

CakePHP Documentation

A:

Most answers I've seen here are either outdated, overloaded with unnecessary information, or rely on withBody(), which feels workaround-ish and not a CakePHP way. 
Here's what worked for me instead:
$my_results = ['foo'=>'bar'];

$this->set([
    'my_response' => $my_results,
    '_serialize' => 'my_response',
]);
$this->RequestHandler->renderAs($this, 'json');

More info on RequestHandler.  Seemingly it's not getting deprecated anytime soon. 
UPDATE: CakePHP 4
$this->set(['my_response' => $my_results]);
$this->viewBuilder()->setOption('serialize', true);
$this->RequestHandler->renderAs($this, 'json');

More info

A:

there are few things to return JSON response:

load RequestHandler component
set rendering mode as json
set content type
set required data
define _serialize value

for example you can move first 3 steps to some method in parent controller class:
protected function setJsonResponse(){
    $this->loadComponent('RequestHandler');
    $this->RequestHandler->renderAs($this, 'json');
    $this->response->type('application/json');
}

later in your controller you should call that method, and set required data;
if ($this->request->is('post')) {
    $location = $this->Locations->patchEntity($location, $this->request->data);

    $success = $this->Locations->save($location);

    $result = [ 'result' => $success ? ' success' : 'error' ];

    $this->setJsonResponse();
    $this->set(['result' => $result, '_serialize' => 'result']);
}

also it looks like you should also check for request->is('ajax); I'm not sure about returning json in case of GET request, so setJsonResponse method is called within if-post block;
in your ajax-call success handler you should check result field value:
success: function (response) {
             if(response.result == "success") {
                 console.log('success');
             } 
             else if(response.result === "error") {
                    console.log('error');
             }
         }

