Как мы победили «монстров» длинных URL: от Base64 к LZ-String
История о том, как мы решили проблему передачи крупных JSON-фильтров через URL, перепробовали несколько подходов и остановились на LZ-String
Введение
В одном из проектов мы столкнулись с задачей, которая поначалу казалась тривиальной: дать пользователю админки возможность строить навороченные фильтры по товарам — с логическими операторами, вложенностью и длинными списками значений.
После JSON.stringify получался «монстр» из сотен символов, который мы толкали в URL. Казалось бы — не проблема, но как только в фильтр добавлялись ещё пара полей, ссылка гарантированно переваливала за 1000 символов.
И тогда мы:
- написали ручной парсер на
?field[]=…и?field[0][sub]=…— оказалось тяжело поддерживать; - переключились на Base64 — думали, что будет достаточно;
- но поняли: это решение живёт недолго.
Откуда растут ноги у длинных URL
Передача параметров фильтрации часто выглядит просто:
Когда фильтр усложняется
Но когда фильтр становится сложным — глубокие вложенности, сотни ID, логические группы вроде (A ИЛИ B) И (C ИЛИ (D И E)) — после JSON.stringify строка легко превышает 300–500 символов, а Base64 её раздувает до 600+.
Проблемы:
- Обрезается в логах nginx/apache;
- Не проходит через старые прокси;
- Сложно читать и отлаживать.
Классические методы сериализации
Прежде чем найти решение, мы попробовали стандартные подходы.
Сравнение подходов
Query-параметры
Нет зависимостей, просто реализовать. Но плохо читается при множестве полей и не поддерживает вложенные объекты.
Base64-кодирование
Сохраняет структуру JSON, поддерживается везде. Но добавляет +33% к размеру — URL всё равно получается тяжёлым.
Как мы наткнулись на LZ-String
LZ-String — это JS-библиотека на основе алгоритма LZ77 с безопасным для URL кодированием.
Почему она нас заинтересовала:
- Компактность — удаляет повторяющиеся фрагменты;
- Простота — лёгкий API
compressToEncodedURIComponent; - Кроссплатформенность — порты для PHP, Python, C#, Java.
LZ77 ищет повторы в данных и заменяет их ссылками на предыдущие вхождения. LZ-String затем кодирует результат в URL-безопасную строку. Чем больше повторов — тем выше коэффициент сжатия.
Интеграция в проект
Внедрение оказалось максимально простым — и на фронте, и на бэке.
Бенчмарки
Мы провели замеры на реальных данных проекта.
Скорость обработки (JSON ≈ 3 КБ)
Base64
Сжатие: 0.12 мс, распаковка: 0.10 мс
LZ-String
Сжатие: 0.40 мс, распаковка: 0.35 мс
Примеры «до» и «после»
На простых фильтрах выигрыш скромный, но на сложных — существенный.
Простой фильтр
JSON: 89 символов → Base64: 120 символов → LZ-String: 82 символа
Сложный вложенный фильтр
JSON: 312 символов → Base64: 416 символов → LZ-String: 276 символов
Подводные камни
- Зависимость: +≈10 КБ к бандлу;
- CPU: при сотнях запросов/сек может быть заметна нагрузка на сжатие;
- Кодировки: всегда используйте
compressToEncodedURIComponent+ обратный метод; - Безопасность: валидируйте вход на бэке, чтобы избежать zip-бомб.
Заключение
Мы прошли путь от ручного парсинга до Base64, затем открыли LZ-String, провели замеры, внедрили на фронте и бэке, написали тесты и отладили фоллбэк.
В итоге:
- Ссылки короче — ≈30% экономии vs Base64;
- Чистый код — единый метод сериализации;
- Надёжность — валидация и fallback на все случаи.
Разберём вашу задачу и предложим решение