Generating automatized JSON as output
This article will show how you can generate JSON (JavaScript Object Notation) output without make a layout or ctp for this.
I have created a View to use in cases that you will return JSON as output (exemple: request AJAX).
The View is below:
This view need copied to app/views/json.php. Later, to use is simple, see the code of controller:
Simple and fast. If you want, you can set a var $view in controller as 'Json' for all, because if not have one set to 'json', this will use traditional View.
With this view, you no need layout or ctp for show the JSON results, the View Json will return it for you.
The View is below:
Helper Class:
Download code
<?php
/**
* Class of view for JSON
*
* @author Juan Basso
* @url http://blog.cakephp-brasil.org/2008/09/11/trabalhando-com-json-no-cakephp-12/
* @licence MIT
*/
class JsonView extends View {
function render($action = null, $layout = null, $file = null) {
if (!isset($this->viewVars['json'])) {
return parent::render($action, $layout, $file);
}
$vars = $this->viewVars['json'];
if (is_string($vars)) {
return $this->renderJson($this->viewVars[$vars]);
}
if (is_array($vars)) {
$jsonVars = array();
foreach ($vars as $var) {
if (isset($this->viewVars[$var])) {
$jsonVars[$var] = $this->viewVars[$var];
} else {
$jsonVars[$var] = null;
}
}
return $this->renderJson($jsonVars);
}
return 'null';
}
function renderJson($content) {
header('Content-type: application/json');
if (function_exists('json_encode')) {
// PHP 5.2+
$out = json_encode($content);
} else {
// For PHP 4 until PHP 5.1
$out = $this->_jsonEncode($content);
}
Configure::write('debug', 0); // Omit time in end of view
return $out;
}
// Adapted from http://www.php.net/manual/en/function.json-encode.php#82904. Author: Steve (30-Apr-2008 05:35)
function _jsonEncode($content) {
if (is_null($content)) {
return 'null';
}
if ($content === false) {
return 'false';
}
if ($content === true) {
return 'true';
}
if (is_scalar($content)) {
if (is_float($content)) {
return floatval(str_replace(",", ".", strval($content)));
}
if (is_string($content)) {
static $jsonReplaces = array(array("\\", "/", "\n", "\t", "\r", "\b", "\f", '"'), array('\\\\', '\\/', '\\n', '\\t', '\\r', '\\b', '\\f', '\"'));
return '"' . str_replace($jsonReplaces[0], $jsonReplaces[1], $content) . '"';
} else {
return $content;
}
}
$isList = true;
for ($i = 0, reset($content); $i < count($content); $i++, next($content)) {
if (key($content) !== $i) {
$isList = false;
break;
}
}
$result = array();
if ($isList) {
foreach ($content as $v) {
$result[] = $this->_jsonEncode($v);
}
return '[' . join(',', $result) . ']';
} else {
foreach ($content as $k => $v) {
$result[] = $this->_jsonEncode($k) . ':' . $this->_jsonEncode($v);
}
return '{' . join(',', $result) . '}';
}
}
}
?>
This view need copied to app/views/json.php. Later, to use is simple, see the code of controller:
Controller Class:
Download code
<?php
class UserController extends AppController {
var $uses = array('User', 'Group');
function index($json = false) {
$this->set('users', $this->User->find('list'));
if ($json) {
$this->view = 'Json';
$this->set('json', 'users');
}
}
function multilist($json = false) {
$this->set('users', $this->User->find('list'));
$this->set('groups', $this->Group->find('list'));
if ($json) {
$this->view = 'Json';
$this->set('json', array('users', 'groups'));
}
}
}
?>
Simple and fast. If you want, you can set a var $view in controller as 'Json' for all, because if not have one set to 'json', this will use traditional View.
With this view, you no need layout or ctp for show the JSON results, the View Json will return it for you.
Comments
Comment
1 what about RequestHandler?
I think using RequestHandler->isAjax()
and making a view with:
$javascript->object($yourData);
will handle the rest automagically
Comment
2 A simpler way?
$this->autoRender = false;
inside the controller method should do it. Then just output JSON in whichever way you like at the end of the method. For example:
e(json_encode($data));
Comment
3 ... but not MVC
Although this is simpler, it breaks MVC. The view should be the one displaying the data (not controller). What if I decide later that the output should be XML?
Again, using RequestHandler is a correct CakePHP approach, but using respondAs() in this case.
Please review: http://book.cakephp.org/view/174/Request-Handling
Comment
4 Automating JSON processing
if ($this->RequestHandler->ext == 'json') {
$this->view = 'Json';
if (!isset($this->viewVars)) {
$this->set('empty', '' );
}
$this->set('json', array_keys($this->viewVars));
}
Firstly, if it's a JSON request, it automatically sets the view, looks for viewVars, and then uses the array_keys to automatically JSONize anything in the viewVars. This keeps my controller methods leaner and I can access the data in a predictable way whether it's a JSON call or a standard call.
Comment
5 JSON scaffolding
I am using the following solution right now:
A views/countries/json/index.ctp with
View Template:
<?php echo $javascript->object($countries); ?>
And layouts/json/default.ctp with
View Template:
<?php echo $content_for_layout; ?>
Comment
6 Additional headers
In controller:
if ($this->RequestHandler->isAjax())
{
$json = '';
.. do magic ..
$this->set(compact('json'));
$this->render('../elements/json');
}
Element: json.ctp
<?php
header("Pragma: no-cache");
header("Cache-Control: no-store, no-cache, max-age=0, must-revalidate");
header('Content-Type: application/json');
header("X-JSON: ".$javascript->object($json));
echo $javascript->object($json);
?>
Using an element lets me reuse the json view across controllers.
By setting the headers, especially Content-Type to 'application/json' Prototype will automatically parse JSON responses (set evalJSON to true in your Ajax.request options) and place them in response.responseJSON for immediate use.
Darren -
www.gabardinestudios.com