Source for file JSON.php
Documentation is available at JSON.php 
/* vim: set expandtab tabstop=4 shiftwidth=4 softtabstop=4: */  
 * Converts to and from JSON format.  
 * JSON (JavaScript Object Notation) is a lightweight data-interchange  
 * format. It is easy for humans to read and write. It is easy for machines  
 * to parse and generate. It is based on a subset of the JavaScript  
 * Programming Language, Standard ECMA-262 3rd Edition - December 1999.  
 * This feature can also be found in  Python. JSON is a text format that is  
 * completely language independent but uses conventions that are familiar  
 * to programmers of the C-family of languages, including C, C++, C#, Java,  
 * JavaScript, Perl, TCL, and many others. These properties make JSON an  
 * ideal data-interchange language.  
 * This package provides a simple encoder and decoder for JSON notation. It  
 * is intended for use with client-side Javascript applications that make  
 * use of HTTPRequest to perform server communication functions - data can  
 * be encoded into JSON notation for use in a client-side javascript, or  
 * decoded from incoming Javascript requests. JSON format is native to  
 * Javascript, and can be directly eval()'ed with no further parsing  
 * All strings should be in ASCII or UTF-8 format!  
 * LICENSE: Redistribution and use in source and binary forms, with or  
 * without modification, are permitted provided that the following  
 * conditions are met: Redistributions of source code must retain the  
 * above copyright notice, this list of conditions and the following  
 * disclaimer. Redistributions in binary form must reproduce the above  
 * copyright notice, this list of conditions and the following disclaimer  
 * in the documentation and/or other materials provided with the  
 * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED  
 * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF  
 * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN  
 * NO EVENT SHALL CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,  
 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,  
 * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS  
 * OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND  
 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR  
 * TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE  
 * USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH  
 * @package     linea21.externals  
 * @subpackage     Services_JSON  
 * @author      Michal Migurski <mike-json@teczno.com>  
 * @author      Matt Knapp <mdknapp[at]gmail[dot]com>  
 * @author      Brett Stimmerman <brettstimmerman[at]gmail[dot]com>  
 * @copyright   2005 Michal Migurski  
 * @version     CVS: $Id: JSON.php,v 1.31 2006/06/28 05:54:17 migurski Exp $  
 * @license     http://www.opensource.org/licenses/bsd-license.php  
 * @link        http://pear.php.net/pepr/pepr-proposal-show.php?id=198  
 * Marker constant for Services_JSON::decode(), used to flag stack state  
define('SERVICES_JSON_SLICE',   1);  
 * Marker constant for Services_JSON::decode(), used to flag stack state  
define('SERVICES_JSON_IN_STR',  2);  
 * Marker constant for Services_JSON::decode(), used to flag stack state  
define('SERVICES_JSON_IN_ARR',  3);  
 * Marker constant for Services_JSON::decode(), used to flag stack state  
define('SERVICES_JSON_IN_OBJ',  4);  
 * Marker constant for Services_JSON::decode(), used to flag stack state  
define('SERVICES_JSON_IN_CMT', 5);  
 * Behavior switch for Services_JSON::decode()  
define('SERVICES_JSON_LOOSE_TYPE', 16);  
 * Behavior switch for Services_JSON::decode()  
