Не полагайтесь на порядок ключей в значениях MySQL JSON

Источник: «Don't rely on key ordering with MySQL JSON values»
Вы наверняка слышали, что полагаться на то, что ключи массива сохраняют определённый порядок, опасно. В спецификации JSON эти пары ключ/значение даже определяются как "неупорядоченные". Но, возможно, на практике вы никогда не сталкивались с ситуацией, когда порядок изменялся по сравнению с тем, что вы ожидали.

Недавно я создавал простой дашборд, собирающий данные из upstream API и представляющий их в виде загружаемого CSV-отчёта для пользователя.

Частично цель заключалась в том, чтобы позволить API определить порядок и заголовки для каждого из столбцов данных. Я хранил бы полезную нагрузку из ответов API в одном столбце JSON в базе данных, чтобы позже использовать точное определение данных при сборке отчёта. Но я заметил, что порядок ключей массива строк не сохраняется при загрузке из базы данных.

// данные полученные из API
$payload = [
'first' => 'abc',
'middle' => 'jkl',
'last' => 'xyz',
];

// данные после загрузки из MySQL
$payload = [
'last' => 'xyz',
'first' => 'abc',
'middle' => 'jkl',
];

Я смог решить эту проблему, но мне стало интересно, почему это происходит. Это проблема сериализации PHP, декодирования/кодирования JSON, кастинга свойств Eloquent или что-то ещё?

Я покопался во всех слоях Laravel, но оказалось, что проблема кроется в MySQL. По соображениям производительности и согласованности, MySQL нормализует значения JSON при сохранении в базу данных.

Это объясняет, почему мои массивы перестраивались просто при сохранении и загрузке из базы данных!

И прежде чем вы начнёте полагаться на конкретную стратегию нормализации, которую использует MySQL, ознакомьтесь с этим предупреждением из документации:

Вы должны знать, что результат этого упорядочивания может быть изменён и не гарантируется, что он будет последовательным в разных релизах.

В целом, совет никогда не полагаться на определённый порядок ключей — хороший совет, который стоит запомнить. Если порядок действительно важен, убедитесь, что он явно отслеживается каким-то другим способом.

Дополнительные материалы

Предыдущая Статья

Продвинутые Value Objects в PHP 8

Следующая Статья

Экранирование с e(), htmlspecialchars() и htmlentities()