Двоичные данные в строке JSON. Что-то лучше, чем Base64

Формат JSON изначально не поддерживает двоичные данные. Двоичные данные должны быть экранированы, чтобы их можно было поместить в строковый элемент (т. Е. Ноль или более символов Unicode в двойных кавычках с использованием обратной косой черты) в JSON.

Очевидный способ избежать двоичных данных - использовать Base64. Тем не менее, Base64 имеет большие накладные расходы на обработку. Также он расширяет 3 байта в 4 символа, что приводит к увеличению размера данных примерно на 33%.

Одним из вариантов использования этого является черновой вариант спецификации API облачного хранилища CDMI версии v0.8. Вы создаете объекты данных через REST-Webservice, используя JSON, например

PUT /MyContainer/BinaryObject HTTP/1.1
Host: cloud.example.com
Accept: application/vnd.org.snia.cdmi.dataobject+json
Content-Type: application/vnd.org.snia.cdmi.dataobject+json
X-CDMI-Specification-Version: 1.0
{
    "mimetype" : "application/octet-stream",
    "metadata" : [ ],
    "value" :   "TWFuIGlzIGRpc3Rpbmd1aXNoZWQsIG5vdCBvbmx5IGJ5IGhpcyByZWFzb24sIGJ1dCBieSB0aGlz
    IHNpbmd1bGFyIHBhc3Npb24gZnJvbSBvdGhlciBhbmltYWxzLCB3aGljaCBpcyBhIGx1c3Qgb2Yg
    dGhlIG1pbmQsIHRoYXQgYnkgYSBwZXJzZXZlcmFuY2Ugb2YgZGVsaWdodCBpbiB0aGUgY29udGlu
    dWVkIGFuZCBpbmRlZmF0aWdhYmxlIGdlbmVyYXRpb24gb2Yga25vd2xlZGdlLCBleGNlZWRzIHRo
    ZSBzaG9ydCB2ZWhlbWVuY2Ugb2YgYW55IGNhcm5hbCBwbGVhc3VyZS4=",
}

Есть ли лучшие способы и стандартные методы для кодирования двоичных данных в строки JSON?

20 ответов

Решение

Существует 94 символа Unicode, которые могут быть представлены одним байтом в соответствии со спецификацией JSON (если ваш JSON передается как UTF-8). Имея это в виду, я думаю, что лучшее, что вы можете сделать в пространстве - это base85, который представляет четыре байта в виде пяти символов. Тем не менее, это всего лишь 7% улучшение по сравнению с base64, его вычисление обходится дороже, а реализации встречаются реже, чем для base64, поэтому, вероятно, это не победа.

Вы также можете просто сопоставить каждый входной байт с соответствующим символом в U+0000-U+00FF, а затем выполнить минимальное кодирование, требуемое стандартом JSON для передачи этих символов; Преимущество здесь в том, что требуемое декодирование равно нулю по сравнению со встроенными функциями, но эффективность пространства плохая - расширение на 105% (если все входные байты одинаково вероятны) против 25% для base85 или 33% для base64.

Окончательный вердикт: base64 выигрывает, на мой взгляд, на том основании, что это обычное, простое и не достаточно плохое решение для замены.

Смотрите также: Base91

Я знаю, что это почти 6-летний вопрос, но я столкнулся с той же проблемой и подумал, что поделюсь решением: multipart / form-data.

Отправляя многокомпонентную форму, вы сначала отправляете в виде строки свои метаданные JSON, а затем отдельно отправляете их в виде необработанного двоичного файла (изображения, wavs и т. Д.), Проиндексированного по имени Content-Disposition.

Вот хороший учебник о том, как сделать это в obj-c, и вот статья в блоге, которая объясняет, как разделить строковые данные с границей формы и отделить их от двоичных данных.

Единственное изменение, которое вам действительно нужно сделать, это на стороне сервера; вам нужно будет захватить ваши метаданные, которые должны соответствующим образом ссылаться на двоичные данные POST (используя границу Content-Disposition).

Конечно, это требует дополнительной работы на стороне сервера, но если вы отправляете много изображений или большие изображения, это того стоит. Объедините это со сжатием gzip, если хотите.

IMHO отправка данных в кодировке base64 - это взлом; RFC multipart/form-data был создан для таких проблем: отправка двоичных данных в сочетании с текстом или метаданными.

Проблема с UTF-8 заключается в том, что это не самая экономичная кодировка. Кроме того, некоторые случайные двоичные байтовые последовательности являются недопустимым кодированием UTF-8. Таким образом, вы не можете просто интерпретировать случайную двоичную последовательность байтов как некоторые данные UTF-8, потому что это будет недопустимой кодировкой UTF-8. Преимущество этого ограничения на кодировку UTF-8 состоит в том, что он делает надежным и возможным обнаружение многобайтовых символов начала и конца любого байта, на который мы начинаем смотреть.

