1
wishcraft
2.4 - JSON Encoding/Decoding Class

Class 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 class 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 overhead

All strings should be in ASCII or UTF-8 format!


/**
 * Marker constant for XoopsJSON::decode(), used to flag stack state
 */
define('XOOPS_JSON_SLICE',   1);

/**
 * Marker constant for XoopsJSON::decode(), used to flag stack state
 */
define('XOOPS_JSON_IN_STR',  2);

/**
 * Marker constant for XoopsJSON::decode(), used to flag stack state
 */
define('XOOPS_JSON_IN_ARR',  3);

/**
 * Marker constant for XoopsJSON::decode(), used to flag stack state
 */
define('XOOPS_JSON_IN_OBJ',  4);

/**
 * Marker constant for XoopsJSON::decode(), used to flag stack state
 */
define('XOOPS_JSON_IN_CMT'5);

/**
 * Behavior switch for XoopsJSON::decode()
 */
define('XOOPS_JSON_LOOSE_TYPE'16);

/**
 * Behavior switch for XoopsJSON::decode()
 */
define('XOOPS_JSON_SUPPRESS_ERRORS'32);

/**
 * Converts to and from JSON format.
 *
 * Brief example of use:
 *
 * 
 * // create a new instance of XoopsJSON
 * $json = new XoopsJSON();
 *
 * // 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);
 *
 * print($output);
 * // 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);
 * 

 */
 
