Наконец-то зарелизил спеку VaryPack - новый, простой, гибкий, шустрый и компактный формат бинарной сериализации произвольных данных.

TS библиотека в MAM — $mol_vary, в NPM — mol_vary. Это всего 600 строчек кода, которые легко портировать на любой другой язык.
? Киллер фичи
Дедупликация. Строки, числа, шейпы объектов (упорядоченные списки имён полей) не дублируются, благодаря встроенной поддержке ссылок. Другие форматы этого достигают только через расширения, ломающие совместимость между библиотеками.
Расширяемость поддержимаемых типов. Из коробки VaryPack уже поддерживает огромные целые, ссылки, бинарники, а $mol_vary ещё и словари, и множества, и таймштампы, и даже DOM! Но довольно легко добавить поддержку и своих типов данных, ни с кем это не согласовывая — маппинг происходит по совпадению шейпов, которые сериализуются вместе с объектами. Другие форматы вынуждены вести централизованные реестры расширений и диапазоны идентификаторов, где творится дикий запад.
Прямая и обратная совместимости. Любой VaryPack декодер сможет распаковать данные, даже если не поддерживает те или иные кастомные типы данных (будет их представление через поддеживаемые типы). Расширения других форматов таким похвастаться не могут.
⁉️ Внимание, вопрос
Пока на VaryPack ещё никто не завязался есть возможность безболезненно его менять. Так что гляньте спеку, попробуйте поиграться со своими данными. Если будет не хватать какой-то фичи — дайте знать. Подумаем, можно ли поддержать и её.
Например, VaryPack сохраняет ссылочную структуру, если она не образует циклов. Можно было бы поддержать и произвольные графы связей между объектами. Но стоит ли это делать ценой усложнения и замедления кода?
Комментарии (22)

a-tk
14.11.2025 06:54А где в опросе yaml?

nin-jin Автор
14.11.2025 06:54Не слышал, чтобы его кто-то использовал для бинаризации данных.

a-tk
14.11.2025 06:54А JSON и XML?

nin-jin Автор
14.11.2025 06:54А вот их используют, как бы кринжово это ни выглядело.

a-tk
14.11.2025 06:54А что в Вашем понимании бинарные данные? Все данные в памяти ПК так или иначе бинарные.

nin-jin Автор
14.11.2025 06:54А все данные в том же json - строки. Поэтому картинки, например, приходится кодировать в base64+utf8.

a-tk
14.11.2025 06:54Картинки можно запихнуть в base64 и сохранить в YAML точно так же, как в JSON

nin-jin Автор
14.11.2025 06:54А вы что доказать то пытаетесь?

a-tk
14.11.2025 06:54Я пытаюсь понять, почему Вы так упираетесь.
Сказали был что про YAML не подумали - вопросов не было бы. Но Вы же сами зачем-то пытаетесь как-то обосновать отсутствие YAML в списке.

nin-jin Автор
14.11.2025 06:54Единственная причина присутствия его в заголовке - его реально используют для сериализация бинарных данных. Оба они из другой категории: https://page.hyoo.ru/#!=8i7ao7_xfyxah

ColdPhoenix
14.11.2025 06:54Забавно, что на хабре вы размер округлили вниз, а на NPM/Github округлили вверх.
Ну и сравнивать совместимость пока у вашего стандарта ровно одна реализация это кощунство ИМХО.

nin-jin Автор
14.11.2025 06:54Скриншот уже устарел, да. Со скринами такое бывает.
Сравнивается концептуальная совместимость.

