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

StackOverflow https://stackoverflow.com/questions/1443158

  •  22-07-2019
  •  | 
  •  

Вопрос

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

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

Одним из вариантов использования этого является проект v0.8 Спецификация API облачного хранилища CDMI.Вы создаете объекты данных через REST-веб-сервис, используя 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?

Это было полезно?

Решение

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

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

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

См. также: Base91

Другие советы

Я столкнулся с той же проблемой и решил поделиться решением: multipart / form-data.

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

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

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

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

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

BSON (двоичный JSON) может вам подойти.http://en.wikipedia.org/wiki/BSON

Редактировать:К вашему сведению, библиотека .NET json.net поддерживает чтение и запись bson, если вы ищете немного любви к серверной части C#.

Проблема с 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 - лучший.

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

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

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

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

  

" yEnc - это схема кодирования двоичного текста для передачи двоичного файла.   файлы в [текст]. Это снижает накладные расходы по сравнению с предыдущими на основе US-ASCII   методы кодирования с использованием 8-битного расширенного метода кодирования ASCII.   Часто возникают накладные расходы yEnc (если значение каждого байта отображается приблизительно   с такой же частотой в среднем) всего 1 ± 2%, по сравнению с   33% & # 8211; накладные расходы 40% для 6-битных методов кодирования, таких как uuencode и Base64.   ... К 2003 году yEnc стал де-факто стандартной системой кодирования для   двоичные файлы в Usenet. "

Однако yEnc - это 8-битная кодировка, поэтому при ее сохранении в строке JSON возникают те же проблемы, что и при сохранении исходных двоичных данных & # 8212; это означает, что расширение на 100% означает, что это хуже, чем base64.

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

Кодирование, декодирование и сжатие очень быстрое

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

Также это расширение JSON, которое позволяет пропустить кодировку base64 для байтовых массивов

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

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

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

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

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

<Ч>

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

  

API Blob

     

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

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

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

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

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

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

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

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

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

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

См. http: // snia. орг / сайты / по умолчанию / файлы / Многокомпонентные% 20MIME% 20Extension% 20v1.0g.pdf

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

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

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

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

Вы можете получить свои данные следующим образом:

Buffer.from(s, 'utf16le');

Я покопался еще немного (во время реализации база128) и разоблачить это когда мы отправляем символы, коды ASCII больше 128, тогда браузер (chrome) фактически отправляет ДВА символа (байта) вместо одного :(.Причина в том, что JSON по умолчанию использует символы utf8, для которых символы с кодами ASCII выше 127 кодируются двумя байтами, о чем упоминалось чмайк отвечать.Я сделал тест таким образом:введите URL-адрес в Chrome хром://нет-экспорт/ , выберите «Включить необработанные байты», начните захват, отправьте POST-запросы (используя фрагмент внизу), остановите захват и сохраните файл json с необработанными данными запросов.Затем мы заглядываем внутрь этого json-файла:

  • Мы можем найти наш запрос base64, найдя строку 4142434445464748494a4b4c4d4e это шестнадцатеричная кодировка ABCDEFGHIJKLMN и мы это увидим "byte_count": 639 для этого.
  • Мы можем найти наш запрос выше 127, найдя строку C2BCC2BDC380C381C382C383C384C385C386C387C388C389C38AC38B это шестнадцатеричные коды символов utf8 запроса ¼½ÀÁÂÃÄÅÆÇÈÉÊË (однако шестнадцатеричные коды этих символов в формате ascii c1c2c3c4c5c6c7c8c9cacbcccdce).Тем "byte_count": 703 поэтому он на 64 байта длиннее, чем запрос base64, поскольку символы с кодами ASCII выше 127 кодируются в запросе на 2 байта :(

Так что по факту нам не выгодно отправлять символы с кодами >127 :( .Для строк base64 мы не наблюдаем такого негативного поведения (вероятно, и для base85 - я это не проверяю) - однако некоторым решением этой проблемы может быть отправка данных в двоичной части POST multipart/form-data, описанная в Алекс ответ (однако обычно в этом случае нам вообще не нужно использовать какое-либо базовое кодирование...).

Альтернативный подход может основываться на сопоставлении двухбайтовой части данных с одним допустимым символом utf8, закодировав его, используя что-то вроде база65280/база65к но, вероятно, это будет менее эффективно, чем base64, из-за спецификация utf8 ...

function postBase64() {
  let formData = new FormData();
  let req = new XMLHttpRequest();

  formData.append("base64ch", "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/");
  req.open("POST", '/testBase64ch');
  req.send(formData);
}


function postAbove127() {
  let formData = new FormData();
  let req = new XMLHttpRequest();

  formData.append("above127", "¼½ÀÁÂÃÄÅÆÇÈÉÊËÌÍÎÏÐÑÒÓÔÕÖ×ØÙÚÛÜÝÞßàáâãäåæçèéêëìíîïðñòóôõö÷øùúûüý");
  req.open("POST", '/testAbove127');
  req.send(formData);
}
<button onclick=postBase64()>POST base64 chars</button>
<button onclick=postAbove127()>POST chars with codes>127</button>

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

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

Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top