Techioz Blog

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;
}