Как следствие, если для кодирования значения байта в диапазоне [0..127] потребуется только один байт в кодировке UTF-8, для кодирования значения байта в диапазоне [128..255] потребуется 2 байта! Хуже этого. В JSON управляющие символы "и \ не могут появляться в строке. Поэтому двоичные данные потребуют некоторого преобразования для правильного кодирования.

Давай посмотрим. Если мы примем равномерно распределенные случайные байтовые значения в наших двоичных данных, то в среднем половина байтов будет закодирована в один байт, а другая половина - в два байта. У двоичных данных в кодировке UTF-8 будет 150% от исходного размера.

Кодировка Base64 увеличивается только до 133% от исходного размера. Таким образом, кодирование Base64 более эффективно.

Как насчет использования другой базовой кодировки? В UTF-8 кодирование 128 значений ASCII является наиболее экономичным. В 8 битах вы можете хранить 7 бит. Поэтому, если мы разрежем двоичные данные на 7-битные порции, чтобы сохранить их в каждом байте строки в кодировке UTF-8, кодированные данные вырастут только до 114% от исходного размера. Лучше, чем Base64. К сожалению, мы не можем использовать этот простой трюк, потому что JSON не допускает некоторые символы ASCII. 33 управляющих символа ASCII ( [0..31] и 127) и символы "и \" должны быть исключены. Это оставляет нам только 128-35 = 93 символа.

Таким образом, теоретически мы можем определить кодировку Base93, которая увеличит кодированный размер до 8/log2(93) = 8*log10(2)/log10(93) = 122%. Но кодировка Base93 будет не такой удобной, как кодировка Base64. Base64 требует разрезать последовательность входных байтов на 6-битные порции, для которых хорошо работает простая побитовая операция. При этом 133% - это не намного больше, чем 122%.

Вот почему я независимо пришел к общему выводу, что Base64 действительно лучший выбор для кодирования двоичных данных в JSON. Мой ответ оправдывает это. Я согласен, что это не очень привлекательно с точки зрения производительности, но учту также преимущество использования JSON с его удобочитаемым представлением строк, которое легко манипулировать во всех языках программирования.

Если производительность критична, то чистая двоичная кодировка должна рассматриваться как замена JSON. Но с JSON я пришел к выводу, что Base64 - лучший.

BSON (Binary JSON) может работать на вас. http://en.wikipedia.org/wiki/BSON

Изменить: FYI.NET библиотека http://json.net/ поддерживает чтение и запись bson, если вы ищете какую-то любовь на стороне C# сервера.

Если вы имеете дело с проблемами пропускной способности, попробуйте сначала сжать данные на стороне клиента, а затем base64-it.

Хороший пример такой магии находится на http://jszip.stuartk.co.uk/ и больше обсуждения этой темы на JavaScript-реализации Gzip

yEnc может работать на вас:

http://en.wikipedia.org/wiki/Yenc

Хотя base64 имеет степень расширения ~33%, это не обязательно так, что накладные расходы обработки значительно больше, чем это: это действительно зависит от используемой вами библиотеки / инструментария JSON. Кодирование и декодирование - это простые прямые операции, и они даже могут быть оптимизированы по кодированию символов (поскольку JSON поддерживает только UTF-8/16/32) - символы base64 всегда являются однобайтовыми для записей строки JSON. Например, на платформе Java есть библиотеки, которые могут выполнять эту работу довольно эффективно, так что накладные расходы в основном связаны с расширенным размером.

Я согласен с двумя предыдущими ответами:

  • base64 - это простой, широко используемый стандарт, поэтому вряд ли найдется что-то более конкретное для использования с JSON (base-85 используется postscript и т. д.; но преимущества в лучшем случае незначительны, когда вы об этом думаете)
  • сжатие перед кодированием (и после декодирования) может иметь много смысла, в зависимости от данных, которые вы используете

Формат улыбки

Это очень быстро кодировать, декодировать и компактировать

Сравнение скорости (на основе Java, но, тем не менее, имеет смысл): https://github.com/eishay/jvm-serializers/wiki/

Кроме того, это расширение JSON, позволяющее пропустить кодирование base64 для байтовых массивов.

Smile-закодированные строки могут быть сжаты, когда пространство критично

Просто добавим еще один вариант, который мы, низкоуровневые программисты-динозавры, используем...

Метод старой школы, который существует уже три года после начала времен, — это формат Intel HEX .Он был создан в 1973 году, а эпоха UNIX началась 1 января 1970 года.

  • Это более эффективно? Нет.
  • Это устоявшийся стандарт? Да.
  • Это удобочитаемо для человека, как JSON? Да-да, и намного читабельнее, чем большинство любых бинарных решений.

JSON будет выглядеть так:

      {
    "data": [
    ":10010000214601360121470136007EFE09D2190140",
    ":100110002146017E17C20001FF5F16002148011928",
    ":10012000194E79234623965778239EDA3F01B2CAA7",
    ":100130003F0156702B5E712B722B732146013421C7",
    ":00000001FF"
    ]
}

