- Strange behaviour: json_encode() producing invalid json dependent on input
- In short:
- The Problem:
- My Question
- Does anyone experience this problem also?
- The original code
- 1 Answer 1
- Решение типовых проблем с json_encode (PHP)
- Доступ к полям
- Решение
- Почему не стоит использовать подход с toJson методом?
- Что если у меня очень много полей в класcе?
- А если нужно private-поля, из класса, который нет возможности редактировать?
- Кодировка текстовых значений
- Кириллица и другие знаки в UTF8
- Символы в других кодировках
- Цифровые значения
- json_encode returns invalid JSON code
Strange behaviour: json_encode() producing invalid json dependent on input
today I had to deal with some veeeery strange behaviour of php’s json_encode(), that I could nowhere read about.
In short:
- Fetching a row of the database
- Building a stdClass-object and storing some of the data in there
- Encoding it using json_encode()
$obj = new stdClass(); $obj->a = "foo"; $obj->b = "bar"; $obj->c = "doo"; $obj->d = "baa"; json_encode( $obj );
The Problem:
The returned JSON was buggy: a second (the last one, but only this one!) variable was merged with into the string value of the previous one.
For example it looked like:
Of course I’ve var_dumped() the object I’m passing over to json_encode(). nothing! Everything was fine and exactly as expected. All the vars were correct and all the values were strings.
But well, that’s not enough. Playing around with that, I found out, that changing the order in wich I’m storing the values into the object solved the problem:
For now I just changed the order of the columns of the database table. But I would like to know, where this never-seen bug comes from and eventually report it.
The bug only appeared on a Debian-VM running PHP 5.3.6. Another VM running PHP 5.3.3 produced correct JSON, just like Mac OS X Lion’s integrated webserver running PHP 5.3.8.
My Question
Does anyone experience this problem also?
The original code
$obj = new stdClass(); $obj->Id = '35'; $obj->EventsCategories_Id = '8'; $obj->Name = 'Blubber'; $obj->OrderValue = '2'; var_dump($obj); json_encode($obj);
I just tested this on PHP 5.3.8 and I can confirm got the correct output string(41) «<"a":"foo","b":"bar","c":"doo","d":"baa">» . Just throwing this out there to confirm that 5.3.8 is working correctly."a":"foo","b":"bar","c":"doo","d":"baa">
I just installed PHP 5.3.6 (CLI, Windows, VC9) to run your same code and original code on and I still got the correct output. I tried both the thread-safe and non thread-safe versions and still got the same results.
Can you post what the results of var_dump($obj) and var_dump(json_encode($obj)) are in your environment?
Nice question/formatting. But can’t reproduce it either with 5.3.6-13ubuntu3.6+suhosin (which is close to your Debian build). You should definitely file this as bug on php.net and debian.org.
1 Answer 1
I have no idea about whether this will help or not, but does running the following code produce any different results?
$obj = new stdClass(); $obj->Id = '35'; $obj->EventsCategories_Id = '8'; $obj->Name = 'Blubber'; $obj->OrderValue = '2'; // convert the stdClass object to an array $obj = (array) $obj; var_dump($obj); json_encode($obj);
I think the pertinent question to ask is whether this is:
- an internal PHP issue (possible, but unlikely)
- a problem arising from some environmental issue (a conflicting library?)
- a missing configuration option
While I highly doubt that you’ve done something wrong (I mean, this is a simple use-case and there’s nothing wrong with your code at all), perhaps you could try a couple of other things.
For example, try appending a 2nd argument to the json_encode function:
echo json_encode($obj, JSON_FORCE_OBJECT);
Could you post the results of these two short experiments? This problem is quite strange, indeed. I’m running PHP 5.3.1 and it generates syntactically correct JSON based on your example — no problems to report.
Решение типовых проблем с json_encode (PHP)
Это краткая статья о наиболее вероятных проблемах с json_encode и их решениях. Иногда при кодировании данных в json, с помощью json_encode в php, мы получаем не тот результат который ожидаем. Я выделил три наиболее частые проблемы с которыми сталкиваются программисты:
Доступ к полям
Проблема заключается в том что json_encode имеет доступ только к публичным полям объекта. Например если у вас есть класс
class Example < public $publicProperty; protected $protectedProperty; private $privateProperty; public function __construct($public, $protected, $private) < $this->publicProperty = $public; $this->protectedProperty = $protected; $this->privateProperty = $private; > >
то результатом выполнения следующего кода будет:
$obj = new Example("some", "value", "here"); echo json_encode($obj); //
как видно в результирующий json были включены только публичные поля.
Что же делать если нужны все поля?
Решение
Для php < 5.4:
нам необходимо будет реализовать в классе метод который будет возвращать готовый json. Т.к. внутри класса есть доступ ко всем полям можно сформировать правильное представление объекта для json_encode
class Example < public $publicProperty; protected $protectedProperty; private $privateProperty; public function __construct($public, $protected, $private) < $this->publicProperty = $public; $this->protectedProperty = $protected; $this->privateProperty = $private; > public function toJson() < return json_encode([ 'publicProperty' =>$this->publicProperty, 'protectedProperty' => $this->protectedProperty, 'privateProperty' => $this->privateProperty, ]); > >
Для получение json-a c объекта теперь нужно пользоваться методом toJson, а не прямым применением json_encode к объекту
$obj = new Example("some", "value", "here"); echo $obj->toJson();
Для php >= 5.4:
достаточно будет реализовать интерфейс JsonSerializable для нашего класса, что подразумевает добавление метода jsonSerialize который будет возвращать структуру представляющую объект для json_encode
class Example implements JsonSerializable < public $publicProperty; protected $protectedProperty; private $privateProperty; public function __construct($public, $protected, $private) < $this->publicProperty = $public; $this->protectedProperty = $protected; $this->privateProperty = $private; > public function jsonSerialize() < return [ 'publicProperty' =>$this->publicProperty, 'protectedProperty' => $this->protectedProperty, 'privateProperty' => $this->privateProperty, ]; > >
Теперь мы можем использовать json_encode как и раньше
$obj = new Example("some", "value", "here"); echo json_encode($obj); //
Почему не стоит использовать подход с toJson методом?
Многие наверно заметили что подход с созданием метода возвращающего json может быть использован и в версиях php >= 5.4. Так почему же не воспользоваться им? Все дело в том что ваш класс может быть использован как часть иной структуры данных
echo json_encode([ 'status' => true, 'message' => 'some message', 'data' => new Example("some", "value", "here"), ]);
и результат уже будет совсем другой.
Также класс может использоваться другими программистами, для которых такой тип получение json-а с объекта может быть не совсем очевиден.
Что если у меня очень много полей в класcе?
В таком случае можно воспользоваться функцией get_object_vars
class Example implements JsonSerializable < public $publicProperty; protected $protectedProperty; private $privateProperty; protected $someProp1; . protected $someProp100500; public function __construct($public, $protected, $private) < $this->publicProperty = $public; $this->protectedProperty = $protected; $this->privateProperty = $private; > public function jsonSerialize() < $fields = get_object_vars($this); // что-то делаем . return $fields; >>
А если нужно private-поля, из класса, который нет возможности редактировать?
Может получиться ситуация когда нужно получить private поля (именно private, т.к. доступ к protected полям можно получить через наследование) в json-е. В таком случае необходимо будет воспользоваться рефлексией:
class Example < public $publicProperty = "someValue"; protected $protectedProperty; private $privateProperty1; private $privateProperty2; private $privateProperty3; public function __construct($privateProperty1, $privateProperty2, $privateProperty3, $protectedProperty) < $this->protectedProperty = $protectedProperty; $this->privateProperty1 = $privateProperty1; $this->privateProperty2 = $privateProperty2; $this->privateProperty3 = $privateProperty3; > > $obj = new Example("value1", 12, "21E021", false); $reflection = new ReflectionClass($obj); $public = []; foreach ($reflection->getProperties() as $property) < $property->setAccessible(true); $public[$property->getName()] = $property->getValue($obj); > echo json_encode($public); //
Кодировка текстовых значений
Кириллица и другие знаки в UTF8
Второй тип распространённых проблем с json_encode это проблемы с кодировкой. Часто текстовые значения которые нужно кодировать в json имеют в себе символы в UTF8 (в том числе кириллица) в результате эти символы будут представлены в виде кодов:
echo json_encode("кириллица or ₳ ƒ 元 ﷼ ₨ ௹ ¥ ₴ £ ฿ $"); // "\u043a\u0438\u0440\u0438\u043b\u043b\u0438\u0446\u0430 or \u20b3 \u0192 \u5143 \ufdfc \u20a8 \u0bf9 \uffe5 \u20b4 \uffe1 \u0e3f \uff04"
Отображение таких символов лечится очень просто — добавлением флага JSON_UNESCAPED_UNICODE вторым аргументом к функции json_encode:
echo json_encode("кириллица or ₳ ƒ 元 ﷼ ₨ ௹ ¥ ₴ £ ฿ $", JSON_UNESCAPED_UNICODE); // "кириллица or ₳ ƒ 元 ﷼ ₨ ௹ ¥ ₴ £ ฿ $"
Символы в других кодировках
Функция json_encode воспринимает строковые значения как строки в UTF8, что может вызвать ошибку, если кодировка другая. Рассмотрим маленький кусочек кода (данный пример кода максимально упрощен для демонстрации проблемной ситуации)
На первый взгляд ничего не предвещает проблем, да и что здесь может пойти не так? Я тоже так думал. В подавляющем большинстве случаев все будет работать, и по этой причине поиск проблемы занял у меня несколько больше времени, когда я впервые столкнулся с тем что результатом json_encode было false.
Для воссоздания такой ситуации предположим что p=%EF%F2%E8%F6%E0 (на пример: localhost?=%EF%F2%E8%F6%E0 ).
*Переменные в суперглобальных массивах $_GET и $_REQUEST уже декодированы.
$decoded = urldecode("%EF%F2%E8%F6%E0"); var_dump(json_encode($decoded)); // bool(false) var_dump(json_last_error_msg()); // string(56) "Malformed UTF-8 characters, possibly incorrectly encoded"
Как можно увидеть из ошибки: проблема с кодировкой переданной строки (это не UTF8). Решение проблемы очевидное — привести значение в UTF8
$decoded = urldecode("%EF%F2%E8%F6%E0"); $utf8 = utf8_encode($decoded); echo json_encode($utf8); // "ïòèöà"
Цифровые значения
Последняя типовая ошибка связана с кодированием числовых значений.
echo json_encode(["string_float" => "3.0"]); //
Как известно php не строго типизированный язык и позволяет использовать числа в виде строки, в большинстве случаев это не приводит к ошибкам внутри php приложения. Но так как json очень часто используется для передачи сообщений между приложениями, такой формат записи числа может вызвать проблемы в другом приложении. Желательно использовать флаг JSON_NUMERIC_CHECK:
echo json_encode(["string_float" => "3.0"], JSON_NUMERIC_CHECK); //
Уже лучше. Но как видим «3.0» превратилось в 3, что в большинстве случаев будет интерпретировано как int. Используем еще один флаг JSON_PRESERVE_ZERO_FRACTION для корректного преобразования в float:
echo json_encode(["string_float" => "3.0"], JSON_NUMERIC_CHECK | JSON_PRESERVE_ZERO_FRACTION); //
Прошу также обратить внимание на следующий фрагмент кода, что иллюстрирует ряд возможных проблем с json_encode и числовыми значениями:
$data = [ "0000021", // нули слева 6.12345678910111213, // много знаков после точки (будет округленно) "+81011321515", // телефон "21E021", // экспоненциальная запись ]; echo json_encode($data, JSON_NUMERIC_CHECK); //[ // 21, // 6.1234567891011, // 81011321515, // 2.1e+22 // ]
Буду рад увидеть в комментариях описание проблем, с которыми вы сталкивались, что не были упомянуты в статье
json_encode returns invalid JSON code
I am creating with PHP some JSON-data that seems to be invalid. I’m trying to integrate the google API in my code.
'id', 'type' => 'string'), array('label' => 'Q1', 'type' => 'number'), array('label' => 'Q2', 'type' => 'number') ); $rows = array(); while($r = mysql_fetch_assoc($sth)) < $temp = array(); // the following line will be used to slice the Pie chart $temp[] = array('v' =>(string) $r['id']); // Values of each slice $temp[] = array('v' => (int) $r['Q1'], 'f' => (int) $r['Q2']); $rows[] = array('c' => $temp); > $table['rows'] = $rows; $jsonTable = json_encode($table); echo $jsonTable; ?>
And the error I could see when I validate Parse error on line 1: < cols: [ -----^ Expecting 'STRING', '>‘ print_r($table) output
Array ( [cols] => Array ( [0] => Array ( [label] => id [type] => string ) [1] => Array ( [label] => Q1 [type] => number ) [2] => Array ( [label] => Q2 [type] => number ) ) [rows] => Array ( [0] => Array ( [c] => Array ( [0] => Array ( [v] => 8710058770 ) [1] => Array ( [v] => 35 ) [2] => Array ( [v] => 40 ) ) ) [1] => Array ( [c] => Array ( [0] => Array ( [v] => 8710058770 ) [1] => Array ( [v] => 60 ) [2] => Array ( [v] => 70 ) ) ) [2] => Array ( [c] => Array ( [0] => Array ( [v] => 8710058770 ) [1] => Array ( [v] => 75 ) [2] => Array ( [v] => 85 ) ) ) ) ) <"cols":[<"label":"id","type":"string">,,],"rows":[<"c":[<"v":"8710058770">,,]>,<"c":[<"v":"8710058770">,,]>,<"c":[<"v":"8710058770">,,]>]>