准确的JSON文本编码检测

Jan*_*ber 5 json

RFC4627 中描述了一种在 BOM 不存在时识别 Unicode 编码的方法。这依赖于 JSON 文本中的前 2 个字符始终是 ASCII 字符。但在RFC7159中,规范将JSON文本定义为“ws value ws”;意味着单个字符串值也是有效的。因此第一个字符将是起始引号,但随后可以是字符串中允许的任何 Unicode 字符。考虑到 RFC7159 也不鼓励使用 BOM;不再描述从前 4 个八位位组(字节)检测编码的过程,应该如何检测呢?UTF-32 应该仍然可以正常工作,如 RFC4627 中所述,因为第一个字符是四个字节并且应该仍然是 ASCII,但是 UTF-16 呢?第二个(2 字节)字符可能不包含零字节以帮助识别正确的编码。

Cou*_*per 5

更新

这个答案是根据RFC 7159为 JSON 编写的, RFC 7159的字符编码允许使用所有 unicode 方案(UTF-8、UTF-16 或 UTF-32),其中 UTF-8 是“默认”。

当前的RFC 8259将字符编码限制为仅 UTF-8,这是一个合理的限制。


在查看了我几年前所做的一个实现之后,我可以看出,只要满足以下假设,就可以从一个字符明确地检测给定的 Unicode 方案:

  • 输入必须是 Unicode
  • 第一个字符必须是 ASCII
  • 必须没有BOM

考虑一下:

假设第一个字符是"["(0x5B) - ASCII。然后,我们可能会得到这些字节模式:

UTF_32LE:    5B 00 00 00  
UTF_32BE:    00 00 00 5B
UTF_16LE:    5B 00 xx xx
UTF_16BE:    00 5B xx xx
UTF_8:       5B xx xx xx
Run Code Online (Sandbox Code Playgroud)

其中“xx”是任一字节EOF或任何其他字节。

我们还应该注意,根据 RFC7159,最短的有效 JSON 只能是一个字符。也就是说,它可能是 1、2 或 4 字节 - 取决于 Unicode 方案。

所以,如果有帮助的话,这里是一个 C++ 实现:

namespace json {
    
    //
    //  Detect Encoding
    //
    // Tries to determine the Unicode encoding of the input starting at 
    // first. A BOM shall not be present (you might check with function 
    // json::unicode::detect_bom() whether there is a BOM, in which case 
    // you don't need to call this function when a BOM is present).
    //
    // Return values:
    // 
    //   json::unicode::UNICODE_ENCODING_UTF_8
    //   json::unicode::UNICODE_ENCODING_UTF_16LE
    //   json::unicode::UNICODE_ENCODING_UTF_16BE
    //   json::unicode::UNICODE_ENCODING_UTF_32LE
    //   json::unicode::UNICODE_ENCODING_UTF_32BE
    //
    //  -1:     unexpected EOF
    //  -2:     unknown encoding
    //
    // Note:
    // detect_encoding() requires to read ahead a few bytes in order to deter-
    // mine the encoding. In case of InputIterators, this has the consequences
    // that these iterators cannot be reused, for example for a parser.
    // Usually, this requires to reset the istreambuff, that is using the 
    // member functions pubseekpos() or pupseekoff() in order to reset the get 
    // pointer of the stream buffer to its initial position.
    // However, certain istreambuf implementations may not be able to set the    
    // stream pos at arbitrary positions. In this case, this method cannot be
    // used and other edjucated guesses to determine the encoding may be
    // needed.
    
    template <typename Iterator>    
    inline int 
    detect_encoding(Iterator first, Iterator last) 
    {
        // Assuming the input is Unicode!
        // Assuming first character is ASCII!
        
        // The first character must be an ASCII character, say a "[" (0x5B)
        
        // UTF_32LE:    5B 00 00 00
        // UTF_32BE:    00 00 00 5B
        // UTF_16LE:    5B 00 xx xx
        // UTF_16BE:    00 5B xx xx
        // UTF_8:       5B xx xx xx
        
        uint32_t c = 0xFFFFFF00;
        
        while (first != last) {
            uint32_t ascii;
            if (static_cast<uint8_t>(*first) == 0)
                ascii = 0; // zero byte
            else if (static_cast<uint8_t>(*first) < 0x80)
                ascii = 0x01;  // ascii byte
            else if (*first == EOF)
                break;
            else
                ascii = 0x02; // non-ascii byte, that is a lead or trail byte
            c = c << 8 | ascii;
            switch (c) {
                    // reading first byte
                case 0xFFFF0000:  // first byte was 0
                case 0xFFFF0001:  // first byte was ASCII
                    ++first;
                    continue;
                case 0xFFFF0002:
                    return -2;  // this is bogus
                    
                    // reading second byte
                case 0xFF000000:    // 00 00 
                    ++first;
                    continue;
                case 0xFF000001:    // 00 01
                    return json::unicode::UNICODE_ENCODING_UTF_16BE;
                case 0xFF000100:    // 01 00
                    ++first;
                    continue;
                case 0xFF000101:    // 01 01
                    return json::unicode::UNICODE_ENCODING_UTF_8;
                    
                    // reading third byte:    
                case 0x00000000:  // 00 00 00
                case 0x00010000:  // 01 00 00  
                    ++first;
                    continue;                    
                    //case 0x00000001:  // 00 00 01  bogus
                    //case 0x00000100:  // 00 01 00  na
                    //case 0x00000101:  // 00 01 01  na
                case 0x00010001:  // 01 00 01 
                    return json::unicode::UNICODE_ENCODING_UTF_16LE;
                    
                    // reading fourth byte    
                case 0x01000000:
                    return json::unicode::UNICODE_ENCODING_UTF_32LE;
                case 0x00000001:
                    return json::unicode::UNICODE_ENCODING_UTF_32BE;
                    
                default:
                    return -2;  // could not determine encoding, that is,
                                // assuming the first byte is an ASCII.
            } // switch
        }  // while 
            
        // premature EOF
        return -1;
    }
}
Run Code Online (Sandbox Code Playgroud)