nuclight
14.11.2025 06:54На связи участник IETF CBOR WG, занимающийся CBAR/CBAPT, альтернативным к принимаемому сейчас cbor-packed стандарту паковки CBOR. Так вот, глядя на что по ссылке - ЭТО не спека. Более того, неразбериха начинается уже в таблице для привлечения внимания - почему размер 100% лучше 30% и 40% ? Почему время обработки 120% лучше времени 100% ? Чтобы такого не было, нужно приводить абсолютные цифры, пример у автора cbor-x в https://gist.github.com/kriszyp/b623b85d2dc25ac9e3b07d8f39df9307 и кстати, в случае паковки положено приводить столбец еще и с gzip.
Переходя к самой "спеке", это не спека, а какая-то мазня на салфетках из ресторана. По ней не только невозможно сделать независимую имплементацию - ни алгоритмов, ни тест-векторов... - но даже просто понять, что собсно имел в виду автор. Ну вот допустим деление на 3 бита и 5 бит понятно (правда, тем, кто уже в теме, для остальных это вообще-то надо пояснять), но тут же справа на картинке "-27 <= num <= 27" - чего?.. такой диапазон в 5 бит не влезет. Значение Both ржачно - кому-то реально нужен такой bool? При этом остальные значения в spec куда-то пропали.sint - целое число от −2n2104 до 2n2104-1. Семантика num - целое число со знаком. Значения от -1 до -27 помещаются в байт kind. Значения от -28 до -31 определяют число доп байт для представления больших чисел. -32 означает, что число доп байт указано в следующем байте. За числами от -28 резервируется индекс для ссылок.
Это вот что за порнография? Почему 2n и почему только до 2104 бит? Что, 4096-битный RSA-ключ уже нельзя? Почему они все здесь отрицательные, хотя uint ссылался sint и для положительных тоже?
blob - бинарник. Семантика num - число байт. Лейаут tnum аналогичен uint. После tnum идёт kind для задания типа элементаю. А после него сам бинарник. За всеми бинарниками резервируется индекс для ссылок.
Терминология просто ппц, что при этом должен значит тип в kind, абсолютно непонятно, потому что у нас уже снаружи этот самый kind, что за рекурсия такая?
Дальше, туплы. Нахер они нужны вместо обычных нормальных мапов, нам опять же никто не рассказывает. Зато лишние байты это тратит еще как. При этом до полноценных таблиц ни один горе-иззобретатель так и не сумел еще додуматься...
Наконец, расширяемость. То есть фактически её отсутствие. Каким-то неведомым вывертом мозга автор нормальную практику расширений и спецификаций объявляет "диким западом". В реальности же этот формат столь же нерасширяем, как msgpack. Соответственно, пассаж про прямую и обратную совместимость - чистой воды лживая лапша на уши, потому что таким же свойством будет обладать и банальный JSON - да, декодер точно так же может всё обработать, и? Он и в CBOR это сможет, и? Ну ладно, допустим:
маппинг происходит по совпадению шейпов, которые сериализуются вместе с объектами
[56.4, 37.3]
-вот это вот "шейп" чего имеет, простите? Это географические координаты? Или это температура внутри и снаружи нашего устройства? Или что-то еще третье, которое имеет три поля, но хвостовые можно не передавать для компактности в случае их равенства дефолтным значениям? Вот в CBOR будет однозначно, если тег 103 - это таки широта и долгота.
Или вот там в примере для этого вашего отвратительного Date (про который юмористические презентации есть) поле unix_time. Ну ладно, допустим оно там внутри такое (я к счастью JS не знаю и не пишу на нём). Опять же, что будет, если попадутся другие данные, в которых тоже будет поле unix_time, но это объект другого типа?Наконец, расширяемость уровня самого формата. В CBOR это можно в широких пределах делать самими тегами, и собственно, самые первые теги, зарегестрированные после RFC, были 25/256 для паковки повторяющих строк (stringref), чуть позже для объектов вообще (28/29), что по совокупности дает то же самое, что здесь было описано как link, то есть совершенно ничего нового. Теперь допустим надо поддержать fp128 - и что тогда? Никак? Блоб длиной 16 байт? Отлично, как его отличать будет от, допустим, UUID ? В CBOR, кстати, тег для UUID зарегистрирован, как и для IPv6-адреса - так что эти блобы одинаковой длины легко отличить. В общем, что за поклёп на CBOR во фразе:
Расширения по задумке должны быть обратно совместимы, но на практике это не так.
решительно непонятно - есть примеры "расширений" CBOR (вообще-то они не таковы), которые не совместимы сами с собой с течением времени?
Про примеры XML и какой-то еще хрени, где неведомыми образом откуда-то берутся текстовые поля - откуда? - комментировать уже лень. На little endian я тоже уже устал ржать (что, и bignum'ы длиннее 8 байт тоже будут little-endian?).
Резюмируя: недоспецифицированная фигня с неочерченным кругом задач и сфер (вот, например, CBOR можно обрабатывать на IoT-устройствах с 1 Кб RAM, а здесь жестко забитый link этому помешает, несмотря на выгоды от паковки, поэтому cbor-packed и альтернативы делают не так), скорее всего неспособная выйти за пределы даже одного языка программирования. По сравнению с Интернет-Стандартом - не, не взлетит.

nin-jin Автор
14.11.2025 06:54почему размер 100% лучше 30% и 40% ?
Там +30% и +40%
Почему время обработки 120% лучше времени 100% ?
Это не время обработки, а скорость.
Чтобы такого не было, нужно приводить абсолютные цифры
Абсолютные цифры на конкретных тестовых данных можете глянуть в бенчмарке. Эта таблица даёт примерную оценку вцелом.
в случае паковки положено приводить столбец еще и с gzip
gzip с какими настройками сжатия там где-то у вас положено? Зипование нивелирует разницу между форматами, но даёт просадку скорости в несколько раз. Смысла это замерять нет никакого - размер зависит в большей степени от уровня энтропии данных.
По ней не только невозможно сделать независимую имплементацию - ни алгоритмов, ни тест-векторов
Всё это есть в референсной реализации.
но тут же справа на картинке "
-27 <= num <= 27" - чего?.. такой диапазон в 5 бит не влезет.Всё прекрасно влезает. Положительные числа начинаются с 000, а отрицательные с 111.
Значение Both ржачно - кому-то реально нужен такой bool?
Это значение не из булевой логики. Более известно как undefined. И да, оно реально нужно. И у вас оно тоже есть.
При этом остальные значения в spec куда-то пропали.
О чём речь?
Почему 2n и почему только до 2104 бит?
(9+254)*8- ровно столько бит можно закодировать одним доп байтом.n- это суффикс бигинтов в JS, убрал его.Что, 4096-битный RSA-ключ уже нельзя?
Он состоит из двух чисел по 2048 бит. Впрочем, сейчас RSA использовать уже нет смысла, так как эллиптическим кривым достаточно чисел размером на порядок меньше.
Почему они все здесь отрицательные, хотя uint ссылался sint и для положительных тоже?
Потому что речь про отрицательные
numв байтеkind.Терминология просто ппц, что при этом должен значит тип в kind, абсолютно непонятно, потому что у нас уже снаружи этот самый kind, что за рекурсия такая?
Уточнил формулировку, чтобы даже вы её поняли.
Дальше, туплы. Нахер они нужны вместо обычных нормальных мапов, нам опять же никто не рассказывает.
А нахер нужны мапы вам кто сказал? В принципе, это +- то же самое. С той лишь разницей, что есть отдельный тип Map, который декодируется именно в словарь, а не в экземпляр ещё какого-нибудь класса.
Зато лишние байты это тратит еще как.
Да особо не тратит: обычно это длина кортежа + ссылка на шейп. Можно было бы избавиться от длины и брать её из шейпа, но я не придумал, как это сделать без адовых костылей. К тому же, разную длину можно для чего-нибудь приспособить. Например, для таблиц.
При этом до полноценных таблиц ни один горе-иззобретатель так и не сумел еще додуматься...
Полноценные - это какие?
Каким-то неведомым вывертом мозга автор нормальную практику расширений и спецификаций объявляет "диким западом".
Дикий запад - диапазон кастомных идентификаторов, которые каждый занимает какой хочет: "New entries in the range 32768 to 18446744073709551615 (upper half of "1+2", "1+4", and "1+8") are assigned by First Come First Served". При этом такие теги занимают минимум 3 байта и предполагают публичность, что не подходит для внутренних и временных проектов.
В реальности же этот формат столь же нерасширяем, как msgpack.
Отучаемся фантазировать о реальности, это так не работает.
Он и в CBOR это сможет, и?
Ну вот в реальности-то как раз и не может. Я проверял.

[56.4, 37.3]-вот это вот "шейп" чего имеет, простите?Это список из двух чисел, тут нет никакого шейпа.
Это географические координаты?
Тогда был бы шейп
[ "lat", "lon" ]Или это температура внутри и снаружи нашего устройства?
А тут был бы
[ "temp_ext", "temp_int" ]Или что-то еще третье, которое имеет три поля, но хвостовые можно не передавать для компактности в случае их равенства дефолтным значениям?
В VaryPack такого нет.
Вот в CBOR будет однозначно, если тег 103 - это таки широта и долгота.
Только если декодер это поддерживает. Иначе там будет "какой-то массив с тегом 103". А вот если придёт "какой-то массив с тегом 67927" - поди догадайся, что это идентификатор кластера и записи в нём.
Опять же, что будет, если попадутся другие данные, в которых тоже будет поле unix_time, но это объект другого типа?
Какого ещё типа может быть объект с единственным полем под названием "unix_time"?
самые первые теги, зарегестрированные после RFC, были 25/256 для паковки повторяющих строк (stringref), чуть позже для объектов вообще (28/29), что по совокупности дает то же самое, что здесь было описано как link, то есть совершенно ничего нового
Но в отличие от CBOR, любая реализация VaryPack будет поддерживать ссылки.
Теперь допустим надо поддержать fp128 - и что тогда? Никак? Блоб длиной 16 байт? Отлично, как его отличать будет от, допустим, UUID ?
У одного будет шейп
[ "fp128" ], у другого[ "uuid" ].В CBOR, кстати, тег для UUID зарегистрирован, как и для IPv6-адреса - так что эти блобы одинаковой длины легко отличить.
Прекрасно, а теперь отличите мне
UserEntityотArticleEntity. В центральный реестр же примут все сущности моей предметной области, правда?решительно непонятно - есть примеры "расширений" CBOR (вообще-то они не таковы), которые не совместимы сами с собой с течением времени?
С декодерами, их не поддерживающими. Скриншот с рекордами выше.
На little endian я тоже уже устал ржать
А что весёлого в отсутствии бессмысленного переворачивания байт как при записи, так и при чтении?
что, и bignum'ы длиннее 8 байт тоже будут little-endian?
Да, причём совместимо с int128, int256 и пр.
CBOR можно обрабатывать на IoT-устройствах с 1 Кб RAM, а здесь жестко забитый link этому помешает
Не помешает. Наоборот, сэкономит память.
cbor-packed и альтернативы делают не так
Ну а как же?


artptr86
Не совсем понял, откуда бенчмарк берёт размер библиотеки 3,70 кБ, когда web.js весит 29 кБ, а сжато передаётся порядка 6 кБ. Аналогичная картина и для конкурентов.
nin-jin Автор
https://bundlephobia.com/