1) Дублирующиеся ключи
В тексте JSON одно и то же имя поля может встретиться несколько раз — стандарт жёстко не запрещает это, но поведение парсеров разное.
{"id": 1, "id": 2}Какие-то парсеры падают с ошибкой. В JavaScript / в большинстве реализаций и в Python результат будет {"id": 2} (последнее значение побеждает). Это приводит к молчаливой потере данных и багам безопасности, если важный флаг перезаписывается.
2) Мало типов данных
JSON не задаёт битовую длину чисел, но в том же JavaScript Number — 64-битный float, и целые > 2^53−1 втихаря теряют точность.
{"id": 9007199254740993}В JS при JSON.parse получится
9007199254740992.Нет типов для дат, для decimal, для бинарных данных. Всё такое приходится передавать как строки и где-то в другом месте описывать как их интерпретировать.
3) Javascript-овые NaN, Infinity запрещены стандартом, но де факто парсеры обрабатывают по-разному
4) Кодировка и стандарты
JSON обычно в UTF-8, но сторонние сервисы иногда присылают UTF-16 или вообще, прости господи, Windows-1251 - парсеры на этом падают или показывают кракозябры. Наличие в документе BOM также может что-нибудь уронить.
Надо еще отметить, что стандартов json несколько штук, в них разные приколы: в старых стандартах корневым элементом должен быть обязательно объект, а в новых - может быть просто строка или число. Числа начинающиеся на 0 в новых стандартах запрещены, в старых могут интерпретироваться как восьмеричное число.
5) Prototype pollution и unsafe merge
если вы берёте JSON от пользователя и напрямую сливаете его в существующий объект (lodash merge, старые extend), поля вроде
__proto или constructor.prototype могут изменить прототипы и вызвать уязвимость.