define('SERVICES_JSON_SUPPRESS_ERRORS', 32);  
 * Converts to and from JSON format.  
 * // create a new instance of Services_JSON  
 * $json = new Services_JSON();  
 * // convert a complexe value to JSON notation, and send it to the browser  
 * $value = array('foo', 'bar', array(1, 2, 'baz'), array(3, array(4)));  
 * $output = $json->encode($value);  
 * // prints: ["foo","bar",[1,2,"baz"],[3,[4]]]  
 * // accept incoming POST data, assumed to be in JSON notation  
 * $input = file_get_contents('php://input', 1000000);  
 * $value = $json->decode($input);  
   * constructs a new JSON instance  
   * @param    int     $use    object behavior flags; combine with boolean-OR  
   *                            - SERVICES_JSON_LOOSE_TYPE:  loose typing.  
   *                                    "{...}" syntax creates associative arrays  
   *                                    instead of objects in decode().  
   *                            - SERVICES_JSON_SUPPRESS_ERRORS:  error suppression.  
   *                                    Values which can't be encoded (e.g. resources)  
   *                                    appear as NULL instead of throwing errors.  
   *                                    By default, a deeply-nested resource will  
   *                                    bubble up with an error, so all return values  
   *                                    from encode() should be checked with isError()  
   * convert a string from one UTF-16 char to one UTF-8 char  
   * Normally should be handled by mb_convert_encoding, but  
   * provides a slower PHP-only method for installations  
   * that lack the multibye string extension.  
   * @param    string  $utf16  UTF-16 character  
   * @return   string  UTF-8 character  
  function utf162utf8($utf16)  
    // oh please oh please oh please oh please oh please  
    $bytes =  (ord($utf16{0}) <<  8) |  ord($utf16{1});  
      case ((0x7F & $bytes) ==  $bytes):   
        // this case should never be reached, because we are in ASCII range  
        // see: http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8  
        return chr(0x7F & $bytes);  
      case (0x07FF & $bytes) ==  $bytes:   
        // return a 2-byte UTF-8 character  
        // see: http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8  
        return chr(0xC0 |  (($bytes >>  6) & 0x1F))  
        .  chr(0x80 |  ($bytes & 0x3F)); 
      case (0xFFFF & $bytes) ==  $bytes:   
        // return a 3-byte UTF-8 character  
        // see: http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8  
        return chr(0xE0 |  (($bytes >>  12) & 0x0F))  
        .  chr(0x80 |  (($bytes >>  6) & 0x3F)) 
        .  chr(0x80 |  ($bytes & 0x3F)); 
    // ignoring UTF-32 for now, sorry  
   * convert a string from one UTF-8 char to one UTF-16 char  
   * Normally should be handled by mb_convert_encoding, but  
   * provides a slower PHP-only method for installations  
   * that lack the multibye string extension.  
   * @param    string  $utf8   UTF-8 character  
   * @return   string  UTF-16 character  
  function utf82utf16($utf8)  
    // oh please oh please oh please oh please oh please  
        // this case should never be reached, because we are in ASCII range  
        // see: http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8  
        // return a UTF-16 character from a 2-byte UTF-8 char  
        // see: http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8  
        return chr(0x07 & (ord($utf8{0}) >>  2))  
        .  chr((0xC0 & (ord($utf8{0}) <<  6)) 
        |  (0x3F & ord($utf8{1}))); 
        // return a UTF-16 character from a 3-byte UTF-8 char  
        // see: http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8  
        return chr((0xF0 & (ord($utf8{0}) <<  4))  
        |  (0x0F & (ord($utf8{1}) >>  2))) 
        .  chr((0xC0 & (ord($utf8{1}) <<  6)) 
        |  (0x7F & ord($utf8{2}))); 
    // ignoring UTF-32 for now, sorry  
   * encodes an arbitrary variable into JSON format  
   * @param    mixed   $var    any number, boolean, string, array, or object to be encoded.  
   *                            see argument 1 to Services_JSON() above for array-parsing behavior.  
   *                            if var is a strng, note that encode() always expects it  
   *                            to be in ASCII or UTF-8 format!  
   * @return   mixed   JSON string representation of input var or an error if a problem occurs  
        return $var ?  'true' :  'false';  
        // STRINGS ARE EXPECTED TO BE IN ASCII OR UTF-8 FORMAT  
         * Iterate over every character in the string,  
         * escaping with a slash or encoding to UTF-8 where necessary  
        for ($c =  0; $c <  $strlen_var; ++ $c) {  
          $ord_var_c =  ord($var{$c});  
              // double quote, slash, slosh  
            case (($ord_var_c >=  0x20) &&  ($ord_var_c <=  0x7F)):   
              // characters U-00000000 - U-0000007F (same as ASCII)  
            case (($ord_var_c & 0xE0) ==  0xC0):   
              // characters U-00000080 - U-000007FF, mask 110XXXXX  
              // see http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8  
              $char =  pack('C*', $ord_var_c, ord($var{$c +  1}));  
              $utf16 =  $this->utf82utf16($char);  
            case (($ord_var_c & 0xF0) ==  0xE0):   
              // characters U-00000800 - U-0000FFFF, mask 1110XXXX  
              // see http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8  
              $char =  pack('C*', $ord_var_c,  
              $utf16 =  $this->utf82utf16($char);  
            case (($ord_var_c & 0xF8) ==  0xF0):   
              // characters U-00010000 - U-001FFFFF, mask 11110XXX  
              // see http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8  
              $char =  pack('C*', $ord_var_c,  
              $utf16 =  $this->utf82utf16($char);  
            case (($ord_var_c & 0xFC) ==  0xF8):   
              // characters U-00200000 - U-03FFFFFF, mask 111110XX  
              // see http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8  
              $char =  pack('C*', $ord_var_c,  
              $utf16 =  $this->utf82utf16($char);  
            case (($ord_var_c & 0xFE) ==  0xFC):   
              // characters U-04000000 - U-7FFFFFFF, mask 1111110X  
              // see http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8  
              $char =  pack('C*', $ord_var_c,  
              $utf16 =  $this->utf82utf16($char);  
         * As per JSON spec if any array key is not an integer  
         * we must treat the the whole array as an object. We  
         * also try to catch a sparsely populated associative  
         * array with numeric keys here because some JS engines  
         * will create an array with empty indexes up to  
         * max_index which can cause memory issues and because  
         * the keys, which may be relevant, will be remapped  
         * As per the ECMA and JSON specification an object may  
         * have any string as a property. Unfortunately due to  
         * a hole in the ECMA specification if the key is a  
         * ECMA reserved word or starts with a digit the  
         * parameter is only accessible using ECMAScript's  
        // treat as a JSON object  
          $properties =  array_map(array($this, 'name_value'),  
          foreach($properties as $property) {  
          return '{' .  join(',', $properties) .  '}';  
        // treat it like a regular array  
        $elements =  array_map(array($this, 'encode'), $var);  
        foreach($elements as $element) {  
        return '[' .  join(',', $elements) .  ']';  
        $properties =  array_map(array($this, 'name_value'),  
        foreach($properties as $property) {  
        return '{' .  join(',', $properties) .  '}';  
   * array-walking function for use in generating JSON-formatted name-value pairs  
   * @param    string  $name   name of key to use  
   * @param    mixed   $value  reference to an array element to be encoded  
   * @return   string  JSON-formatted name-value pair, like '"name":value'  
  function name_value($name, $value)  
    $encoded_value =  $this->encode($value);  
   * reduce a string by removing leading and trailing comments and whitespace  
   * @param    $str    string      string value to strip of comments and whitespace  
   * @return   string  string value stripped of comments and whitespace  
  function reduce_string($str)  
    // eliminate single line comments in '// ...' form  
    // eliminate multi-line comments in '/* ... */' form, at start of string  
    // eliminate multi-line comments in '/* ... */' form, at end of string  
                // eliminate extraneous space  
   * decodes a JSON string into appropriate variable  
   * @param    string  $str    JSON-formatted string  
   * @return   mixed   number, boolean, string, array, or object  
   *                    corresponding to given JSON input string.  
   *                    See argument 1 to Services_JSON() above for object-output behavior.  
   *                    Note that decode() always returns strings  
   *                    in ASCII or UTF-8 format!  
    $str =  $this->reduce_string($str);  
          // Lookie-loo, it's a number  
          // This would work on its own, but I'm trying to be  
          // good about returning integers where appropriate:  
          // Return float or int, as appropriate  
          return ((float) $str == (integer) $str)  
        } elseif (preg_match('/^("|\').*(\1)$/s', $str, $m) &&  $m[1] ==  $m[2]) {  
          // STRINGS RETURNED IN UTF-8 FORMAT  
          for ($c =  0; $c <  $strlen_chrs; ++ $c) {  
            $substr_chrs_c_2 =  substr($chrs, $c, 2);  
            $ord_chrs_c =  ord($chrs{$c});  
              case $substr_chrs_c_2 ==  '\b':   
              case $substr_chrs_c_2 ==  '\t':   
              case $substr_chrs_c_2 ==  '\n':   
              case $substr_chrs_c_2 ==  '\f':   
              case $substr_chrs_c_2 ==  '\r':   
              case $substr_chrs_c_2 ==  '\\"':   
              case $substr_chrs_c_2 ==  '\\\'':   
              case $substr_chrs_c_2 ==  '\\\\':   
              case $substr_chrs_c_2 ==  '\\/':   
                if (($delim ==  '"' &&  $substr_chrs_c_2 !=  '\\\'') ||   
                ($delim ==  "'" &&  $substr_chrs_c_2 !=  '\\"')) {  
                // single, escaped unicode character  
                $utf8 .=  $this->utf162utf8($utf16);  
              case ($ord_chrs_c >=  0x20) &&  ($ord_chrs_c <=  0x7F):   
              case ($ord_chrs_c & 0xE0) ==  0xC0:   
                // characters U-00000080 - U-000007FF, mask 110XXXXX  
                //see http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8  
                $utf8 .=  substr($chrs, $c, 2);  
              case ($ord_chrs_c & 0xF0) ==  0xE0:   
                // characters U-00000800 - U-0000FFFF, mask 1110XXXX  
                // see http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8  
                $utf8 .=  substr($chrs, $c, 3);  
              case ($ord_chrs_c & 0xF8) ==  0xF0:   
                // characters U-00010000 - U-001FFFFF, mask 11110XXX  
                // see http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8  
                $utf8 .=  substr($chrs, $c, 4);  
              case ($ord_chrs_c & 0xFC) ==  0xF8:   
                // characters U-00200000 - U-03FFFFFF, mask 111110XX  
                // see http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8  
                $utf8 .=  substr($chrs, $c, 5);  
              case ($ord_chrs_c & 0xFE) ==  0xFC:   
                // characters U-04000000 - U-7FFFFFFF, mask 1111110X  
                // see http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8  
                $utf8 .=  substr($chrs, $c, 6);  
          // array, or object notation  
          $chrs =  $this->reduce_string($chrs);  
          //print("\nparsing {$chrs}\n");  
          for ($c =  0; $c <=  $strlen_chrs; ++ $c) {  
            $substr_chrs_c_2 =  substr($chrs, $c, 2);  
              // found a comma that is not inside a string, array, etc.,  
              // OR we've reached the end of the character list  
              $slice =  substr($chrs, $top['where'], ($c -  $top['where']));  
              //print("Found split at {$c}: ".substr($chrs, $top['where'], (1 + $c - $top['where']))."\n");  
                // we are in an array, so just push an element onto the stack  
                // we are in an object, so figure  
                // out the property name and set an  
                // element in an associative array,  
                if (preg_match('/^\s*(["\'].*[^\\\]["\'])\s*:\s*(\S.*),?$/Uis', $slice, $parts)) {  
                  $key =  $this->decode($parts[1]);  
                  $val =  $this->decode($parts[2]);  
                } elseif (preg_match('/^\s*(\w+)\s*:\s*(\S.*),?$/Uis', $slice, $parts)) {  
                  // name:value pair, where name is unquoted  
                  $val =  $this->decode($parts[2]);  
              // found a quote, and we are not inside a string  
              //print("Found start of string at {$c}\n");  
            } elseif (($chrs{$c} ==  $top['delim']) &&   
              // found a quote, we're in a string, and it's not escaped  
              // we know that it's not escaped becase there is _not_ an  
              // odd number of backslashes at the end of the string so far  
              //print("Found end of string at {$c}: ".substr($chrs, $top['where'], (1 + 1 + $c - $top['where']))."\n");  
            } elseif (($chrs{$c} ==  '[') &&   
              // found a left-bracket, and we are in an array, object, or slice  
              //print("Found start of array at {$c}\n");  
              // found a right-bracket, and we're in an array  
              //print("Found end of array at {$c}: ".substr($chrs, $top['where'], (1 + $c - $top['where']))."\n");  
            } elseif (($chrs{$c} ==  '{') &&   
              // found a left-brace, and we are in an array, object, or slice  
              //print("Found start of object at {$c}\n");  
              // found a right-brace, and we're in an object  
              //print("Found end of object at {$c}: ".substr($chrs, $top['where'], (1 + $c - $top['where']))."\n");  
            } elseif (($substr_chrs_c_2 ==  '/*') &&   
              // found a comment start, and we are in an array, object, or slice  
              //print("Found start of comment at {$c}\n");  
              // found a comment end, and we're in one now  
              for ($i =  $top['where']; $i <=  $c; ++ $i)  
              //print("Found end of comment at {$c}: ".substr($chrs, $top['where'], (1 + $c - $top['where']))."\n");  
   * @todo Ultimately, this should just call PEAR::isError()  
  function isError($data, $code =  null)  
      return PEAR::isError($data, $code);  
    $mode =  null, $options =  null, $userinfo =  null)  
      parent::PEAR_Error($message, $code, $mode, $options, $userinfo);  
   * @todo Ultimately, this class shall be descended from PEAR_Error  
    $mode =  null, $options =  null, $userinfo =  null)  
 
 
        
       |