Rubyのjson内の単一フィールドをGzipします
概要
非常に大きな文字列である単一のプロパティを持つ Ruby ハッシュがあるとします。これは非常に大きいため、文字列を圧縮することが合理的である可能性があります。 ActiveSupport::Gzip.compress を使用して文字列を圧縮するのは簡単ですが、そのハッシュを JSON に変換すると、エンコーディングが原因で問題が発生することが判明しています。
基本的に、このコードは失敗します。
{ 圧縮: ActiveSupport::Gzip.compress(‘asdf’) }.to_json
次のエラーが発生します:
JSON::GeneratorError: 無効な Unicode [8b 08 00 56 dd] 1
圧縮データが含まれていないハッシュは、to_json を使用して json に変換されるときに UTF-8 としてエンコードされますが、ActiveSupport::Gzip.compress(‘asdf’).encode(‘UTF-8’) の呼び出しは次のエラーで失敗します。 :
エンコーディング::UnknownConversionError: “�” を ASCII-8BIT から UTF-8 に変換
これは私が行っている愚かな用事でしょうか?私の目標は達成できるでしょうか?
解決策
gzip 出力を Base-64 エンコードして文字を読み取り可能にし、したがって有効な UTF-8 にします。これにより、圧縮の一部が相殺され、データがさらに約 3 分の 1 拡張されます。より有効な文字を使用してエンコードすることもできます。 Base-85 の場合、影響は少し小さくなりますが、その場合はさらに約 4 分の 1 拡大します。もう少し工夫すれば、1/7 近くまで減らすことができるはずです。
以下は、バイトを 1..127 のシンボルにエンコードする C のコード例です。これらはすべて有効な UTF-8 です。 (JSON では、文字列内の null バイトは許可されません。) 結果の拡張は約 1.145 倍になります。
#include <stddef.h>
// Encode, reading six or seven bits from bin[0..len-1] to encode each symbol
// to *enc, where a symbol is one byte in the range 1..127. enc must have room
// for at least ceil((len * 4) / 3) symbols. The average number of encoded
// symbols for random input is 1.1454 * len. The number of encoded symbols is
// returned.
size_t enc127(char *enc, unsigned char const *bin, size_t len) {
unsigned buf = 0;
int bits = 0;
size_t i = 0, k = 0;
for (;;) {
if (bits < 7) {
if (i == len)
break;
buf = (buf << 8) | bin[i++];
bits += 8;
}
unsigned sym = ((buf >> (bits - 7)) & 0x7f) + 1;
if (sym > 0x7e) {
enc[k++] = 0x7f;
bits -= 6;
}
else {
enc[k++] = sym;
bits -= 7;
}
}
if (bits)
enc[k++] = ((buf << (7 - bits)) & 0x7f) + 1;
return k;
}
// Decode, converting each symbol from enc, which must be in the range 1..127,
// into 6 or 7 bits in the output, from which 8 bits at a time is written to
// bin. bin must have room for at least floor((len * 7) / 8) bytes. The number
// of decoded bytes is returned.
size_t dec127(unsigned char *bin, char const *enc, size_t len) {
unsigned buf = 0;
int bits = 0;
size_t k = 0;
for (size_t i = 0; i < len; i++) {
unsigned sym = enc[i];
if (sym == 0x7f) {
buf = (buf << 6) | 0x3f;
bits += 6;
}
else {
buf = (buf << 7) | (sym - 1);
bits += 7;
}
if (bits >= 8) {
bin[k++] = buf >> (bits - 8);
bits -= 8;
}
}
return k;
}