Tip - Using $H With PHP json_encode()

The Problem

$H() is very helpful function used often to deal with so called associative arrays generated from PHP scripts. PHP uses json_encode() function to convert it's native arrays or object to JSON format.
While numerically indexed arrays are simply converted to native Javascript arrays, associative arrays are converted to Javascript objects.

<?php
$numericArray = array(
    'one', 'two', 'three'
);
$associativeArray = array(
    'first'  => 'one',
    'second' => 'two',
    'third'  =>'three'
);
 
echo 'var numericArray     = ' . json_encode($numericArray) . ";\n" ;
echo 'var associativeArray = ' . json_encode($associativeArray) . ";\n" ;
 
/* lets empty those arrays */
unset($numericArray[0]);
unset($numericArray[1]);
unset($numericArray[2]); 
unset($associativeArray['first']);
unset($associativeArray['second']);
unset($associativeArray['third']);
 
echo 'var emptyNumericArray     = ' . json_encode($numericArray) . ";\n" ;
echo 'var emptyAssociativeArray = ' . json_encode($associativeArray) . ";\n" ;

The PHP code above generates this output:

var numericArray     = ["one","two","three"];
var associativeArray = {"first":"one","second":"two","third":"three"};
var emptyNumericArray     = [];  //This is empty numerically indexed array
var emptyAssociativeArray = [];  //This was an associative array. But empty array for PHP is just an array...

The problem is, that when your Javascript code expects an Object made from associative array, and it gets an empty array, then the code might by accient see prototype's extensions added to array.

$H(associativeArray).each(function(pair) {
   $('assocOutput').insert('<div>' + pair.key + ': ' + pair.value + '</div>');
});

The output:
//normally would look like:
first: one
second: two
third: three
//but if the associative array is empty, you will get all the prototype extensions to the Array object, instead of an empty plain object:
each: function (iterator, context) { var index = 0; try { this._each(function (value) {iterator.call(context, value, index++);}); } catch (e) { if (e != $break) { throw e; } } return this; }
eachSlice: function ...
[.....]

This is correct behavior, as the $H function was never intended to be used with arrays.
kangax wrote in Detecting an empty array as a parameter to a function thread on Prototype & script.aculo.us mailing list.

$H([]) produces something that it was never intended to produce - an
object extended with Array.prototype.* methods. You probably don't
ever want to use it.

$H() uses a for..in loop internally to traverse object properties, so it does see all the functions added to Array by prototype.js framework.

Workaround

If you are expecting to always get plain JS object, and never an array, then the line below is enough to work around the problem with php's json_encode()

var emptyObject = [] ; //this is what you got from php...
//lets make sure you get always an object:
emptyObject = (Object.isArray(emptyObject ) && !emptyObject .length) ? {} : emptyObject; 
//now you can start using the $H():
var objectHash = $H(emptyObject);

The only thing needed here is the check if you go an empty array, and if so, replace the array with empty object, otherwise get the object itself.

Test it yourself

Copy the code below to a html file and view in browser to see the effects:

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
  "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
 
<head>
<script src="http://ajax.googleapis.com/ajax/libs/prototype/1.6.0.3/prototype.js"></script>
<title>Sandbox</title>
<meta http-equiv="Content-type" content="text/html; charset=utf-8" />
<style type="text/css" media="screen">
body { background-color: #000; font: 16px Helvetica, Arial; color: #fff; }
</style>
</head>
<body> 
<h2>Assoc output:</h2>
  <div id="assocOutput"></div>
<h2>Empty Array output:</h2>
  <div id="emptyOutput"></div>
<h2> Empty array with workaround:</h2>
   <div id="workaroundOutput"></div>
<hr/>
<div>end of page</div>
<script type="text/javascript">
<!-- we run in the footer so no need to use onload -->
var numericArray = ["one","two","three"]; 
var associativeArray = {"first":"one","second":"two","third":"three"}; 
var emptyNumericArray = []; 
var emptyAssociativeArray = [];
 
$H(associativeArray).each(function(pair) {
   $('assocOutput').insert('<div>' + pair.key + ': ' + pair.value + '</div>');
});
 
$H(emptyAssociativeArray).each(function(pair) {
   $('emptyOutput').insert('<div>' + pair.key + ': ' + pair.value + '</div>');
});
 
emptyAssociativeArray= (Object.isArray(emptyAssociativeArray) && !emptyAssociativeArray.length) ? {} : emptyAssociativeArray; 
$H(emptyAssociativeArray).each(function(pair) {
   $('workaroundOutput').insert('<div>' + pair.key + ': ' + pair.value + '</div>');
});
 
</script>
</html>

Update

If you are using PHP V5.3 or later, there is a new option to the json_encode() function. See json_encode.

<?php
$numericArray = array(
    'one', 'two', 'three'
);
$associativeArray = array(
    'first'  => 'one',
    'second' => 'two',
    'third'  =>'three'
);
 
echo 'var numericArray     = ' . json_encode($numericArray, JSON_FORCE_OBJECT) . ";\n" ;
echo 'var associativeArray = ' . json_encode($associativeArray, JSON_FORCE_OBJECT) . ";\n" ;
 
/* lets empty those arrays */
unset($numericArray[0]);
unset($numericArray[1]);
unset($numericArray[2]); 
unset($associativeArray['first']);
unset($associativeArray['second']);
unset($associativeArray['third']);
 
echo 'var emptyNumericArray     = ' . json_encode($numericArray, JSON_FORCE_OBJECT) . ";\n" ;
echo 'var emptyAssociativeArray = ' . json_encode($associativeArray, JSON_FORCE_OBJECT) . ";\n" ;
?>
will output
var numericArray     = {"0":"one","1":"two","2":"three"};
var associativeArray = {"first":"one","second":"two","third":"three"};
var emptyNumericArray     = {};
var emptyAssociativeArray = {};

As you can see, the outputs are all objects. This makes using $H() on them very simple.

This was due to the following feature request : Option to force empty arrays to be an empty object for json_encode()

Unless otherwise stated, the content of this page is licensed under Creative Commons Attribution-ShareAlike 3.0 License