3 * Zend Framework (http://framework.zend.com/)
5 * @link http://github.com/zendframework/zf2 for the canonical source repository
6 * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
7 * @license http://framework.zend.com/license/new-bsd New BSD License
13 use Zend\Json\Exception\InvalidArgumentException
;
14 use Zend\Json\Exception\RuntimeException
;
17 * Decode JSON encoded string to PHP variable constructs
22 * Parse tokens used to decode the JSON object. These are not
23 * for public consumption, they are just used internally to the
36 * Use to maintain a "pointer" to the source being decoded
43 * Caches the source length
47 protected $sourceLength;
50 * The offset within the source being decoded
58 * The current token being considered in the parser cycle
65 * Flag indicating how objects should be decoded
70 protected $decodeType;
75 protected $tokenValue;
78 * Decode Unicode Characters from \u0000 ASCII syntax.
80 * This algorithm was originally developed for the
81 * Solar Framework by Paul M. Jones
83 * @link http://solarphp.com/
84 * @link https://github.com/solarphp/core/blob/master/Solar/Json.php
88 public static function decodeUnicodeString($chrs)
90 $chrs = (string) $chrs;
92 $strlenChrs = strlen($chrs);
94 for ($i = 0; $i < $strlenChrs; $i++
) {
95 $ordChrsC = ord($chrs[$i]);
98 case preg_match('/\\\u[0-9A-F]{4}/i', substr($chrs, $i, 6)):
99 // single, escaped unicode character
100 $utf16 = chr(hexdec(substr($chrs, ($i +
2), 2)))
101 . chr(hexdec(substr($chrs, ($i +
4), 2)));
102 $utf8char = self
::_utf162utf8($utf16);
103 $search = array('\\', "\n", "\t", "\r", chr(0x08), chr(0x0C), '"', '\'', '/');
104 if (in_array($utf8char, $search)) {
105 $replace = array('\\\\', '\\n', '\\t', '\\r', '\\b', '\\f', '\\"', '\\\'', '\\/');
106 $utf8char = str_replace($search, $replace, $utf8char);
111 case ($ordChrsC >= 0x20) && ($ordChrsC <= 0x7F):
114 case ($ordChrsC & 0xE0) == 0xC0:
115 // characters U-00000080 - U-000007FF, mask 110XXXXX
116 //see http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8
117 $utf8 .= substr($chrs, $i, 2);
120 case ($ordChrsC & 0xF0) == 0xE0:
121 // characters U-00000800 - U-0000FFFF, mask 1110XXXX
122 // see http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8
123 $utf8 .= substr($chrs, $i, 3);
126 case ($ordChrsC & 0xF8) == 0xF0:
127 // characters U-00010000 - U-001FFFFF, mask 11110XXX
128 // see http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8
129 $utf8 .= substr($chrs, $i, 4);
132 case ($ordChrsC & 0xFC) == 0xF8:
133 // characters U-00200000 - U-03FFFFFF, mask 111110XX
134 // see http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8
135 $utf8 .= substr($chrs, $i, 5);
138 case ($ordChrsC & 0xFE) == 0xFC:
139 // characters U-04000000 - U-7FFFFFFF, mask 1111110X
140 // see http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8
141 $utf8 .= substr($chrs, $i, 6);
153 * @param string $source String source to decode
154 * @param int $decodeType How objects should be decoded -- see
155 * {@link Zend\Json\Json::TYPE_ARRAY} and {@link Zend\Json\Json::TYPE_OBJECT} for
157 * @throws InvalidArgumentException
159 protected function __construct($source, $decodeType)
162 $this->source
= self
::decodeUnicodeString($source);
163 $this->sourceLength
= strlen($this->source
);
164 $this->token
= self
::EOF
;
167 switch ($decodeType) {
168 case Json
::TYPE_ARRAY
:
169 case Json
::TYPE_OBJECT
:
170 $this->decodeType
= $decodeType;
173 throw new InvalidArgumentException("Unknown decode type '{$decodeType}', please use one of the constants Json::TYPE_*");
176 // Set pointer at first token
177 $this->_getNextToken();
181 * Decode a JSON source string
183 * Decodes a JSON encoded string. The value returned will be one of the
191 * - array of one or more of the above types
193 * By default, decoded objects will be returned as associative arrays; to
194 * return a stdClass object instead, pass {@link Zend\Json\Json::TYPE_OBJECT} to
195 * the $objectDecodeType parameter.
199 * @param string $source String to be decoded
200 * @param int $objectDecodeType How objects should be decoded; should be
201 * either or {@link Zend\Json\Json::TYPE_ARRAY} or
202 * {@link Zend\Json\Json::TYPE_OBJECT}; defaults to TYPE_ARRAY
205 public static function decode($source, $objectDecodeType = Json
::TYPE_OBJECT
)
207 $decoder = new static($source, $objectDecodeType);
208 return $decoder->_decodeValue();
212 * Recursive driving routine for supported toplevel tops
216 protected function _decodeValue()
218 switch ($this->token
) {
220 $result = $this->tokenValue
;
221 $this->_getNextToken();
224 return($this->_decodeObject());
226 return($this->_decodeArray());
233 * Decodes an object of the form:
234 * { "attribute: value, "attribute2" : value,...}
236 * If Zend\Json\Encoder was used to encode the original object then
237 * a special attribute called __className which specifies a class
238 * name that should wrap the data contained within the encoded source.
240 * Decodes to either an array or stdClass object, based on the value of
241 * {@link $decodeType}. If invalid $decodeType present, returns as an
244 * @return array|stdClass
245 * @throws RuntimeException
247 protected function _decodeObject()
250 $tok = $this->_getNextToken();
252 while ($tok && $tok != self
::RBRACE
) {
253 if ($tok != self
::DATUM ||
! is_string($this->tokenValue
)) {
254 throw new RuntimeException('Missing key in object encoding: ' . $this->source
);
257 $key = $this->tokenValue
;
258 $tok = $this->_getNextToken();
260 if ($tok != self
::COLON
) {
261 throw new RuntimeException('Missing ":" in object encoding: ' . $this->source
);
264 $this->_getNextToken();
265 $members[$key] = $this->_decodeValue();
268 if ($tok == self
::RBRACE
) {
272 if ($tok != self
::COMMA
) {
273 throw new RuntimeException('Missing "," in object encoding: ' . $this->source
);
276 $tok = $this->_getNextToken();
279 switch ($this->decodeType
) {
280 case Json
::TYPE_OBJECT
:
281 // Create new stdClass and populate with $members
282 $result = new stdClass();
283 foreach ($members as $key => $value) {
287 $result->$key = $value;
290 case Json
::TYPE_ARRAY
:
296 $this->_getNextToken();
301 * Decodes a JSON array format:
302 * [element, element2,...,elementN]
305 * @throws RuntimeException
307 protected function _decodeArray()
310 $tok = $this->_getNextToken(); // Move past the '['
313 while ($tok && $tok != self
::RBRACKET
) {
314 $result[$index++
] = $this->_decodeValue();
318 if ($tok == self
::RBRACKET ||
!$tok) {
322 if ($tok != self
::COMMA
) {
323 throw new RuntimeException('Missing "," in array encoding: ' . $this->source
);
326 $tok = $this->_getNextToken();
329 $this->_getNextToken();
334 * Removes whitespace characters from the source input
336 protected function _eatWhitespace()
338 if (preg_match('/([\t\b\f\n\r ])*/s', $this->source
, $matches, PREG_OFFSET_CAPTURE
, $this->offset
)
339 && $matches[0][1] == $this->offset
) {
340 $this->offset +
= strlen($matches[0][0]);
345 * Retrieves the next token from the source stream
347 * @return int Token constant value specified in class definition
348 * @throws RuntimeException
350 protected function _getNextToken()
352 $this->token
= self
::EOF
;
353 $this->tokenValue
= null;
354 $this->_eatWhitespace();
356 if ($this->offset
>= $this->sourceLength
) {
360 $str = $this->source
;
361 $strLength = $this->sourceLength
;
367 $this->token
= self
::LBRACE
;
370 $this->token
= self
::RBRACE
;
373 $this->token
= self
::LBRACKET
;
376 $this->token
= self
::RBRACKET
;
379 $this->token
= self
::COMMA
;
382 $this->token
= self
::COLON
;
388 if ($i >= $strLength) {
396 if ($i >= $strLength) {
429 throw new RuntimeException("Illegal escape sequence '{$chr}'");
431 } elseif ($chr == '"') {
436 } while ($i < $strLength);
438 $this->token
= self
::DATUM
;
439 //$this->tokenValue = substr($str, $start + 1, $i - $start - 1);
440 $this->tokenValue
= $result;
443 if (($i+
3) < $strLength && substr($str, $start, 4) == "true") {
444 $this->token
= self
::DATUM
;
446 $this->tokenValue
= true;
450 if (($i+
4) < $strLength && substr($str, $start, 5) == "false") {
451 $this->token
= self
::DATUM
;
453 $this->tokenValue
= false;
457 if (($i+
3) < $strLength && substr($str, $start, 4) == "null") {
458 $this->token
= self
::DATUM
;
460 $this->tokenValue
= null;
465 if ($this->token
!= self
::EOF
) {
466 $this->offset
= $i +
1; // Consume the last token character
467 return($this->token
);
471 if ($chr == '-' ||
$chr == '.' ||
($chr >= '0' && $chr <= '9')) {
472 if (preg_match('/-?([0-9])*(\.[0-9]*)?((e|E)((-|\+)?)[0-9]+)?/s', $str, $matches, PREG_OFFSET_CAPTURE
, $start) && $matches[0][1] == $start) {
473 $datum = $matches[0][0];
475 if (is_numeric($datum)) {
476 if (preg_match('/^0\d+$/', $datum)) {
477 throw new RuntimeException("Octal notation not supported by JSON (value: {$datum})");
479 $val = intval($datum);
480 $fVal = floatval($datum);
481 $this->tokenValue
= ($val == $fVal ?
$val : $fVal);
484 throw new RuntimeException("Illegal number format: {$datum}");
487 $this->token
= self
::DATUM
;
488 $this->offset
= $start +
strlen($datum);
491 throw new RuntimeException('Illegal Token');
498 * Convert a string from one UTF-16 char to one UTF-8 char.
500 * Normally should be handled by mb_convert_encoding, but
501 * provides a slower PHP-only method for installations
502 * that lack the multibyte string extension.
504 * This method is from the Solar Framework by Paul M. Jones
506 * @link http://solarphp.com
507 * @param string $utf16 UTF-16 character
508 * @return string UTF-8 character
510 protected static function _utf162utf8($utf16)
512 // Check for mb extension otherwise do by hand.
513 if (function_exists('mb_convert_encoding')) {
514 return mb_convert_encoding($utf16, 'UTF-8', 'UTF-16');
517 $bytes = (ord($utf16{0}) << 8) |
ord($utf16{1});
520 case ((0x7F & $bytes) == $bytes):
521 // this case should never be reached, because we are in ASCII range
522 // see: http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8
523 return chr(0x7F & $bytes);
525 case (0x07FF & $bytes) == $bytes:
526 // return a 2-byte UTF-8 character
527 // see: http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8
528 return chr(0xC0 |
(($bytes >> 6) & 0x1F))
529 . chr(0x80 |
($bytes & 0x3F));
531 case (0xFFFF & $bytes) == $bytes:
532 // return a 3-byte UTF-8 character
533 // see: http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8
534 return chr(0xE0 |
(($bytes >> 12) & 0x0F))
535 . chr(0x80 |
(($bytes >> 6) & 0x3F))
536 . chr(0x80 |
($bytes & 0x3F));
539 // ignoring UTF-32 for now, sorry