class XoopsJSON /*extends XoopsObject*/
{
   
/**
    * constructs a new JSON instance
    *
    * @param    int     $use    object behavior flags; combine with boolean-OR
    *
    *                           possible values:
    *                           - XOOPS_JSON_LOOSE_TYPE:  loose typing.
    *                                   "{...}" syntax creates associative arrays
    *                                   instead of objects in decode().
    *                           - XOOPS_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()
    */
    
function XoopsJSON($use 0)
    {
        
$this->use $use;
    }

   
/**
    * 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
    * @access   private
    */
    
function utf162utf8($utf16)
    {
        if(
function_exists('mb_convert_encoding')) {
            return 
mb_convert_encoding($utf16'UTF-8''UTF-16');
        }
        
$bytes = (ord($utf16{0}) << 8) | ord($utf16{1});
        switch(
true) {
            case ((
0x7F $bytes) == $bytes):
                return 
chr(0x7F $bytes);
            case (
0x07FF $bytes) == $bytes:
                return 
chr(0xC0 | (($bytes >> 6) & 0x1F))
                     . 
chr(0x80 | ($bytes 0x3F));

            case (
0xFFFF $bytes) == $bytes:
                return 
chr(0xE0 | (($bytes >> 12) & 0x0F))
                     . 
chr(0x80 | (($bytes >> 6) & 0x3F))
                     . 
chr(0x80 | ($bytes 0x3F));
        }
        return 
'';
    }

   
/**
    * 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
    * @access   private
    */
    
function utf82utf16($utf8)
    {
        if(
function_exists('mb_convert_encoding')) {
            return 
mb_convert_encoding($utf8'UTF-16''UTF-8');
        }

        switch(
strlen($utf8)) {
            case 
1:
                return 
$utf8;
            case 
2:
                return 
chr(0x07 & (ord($utf8{0}) >> 2))
                     . 
chr((0xC0 & (ord($utf8{0}) << 6))
                         | (
0x3F ord($utf8{1})));
            case 
3:
                return 
chr((0xF0 & (ord($utf8{0}) << 4))
                         | (
0x0F & (ord($utf8{1}) >> 2)))
                     . 
chr((0xC0 & (ord($utf8{1}) << 6))
                         | (
0x7F ord($utf8{2})));
        }
        return 
'';
    }

   
/**
    * encodes an arbitrary variable into JSON format (and sends JSON Header)
    *
    * @param    mixed   $var    any number, boolean, string, array, or object to be encoded.
    *                           see argument 1 to XoopsJSON() 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
    * @access   public
    */
    
function encode($var)
    {
        
header('Content-type: application/json');
        return 
$this->encodeUnsafe($var);
    }

    
/**
    * encodes an arbitrary variable into JSON format without JSON Header - warning - may allow CSS!!!!)
    *
    * @param    mixed   $var    any number, boolean, string, array, or object to be encoded.
    *                           see argument 1 to XoopsJSON() 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
    * @access   public
    */
    
function encodeUnsafe($var)
    {
        
$lc setlocale(LC_NUMERIC0);
        
setlocale(LC_NUMERIC'C');
        
$ret $this->_encode($var);
        
setlocale(LC_NUMERIC$lc);
        return 
$ret;    
    }
    
    
/**
    * PRIVATE CODE that does the work of encodes an arbitrary variable into JSON format 
    *
    * @param    mixed   $var    any number, boolean, string, array, or object to be encoded.
    *                           see argument 1 to XoopsJSON() 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
    * @access   public
    */
    
function _encode($var
    {        
        switch (
gettype($var)) {
            case 
'boolean':
`
               return $var ? 'true' : 'false';
            case 'NULL':
                return 'null';
            case 'integer':
                return (int) 
$var;
            case 'double':
            case 'float':
                return  (float) 
$var;
            case 'string':
                
$ascii = '';
                
$strlen_var = strlen($var);
                for (
$c = 0; $c < $strlen_var; ++$c) {
                    
$ord_var_c = ord($var{$c});
                    switch (true) {
                        case 
$ord_var_c == 0x08:
                            
$ascii .= 'b';
                            break;
                        case 
$ord_var_c == 0x09:
                            
$ascii .= 't';
                            break;
                        case 
$ord_var_c == 0x0A:
                            
$ascii .= 'n';
                            break;
                        case 
$ord_var_c == 0x0C:
                            
$ascii .= 'f';
                            break;
                        case 
$ord_var_c == 0x0D:
                            
$ascii .= 'r';
                            break;
                        case 
$ord_var_c == 0x22:
                        case 
$ord_var_c == 0x2F:
                        case 
$ord_var_c == 0x5C:
                            
$ascii .= '\'.$var{$c};
                            break;
                        case ((
$ord_var_c >= 0x20) && ($ord_var_c <= 0x7F)):
                            
$ascii .= $var{$c};
                            break;
                        case ((
$ord_var_c & 0xE0) == 0xC0):
                            if (
$c+1 >= $strlen_var) {
                                
$c += 1;
                                
$ascii .= '?';
                                break;
                            }                           
                            
$char = pack('C*', $ord_var_c, ord($var{$c 1}));
                            
$c += 1;
                            
$utf16 = $this->utf82utf16($char);
                            
$ascii .= sprintf('u%04s', bin2hex($utf16));
                            break;
                        case ((
$ord_var_c & 0xF0) == 0xE0):
                            if (
$c+2 >= $strlen_var) {
                                
$c += 2;
                                
$ascii .= '?';
                                break;
                            }
                            
$char = pack('C*', $ord_var_c,
                                         @ord(
$var{$c 1}),
                                         @ord(
$var{$c 2}));
                            
$c += 2;
                            
$utf16 = $this->utf82utf16($char);
                            
$ascii .= sprintf('u%04s', bin2hex($utf16));
                            break;
                        case ((
$ord_var_c & 0xF8) == 0xF0):
                            if (
$c+3 >= $strlen_var) {
                                
$c += 3;
                                
$ascii .= '?';
                                break;
                            }
                            
$char = pack('C*', $ord_var_c,
                                         ord(
$var{$c 1}),
                                         ord(
$var{$c 2}),
                                         ord(
$var{$c 3}));
                            
$c += 3;
                            
$utf16 = $this->utf82utf16($char);
                            
$ascii .= sprintf('u%04s', bin2hex($utf16));
                            break;
                        case ((
$ord_var_c & 0xFC) == 0xF8):
                            if (
$c+4 >= $strlen_var) {
                                
$c += 4;
                                
$ascii .= '?';
                                break;
                            }
                            
$char = pack('C*', $ord_var_c,
                                         ord(
$var{$c 1}),
                                         ord(
$var{$c 2}),
                                         ord(
$var{$c 3}),
                                         ord(
$var{$c 4}));
                            
$c += 4;
                            
$utf16 = $this->utf82utf16($char);
                            
$ascii .= sprintf('u%04s', bin2hex($utf16));
                            break;
                        case ((
$ord_var_c & 0xFE) == 0xFC):
                            if (
$c+5 >= $strlen_var) {
                                
$c += 5;
                                
$ascii .= '?';
                                break;
                            }
                            
$char = pack('C*', $ord_var_c,
                                         ord(
$var{$c 1}),
                                         ord(
$var{$c 2}),
                                         ord(
$var{$c 3}),
                                         ord(
$var{$c 4}),
                                         ord(
$var{$c 5}));
                            
$c += 5;
                            
$utf16 = $this->utf82utf16($char);
                            
$ascii .= sprintf('u%04s', bin2hex($utf16));
                            break;
                    }
                }
                return  '"'.
$ascii.'"';
            case 'array':
                if (is_array(
$var) && count($var) && (array_keys($var) !== range(0, sizeof($var) - 1))) {
                    
$properties = array_map(array($this, 'name_value'),
                                            array_keys(
$var),
                                            array_values(
$var));
                    foreach(
$properties as $property) {
                        if(XoopsJSON::isError(
$property)) {
                            return 
$property;
                        }
                    }

                    return '{' . join(',', 
$properties) . '}';
                }
                
$elements = array_map(array($this, '_encode'), $var);
                foreach(
$elements as $element) {
                    if(XoopsJSON::isError(
$element)) {
                        return 
$element;
                    }
                }
                return '[' . join(',', 
$elements) . ']';
            case 'object':
                
$vars = get_object_vars($var);
                
$properties = array_map(array($this, 'name_value'),
                                        array_keys(
$vars),
                                        array_values(
$vars));
                foreach(
$properties as $property) {
                    if(XoopsJSON::isError(
$property)) {
                        return 
$property;
                    }
                }
                return '{' . join(',', 
$properties) . '}';
            default:
                return (
$this->use & XOOPS_JSON_SUPPRESS_ERRORS)
                    ? 'null'
                    : new XoopsJSON_Error(gettype(
$var)." can not be encoded as JSON string");
        }
    }

   /**
    * 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'
    * @access   private
    */
    function name_value(
$name$value)
    {
        
$encoded_value = $this->_encode($value);
        if(XoopsJSON::isError(
$encoded_value)) {
            return 
$encoded_value;
        }
        return 
$this->_encode(strval($name)) . ':' . $encoded_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
    * @access   private
    */
    function reduce_string(
$str)
    {
        
$str = preg_replace(array(
                '#^s*//(.+)$#m',
                '#^s*/*(.+)*/#Us',
                '#/*(.+)*/s*$#Us'
            ), '', 
$str);
        return trim(
$str);
    }

   /**
    * 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 XoopsJSON() above for object-output behavior.
    *                   Note that decode() always returns strings
    *                   in ASCII or UTF-8 format!
    * @access   public
    */
    function decode(
$str)
    {
        
$str = $this->reduce_string($str);
        switch (strtolower(
$str)) {
            case 'true':
                return true;
            case 'false':
                return false;
            case 'null':
                return null;
            default:
                
$m = array();
                if (is_numeric(
$str)) {
                    return ((float)
$str == (integer)$str)
                        ? (integer)
$str
                        : (float)
$str;
                } elseif (preg_match('/^("|').*(1)$/s', 
$str$m) && $m[1] == $m[2]) {
                    
$delim = substr($str, 0, 1);
                    
$chrs = substr($str, 1, -1);
                    
$utf8 = '';
                    
$strlen_chrs = strlen($chrs);
                    for (
$c = 0; $c < $strlen_chrs; ++$c) {
                        
$substr_chrs_c_2 = substr($chrs$c, 2);
                        
$ord_chrs_c = ord($chrs{$c});
                        switch (true) {
                            case 
$substr_chrs_c_2 == 'b':
                                
$utf8 .= chr(0x08);
                                ++
$c;
                                break;
                            case 
$substr_chrs_c_2 == 't':
                                
$utf8 .= chr(0x09);
                                ++
$c;
                                break;
                            case 
$substr_chrs_c_2 == 'n':
                                
$utf8 .= chr(0x0A);
                                ++
$c;
                                break;
                            case 
$substr_chrs_c_2 == 'f':
                                
$utf8 .= chr(0x0C);
                                ++
$c;
                                break;
                            case 
$substr_chrs_c_2 == 'r':
                                
$utf8 .= chr(0x0D);
                                ++
$c;
                                break;
                            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 != '"')) {
                                    
$utf8 .= $chrs{++$c};
                                }
                                break;
                            case preg_match('/\u[0-9A-F]{4}/i', substr(
$chrs$c, 6)):
                                
$utf16 = chr(hexdec(substr($chrs, ($c + 2), 2)))
                                       . chr(hexdec(substr(
$chrs, ($c + 4), 2)));
                                
$utf8 .= $this->utf162utf8($utf16);
                                
$c += 5;
                                break;
                            case (
$ord_chrs_c >= 0x20) && ($ord_chrs_c <= 0x7F):
                                
$utf8 .= $chrs{$c};
                                break;
                            case (
$ord_chrs_c & 0xE0) == 0xC0:
                                
$utf8 .= substr($chrs$c, 2);
                                ++
$c;
                                break;
                            case (
$ord_chrs_c & 0xF0) == 0xE0:
                                
$utf8 .= substr($chrs$c, 3);
                                
$c += 2;
                                break;
                            case (
$ord_chrs_c & 0xF8) == 0xF0:
                                
$utf8 .= substr($chrs$c, 4);
                                
$c += 3;
                                break;
                            case (
$ord_chrs_c & 0xFC) == 0xF8:
                                
$utf8 .= substr($chrs$c, 5);
                                
$c += 4;
                                break;
                            case (
$ord_chrs_c & 0xFE) == 0xFC:
                                
$utf8 .= substr($chrs$c, 6);
                                
$c += 5;
                                break;
                        }

                    }
                    return 
$utf8;
                } elseif (preg_match('/^[.*]$/s', 
$str) || preg_match('/^{.*}$/s', $str)) {
                    if (
$str{0} == '[') {
                        
$stk = array(XOOPS_JSON_IN_ARR);
                        
$arr = array();
                    } else {
                        if (
$this->use & XOOPS_JSON_LOOSE_TYPE) {
                            
$stk = array(XOOPS_JSON_IN_OBJ);
                            
$obj = array();
                        } else {
                            
$stk = array(XOOPS_JSON_IN_OBJ);
                            
$obj = new stdClass();
                        }
                    }
                    array_push(
$stk, array('what'  => XOOPS_JSON_SLICE,
                                           'where' => 0,
                                           'delim' => false));
                    
$chrs = substr($str, 1, -1);
                    
$chrs = $this->reduce_string($chrs);
                    if (
$chrs == '') {
                        if (reset(
$stk) == XOOPS_JSON_IN_ARR) {
                            return 
$arr;
                        } else {
                            return 
$obj;
                        }
                    }
                    
$strlen_chrs = strlen($chrs);
                    for (
$c = 0; $c <= $strlen_chrs; ++$c) {
                        
$top = end($stk);
                        
$substr_chrs_c_2 = substr($chrs$c, 2);
                        if ((
$c == $strlen_chrs) || (($chrs{$c} == ',') && ($top['what'] == XOOPS_JSON_SLICE))) {
                            
$slice = substr($chrs$top['where'], ($c - $top['where']));
                            array_push(
$stk, array('what' => XOOPS_JSON_SLICE, 'where' => ($c + 1), 'delim' => false));
                            if (reset(
$stk) == XOOPS_JSON_IN_ARR) {
                                array_push(
$arr$this->decode($slice));
                            } elseif (reset(
$stk) == XOOPS_JSON_IN_OBJ) {
                                
$parts = array();                               
                                if (preg_match('/^s*(["'].*[^\]["'])s*:s*(S.*),?$/Uis', 
$slice$parts)) {
                                    
$key = $this->decode($parts[1]);
                                    
$val = $this->decode($parts[2]);

                                    if (
$this->use & XOOPS_JSON_LOOSE_TYPE) {
                                        
$obj[$key] = $val;
                                    } else {
                                        
$obj->$key = $val;
                                    }
                                } elseif (preg_match('/^s*(w+)s*:s*(S.*),?$/Uis', 
$slice$parts)) {
                                    
$key = $parts[1];
                                    
$val = $this->decode($parts[2]);
                                    if (
$this->use & XOOPS_JSON_LOOSE_TYPE) {
                                        
$obj[$key] = $val;
                                    } else {
                                        
$obj->$key = $val;
                                    }
                                }

                            }
                        } elseif (((
$chrs{$c} == '"') || ($chrs{$c} == "'")) && ($top['what'] != XOOPS_JSON_IN_STR)) {
                            array_push(
$stk, array('what' => XOOPS_JSON_IN_STR, 'where' => $c, 'delim' => $chrs{$c}));
                        } elseif ((
$chrs{$c} == $top['delim']) &&
                                 (
$top['what'] == XOOPS_JSON_IN_STR) &&
                                 ((strlen(substr(
$chrs, 0, $c)) - strlen(rtrim(substr($chrs, 0, $c), '\'))) % 2 != 1)) {
                            array_pop(
$stk);
                        } elseif ((
$chrs{$c} == '[') &&
                            in_array(
$top['what'], array(XOOPS_JSON_SLICE, XOOPS_JSON_IN_ARR, XOOPS_JSON_IN_OBJ))) {
                            array_push(
$stk, array('what' => XOOPS_JSON_IN_ARR, 'where' => $c, 'delim' => false));
                        } elseif ((
$chrs{$c} == ']') && ($top['what'] == XOOPS_JSON_IN_ARR)) {
                            array_pop(
$stk);
                        } elseif ((
$chrs{$c} == '{') &&
                            in_array(
$top['what'], array(XOOPS_JSON_SLICE, XOOPS_JSON_IN_ARR, XOOPS_JSON_IN_OBJ))) {
                            array_push(
$stk, array('what' => XOOPS_JSON_IN_OBJ, 'where' => $c, 'delim' => false));
                        } elseif ((
$chrs{$c} == '}') && ($top['what'] == XOOPS_JSON_IN_OBJ)) {
                            array_pop(
$stk);
                        } elseif ((
$substr_chrs_c_2 == '/*') &&
                                 in_array(
$top['what'], array(XOOPS_JSON_SLICE, XOOPS_JSON_IN_ARR, XOOPS_JSON_IN_OBJ))) {
                            array_push(
$stk, array('what' => XOOPS_JSON_IN_CMT, 'where' => $c, 'delim' => false));
                            
$c++;
                        } elseif ((
$substr_chrs_c_2 == '*/') && ($top['what'] == XOOPS_JSON_IN_CMT)) {
                            array_pop(
$stk);
                            
$c++;

                            for (
$i = $top['where']; $i <= $c; ++$i)
                                
$chrs = substr_replace($chrs, ' ', $i, 1);
                        }

                    }

                    if (reset(
$stk) == XOOPS_JSON_IN_ARR) {
                        return 
$arr;

                    } elseif (reset(
$stk) == XOOPS_JSON_IN_OBJ) {
                        return 
$obj;

                    }

                }
        }
    }

    function isError(
$data$code = null)
    {
        if (is_object(
$data) && (get_class($data) == 'services_json_error' ||
                                 is_subclass_of(
$data, 'services_json_error'))) {
            return true;
        }

        return false;
    }
}

class XoopsJSON_Error
{
    function XoopsJSON_Error(
$message = 'unknown error', $code = null,
                                 
$mode = null, $options = null, $userinfo = null)
    {
        trigger_error(
$message);
    }
}

?>


This class would normally live in /class/xoopsjson.php JSON is a fantastic feature we now have access to for forms, objects pages anything advanced with XOOPS and JQuery especially JSON for checking form inputs is great!!

Login

Who's Online

270 user(s) are online (204 user(s) are browsing Support Forums)


Members: 0


Guests: 270


more...

Donat-O-Meter

Stats
Goal: $100.00
Due Date: Nov 30
Gross Amount: $0.00
Net Balance: $0.00
Left to go: $100.00
Make donations with PayPal!

Latest GitHub Commits