Моя реализация функций JSON для PHP (начиная с PHP 5.2.0 эти функции являются встроенными, если их не отключить)
Моя реализация функций JSON для PHP (начиная с PHP 5.2.0 эти функции являются встроенными, если их не отключить)
<?
// Кодирование и декодирование формата JSON
// Евгений Степанищев (BOLK) 2006 год
class U_JSON
{
// Основная функция, она и вызывается
// $str - строка, которую надо декодировать
// $array - выдавать результат в виде массива или в виде объекта
static function decode($str, $array = false)
{
if ($str == '{}') return $array ? array() : new stdClass();
// всегда должно начинаться с {
if ($str[0] <> '{') return null;
$i = 1;
return self::odecode($str, $array, 0, $i);
}
// рекурсивная функция декодирования
// параметр assoc означает:
// 0 - проходим массив [ .. ]
// 1 - проходим объект { .. }
static private function odecode($str, $array, $assoc, &$i)
{
$out = $array || !$assoc ? array() : new stdClass();
$idx = -1;
for ($len = strlen($str); $i<$len; )
{
if (!$assoc)
{
// теперь идёт пара - name:value, ищем двоеточие
$next = strpos($str, ':', $i);
// если двоеточие не найдено, то неверный формат
if ($next === false)
return null;
$idx = trim(substr($str, $i, $next - $i));
// проверка на строку
if ($idx[0] <> '"' || substr($idx, -1) <> '"')
return null;
// если строка верная, преобразование символов \r и т.п
$idx = self::convert(substr($idx, 1, -1));
// дальше нужно найти значение - это может быть массив или скаляр
$i = $next + 1;
if ($i + 1 >= $len) return null;
}
else
{
$idx++;
}
// нужно пропустить пробелы (FIXME: переделать на strcspn)
for (; $i < $len && $str[$i] == ' '; $i++);
if ($i >= $len) return null;
// это объект
if ($str[$i] == '{')
{
$i++;
if ($array)
$out[$idx] = self::odecode($str, $array, 0, $i); else
$out->$idx = self::odecode($str, $array, 0, $i);
}
else
// это массив
if ($str[$i] == '[')
{
$i++;
if ($array)
$out[$idx] = self::odecode($str, $array, 1, $i); else
$out->$idx = self::odecode($str, $array, 1, $i);
}
else
{
if ($array)
$out[$idx] = self::scalar(substr($str, $i), $l); else
$out->$idx = self::scalar(substr($str, $i), $l);
if ($l === null)
return null;
$i += $l;
}
// тут должна быть запятая или theend
if (!$assoc && $str[$i] == '}' || $assoc && $str[$i] == ']')
{
$i++;
return $out;
}
// нужно пропустить пробелы до запятой (FIXME: переделать на strcspn)
for (; $i < $len && $str[$i] == ' '; $i++);
// проверяем - элементы у нас разделены запятой
if ($i >= $len || $str[$i] <> ',')
return null;
// нужно пропустить пробелы после запятой (FIXME: переделать на strcspn)
for ($i++; $i < $len && $str[$i] == ' '; $i++);
}
return $out;
}
// функция декодирует скаляры - смотрит начинается ли строка
// со скаляра и возвращает его длину
static private function scalar($str, &$len)
{
$scalar =
'/
^(
true |
false |
null |
"(?:\\\\.|[^"])*" |
-?\d+(?:\.\d+)?(?:[Ee][+-]?\d+)*
)/x';
if (preg_match($scalar, $str, $m) && $m[1] <> '')
{
$len = strlen($m[1]);
switch ($m[1][0])
{
case 't':
return true;
case 'f':
return false;
case 'n':
return null;
case '"':
return self::convert(substr($m[1], 1, -1));
default:
return preg_match('/^-?[0-9]+$/', $m[1]) ? (int) $m[1] : (float) $m[1];
}
}
return $len = null;
}
// конвертирование \t, \r, \uXXXX и прочих вхождений
static private function convert($str)
{
// преобразуем комбинацию \u1234 из UCS-2BE в UTF-8
$str = preg_replace
(
'/u([0-9a-f]{2})([0-9a-f]{2})/ie',
"mb_convert_encoding(chr(0x$1).chr(0x$2), 'UTF-8', 'UCS-2BE')",
$str
);
return stripcslashes($str);
}
// Основная функция кодирования в формат JSON
// $mix - массив или объект для кодирования
static public function encode($mix)
{
if (is_scalar($mix) || is_null($mix))
{
return '{}';
}
return self::oencode($mix, true);
}
static private function oencode($mix, $force_hash = false)
{
if (is_scalar($mix) || is_null($mix)) return self::encodeScalar($mix);
// если это массив, но не хеш
if (!$force_hash && self::encodeCheckArray($mix))
{
foreach ($mix as &$val)
{
$val = self::oencode($val);
// убираем ссылку на элемент массива
// чтобы элемент перестал быть ссылкой
unset($val);
}
return '['.implode(',', $mix).']';
}
$out = array();
// это хеш или объект
foreach ($mix as $key => $val)
$out[] = self::encodeString($key).':'.self::oencode($val);
return '{'.implode(',', $out).'}';
}
// функция, которая проверяет является ли переменная
// массивом и, если да, является ли она не хеш массивом
static private function encodeCheckArray($mix)
{
if (!is_array($mix)) return false;
// массив не содержит элементов,
// считаем что это не хеш
$cnt = sizeof($mix);
if (!$cnt) return true;
// обычно сюда передаются очень небольшие массивы,
// поэтому проверять будем на сравнение с эталоном,
// это проще всего
$model = range(0, $cnt - 1);
return $model == array_keys($mix);
}
// кодирование скаляров - number, string, boolean, NULL
static private function encodeScalar($mix)
{
switch (true)
{
case is_numeric($mix):
return $mix;
case is_bool($mix):
return $mix ? 'true' : 'false';
case is_null($mix):
return 'null';
default:
return self::encodeString($mix);
}
}
// кодирование строки
static private function encodeString($str)
{
// замена в строках: нужно заэкранировать ", / и \,
// заменить \n, \r, \t, \b, \f
return '"'.addcslashes($str, "\"\n\r\t\x08\x0C/").'"';
}
}
function json_decode($str, $array = false)
{
return U_JSON::decode($str, $array);
}
function json_encode($mix)
{
return U_JSON::encode($mix);
}
?>