Поскольку вы ищете возможность вводить двоичные данные в строго текстовый и очень ограниченный формат, я думаю, что издержки Base64 минимальны по сравнению с удобством, которое вы ожидаете поддерживать с JSON. Если вычислительная мощность и пропускная способность являются проблемой, вам, вероятно, придется пересмотреть форматы файлов.

(Отредактируйте 7 лет спустя: Google Gears пропал. Игнорируйте этот ответ.)

Команда Google Gears столкнулась с проблемой отсутствия типов двоичных данных и попыталась решить ее:

Blob API

В JavaScript есть встроенный тип данных для текстовых строк, но ничего для двоичных данных. Объект Blob пытается устранить это ограничение.

Может быть, вы можете сплести это как-нибудь.

Используйте base128, который на самом деле может генерировать допустимую строку JSON - ЗДЕСЬ ВСЕ - все детали и процедуры JS для кодирования / декодирования (выходной массив base128 на 15% больше входного массива байтов)

Просто чтобы добавить ресурс и сложность с точки зрения обсуждения. Поскольку выполняются операции PUT/POST и PATCH для хранения новых ресурсов и их изменения, следует помнить, что передача контента является точным представлением контента, который хранится и который получен путем выполнения операции GET.

Сообщение, состоящее из нескольких частей, часто используется как спаситель, но по причине простоты и для более сложных задач я предпочитаю выдавать контент целиком. Это самоочевидно и просто.

И да, JSON - это что-то серьезное, но, в конце концов, сам JSON многословен. А накладные расходы на сопоставление с BASE64 - это путь к малым.

Правильно используя составные сообщения, нужно либо разобрать объект для отправки, использовать путь свойства в качестве имени параметра для автоматического объединения, либо создать другой протокол / формат для простого выражения полезной нагрузки.

Также нравится подход BSON, он не так широко и легко поддерживается, как хотелось бы.

По сути, мы просто что-то здесь упускаем, но встраивание двоичных данных, поскольку base64 хорошо зарекомендовало себя, и есть путь, если только вы действительно не определили необходимость выполнять реальную двоичную передачу (что вряд ли так).

Я предлагаю использовать нестандартный JSON для сохранения двоичных данных. Поскольку мы на самом деле не видим JSON, меня не волнует, как он представляет, поэтому все двоичные данные в строке приемлемы, единственный символ, который нужно экранировать, - это просто двойная кавычка() и обратная косая черта(), все остальные символы приемлемы, даже\nне обязательно экранировать, просто сохраните двоичный код0dи0aвсе в порядке, это не нарушит строку JSON, поскольку это не так, поэтому она не завершит строку.

Ваш код может без проблем обрабатывать все эти двоичные данные, просто позаботьтесь о"и\все в порядке.

Еще одна, более новая идея — кодировать данные с помощью uuencode . Это в основном устарело, но все же может быть альтернативой. (Хотя, может быть, и не серьезно.)

В Node.js вы можете преобразовать буфер в строку и обратно без каких-либо изменений:

      const serialized = buffer.toString("binary")
const deserialized = Buffer.from(serialized, "binary")

Если вы хотите повысить надежность за счет уменьшения размера, замените "binary"с "base64"

Если вы используете Node, я думаю, что самый эффективный и простой способ - это конвертировать в UTF16 с помощью:

Buffer.from(data).toString('utf16le');

Вы можете получить свои данные:

Buffer.from(s, 'utf16le');

Тип данных действительно касается. Я проверил различные сценарии отправки полезных данных из ресурса RESTful. Для кодирования я использовал Base64(Apache) и для сжатия GZIP(java.utils.zip.*). Полезная нагрузка содержит информацию о фильме, изображении и аудиофайле. Я сжал и закодировал изображения и аудиофайлы, что резко ухудшило производительность. Кодирование до сжатия получилось хорошо. Изображение и аудиоконтент были отправлены в виде закодированных и сжатых байтов [] .

См. http://snia.org/sites/default/files/Multi-part%20MIME%20Extension%20v1.0g.pdf

Он описывает способ передачи двоичных данных между клиентом и сервером CDMI с помощью операций "тип содержимого CDMI", не требуя преобразования двоичных данных в base64.

Если вы можете использовать операцию "Тип содержимого не-CDMI", то идеально подходит для передачи "данных" в / из объекта. Затем метаданные могут быть впоследствии добавлены / извлечены в / из объекта как последующая операция "тип содержимого CDMI".

Мое решение сейчас, XHR2 использует ArrayBuffer. ArrayBuffer в виде двоичной последовательности содержит многокомпонентный контент, видео, аудио, графику, текст и т. Д. С несколькими типами контента. Все в одном ответе.

В современном браузере имеются DataView, StringView и Blob для разных компонентов. Смотрите также: http://rolfrost.de/video.html для более подробной информации.

Другие вопросы по тегам