Generating automatized JSON as output

By Juan Basso (juan.basso)
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:

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 0reset($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 836

CakePHP Team Comments Author Comments
 

Comment

1 what about RequestHandler?

Unless I misunderstand the purpose of this approach...

I think using RequestHandler->isAjax()

and making a view with:

$javascript->object($yourData);

will handle the rest automagically
Posted Oct 28, 2008 by teknoid
 

Comment

2 A simpler way?

If the point is to avoid making a view then


$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));
Posted Oct 30, 2008 by Daniel Guffen
 

Comment

3 ... but not MVC

If the point is to avoid making a view then


$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));

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
Posted Oct 30, 2008 by teknoid
 

Comment

4 Automating JSON processing

To save myself the hassle of having to declare something as JSON, I use the RequestHandler to include additional logic in the AppController::beforeRender().


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.
Posted Nov 7, 2008 by Jonathan Snook
 

Comment

5 JSON scaffolding

The ideal solution IMHO will be JSON scaffolding based on ext. Any ideas ? While at that subject, I won't mind a generic solution to scaffolding and thereby derive XML, JSON, RSS, even PDF output just by changing the ext.

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?>
Posted Dec 13, 2008 by ajay malhotra
 

Comment

6 Additional headers

Just my two cents: I combined Jonathan Snook's method with Pagebaker's (http://bit.ly/ge0k) to create the following (excuse my unfamiliarity with bbcode):

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
Posted Dec 27, 2008 by Darren