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

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

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

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

Ответ 1

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

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

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

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

Ответ 2

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

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

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

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

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

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

Ответ 3

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

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

Ответ 4

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

Ответ 5

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

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

Ответ 6

yEnc может работать для вас:

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

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

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

Ответ 7

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

Он очень быстрый для кодирования, декодирования и сжатия

Сравнение скорости (на основе java, но все же значимое): https://github.com/eishay/jvm-serializers/wiki/

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

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

Ответ 8

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

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

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

Ответ 9

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


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

Blob API

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

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

Ответ 10

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

Ответ 11

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

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

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

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

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

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

Ответ 12

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

Ответ 13

Обратитесь: http://snia.org/sites/default/files/Multi-part%20MIME%20Extension%20v1.0g.pdf

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

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

Ответ 14

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

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

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

Buffer.from(s, 'utf16le');

Ответ 15

Я копаю немного больше (во время реализации base128) и раскрываю, что когда мы отправляем символы, коды которых больше 128, тогда браузер (chrome) фактически отправляет ДВА символа (байта) вместо одного :(. Причина в том, что JSON по умолчанию используйте символы utf8, для которых символы с кодами ascii выше 127 кодируются двумя байтами, что было упомянуто в ответе chmike. Я сделал тест следующим образом: введите chrome url bar chrome://net-export/, выберите "Включить raw bytes ", начать захват, отправлять POST-запросы (используя фрагмент внизу), прекратить захват и сохранить файл json с необработанными данными запросов. Затем мы посмотрим в этот файл json:

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

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

Альтернативный подход может основываться на отображении двухбайтовой части данных в один действительный символ utf8 путем его кодирования с использованием чего-то вроде base65280/base65k, но, вероятно, он будет менее эффективным, чем 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>

Ответ 16

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

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