【问题标题】:How to keep json_encode() from dropping strings with invalid characters如何防止 json_encode() 删除包含无效字符的字符串
【发布时间】:2011-06-07 12:24:57
【问题描述】:

有没有办法防止json_encode() 为包含无效(非UTF-8)字符的字符串返回null

在复杂的系统中进行调试可能会让人头疼。实际看到无效字符会更合适,或者至少将其省略。就目前而言,json_encode() 会默默地删除整个字符串。

示例(UTF-8 格式):

$string = 
  array(utf8_decode("Düsseldorf"), // Deliberately produce broken string
        "Washington",
        "Nairobi"); 

print_r(json_encode($string));

结果

[null,"Washington","Nairobi"]

想要的结果:

["D�sseldorf","Washington","Nairobi"]

注意:我希望使损坏的字符串在 json_encode() 中工作。我正在寻找更容易诊断编码错误的方法。 null 字符串对此没有帮助。

【问题讨论】:

  • 字符串"Düsseldorf"是不是只有你utf8_decode()才无效?
  • @Matt 不,这只是为回答者创建一个损坏的字符串以供测试的示例
  • 所以您收到了一些可能包含无效 UTF-8 字符串的 JSON 数据?
  • 我只是为我的 json 解码器编写了一个包装器,它首先使用 mb_detect_encoding($str) 检查字符串。
  • upgradephp.berlios.de 中有一个 json_encode() 实现——它首先并不关心字符集。但我想 ZendF 中的那个可以很容易地适应。

标签: php utf-8 json


【解决方案1】:

php 确实会尝试产生错误,但只有在您关闭 display_errors 时。这很奇怪,因为display_errors 设置仅用于控制是否将错误打印到标准输出,而不是控制是否触发错误。我要强调的是,当你打开display_errors 时,即使你可能会看到各种其他的php 错误,php 并不仅仅隐藏这个错误,它甚至不会触发它。这意味着它不会出现在任何错误日志中,也不会调用任何自定义 error_handlers。错误永远不会发生。

这里有一些代码可以证明这一点:

error_reporting(-1);//report all errors
$invalid_utf8_char = chr(193);

ini_set('display_errors', 1);//display errors to standard output
var_dump(json_encode($invalid_utf8_char));
var_dump(error_get_last());//nothing

ini_set('display_errors', 0);//do not display errors to standard output
var_dump(json_encode($invalid_utf8_char));
var_dump(error_get_last());// json_encode(): Invalid UTF-8 sequence in argument

这种奇怪而不幸的行为与这个错误 https://bugs.php.net/bug.php?id=47494 和其他一些错误有关,而且看起来永远不会被修复。

解决方法:

在将字符串传递给 json_encode 之前清理字符串可能是一个可行的解决方案。

$stripped_of_invalid_utf8_chars_string = iconv('UTF-8', 'UTF-8//IGNORE', $orig_string);
if ($stripped_of_invalid_utf8_chars_string !== $orig_string) {
    // one or more chars were invalid, and so they were stripped out.
    // if you need to know where in the string the first stripped character was, 
    // then see http://stackoverflow.com/questions/7475437/find-first-character-that-is-different-between-two-strings
}
$json = json_encode($stripped_of_invalid_utf8_chars_string);

http://php.net/manual/en/function.iconv.php

说明书上说

//IGNORE 静默丢弃目标中的非法字符 字符集。

所以通过首先删除有问题的字符,理论上 json_encode() 不应该得到任何它会阻塞和失败的东西。我尚未验证带有 //IGNORE 标志的 iconv 的输出是否与有效 utf8 字符的 json_encodes 概念完全兼容,所以买家要小心……因为可能存在仍然失败的边缘情况。呃,我讨厌字符集问题。

编辑
在 php 7.2+ 中,json_encode 似乎有一些新标志: JSON_INVALID_UTF8_IGNOREJSON_INVALID_UTF8_SUBSTITUTE
目前还没有太多的文档,但现在,这个测试应该可以帮助你理解预期的行为: https://github.com/php/php-src/blob/master/ext/json/tests/json_encode_invalid_utf8.phpt

而且,在 php 7.3+ 中有新标志 JSON_THROW_ON_ERROR。见http://php.net/manual/en/class.jsonexception.php

【讨论】:

  • 有趣而且听起来很奇怪!我明天会调查这个。一个警告对我来说就足够了
  • iconv() 的想法看起来很有趣,可能会奏效。我明天也试试。
  • 这对我有用。我现在是 iconv() 在 json_encoding 之前输入数据。
  • @Pekka:我刚遇到这个,现在可能不相关了,但是 utf8_encodeing 数组中的所有内容都可以正常工作,而不是使用 iconv
  • @Pekka - 我认为 utf8_encode 用于从 iso88591 显式更改为 utf8。 iconv 根据 php.net 更普遍适用:php.net/manual/en/function.utf8-encode.php
【解决方案2】:

此函数将从字符串中删除所有无效的 UTF8 字符:

function removeInvalidChars( $text) {
    $regex = '/( [\x00-\x7F] | [\xC0-\xDF][\x80-\xBF] | [\xE0-\xEF][\x80-\xBF]{2} | [\xF0-\xF7][\x80-\xBF]{3} ) | ./x';
    return preg_replace($regex, '$1', $text);
}

我在将 Excel 文档转换为 json 后使用它,因为不能保证 Excel 文档为 UTF8。

我认为没有一种特别明智的方法可以将无效字符转换为可见但有效的字符。您可以通过翻转上面的正则表达式将无效字符替换为 U+FFFD,即 unicode replacement character,但这并不能提供比仅删除无效字符更好的用户体验。

【讨论】:

    【解决方案3】:
    $s = iconv('UTF-8', 'UTF-8//IGNORE', $s);
    

    这解决了问题。 我不知道为什么 php 的人没有通过修复 json_encode() 让生活更轻松。

    无论如何使用上述方法,即使数据包含特殊字符(例如瑞典字母),json_encode() 也可以创建对象。

    然后您可以在 javascript 中使用结果,而无需将数据解码回其原始编码(使用escape()unescape()encodeURIComponent()decodeURIComponent());

    我在 php 中这样使用它(聪明):

    $template = iconv('UTF-8', 'UTF-8//IGNORE', $screen->fetch("my_template.tpl"));
    

    然后我将结果发送到 javascript 和 innerHTML 我文档中的现成模板(html 和平)。

    简单地说,上面的行应该以某种方式在json_encode() 中实现,以便允许它使用任何编码。

    【讨论】:

    • 这对我的情况没有帮助:json_encode(iconv('UTF-8', 'UTF-8//IGNORE', 'I’m gonna')) 仍然显示 \u2019 而不是 '。
    【解决方案4】:

    您需要知道您正在处理的所有字符串的编码,否则您将进入一个痛苦的世界。

    UTF-8 是一种易于使用的编码。此外,JSON 被定义为使用 UTF-8 (http://www.json.org/JSONRequest.html)。那为什么不使用呢?

    简短回答:避免 json_encode() 丢弃字符串的方法是确保它们是有效的 UTF-8。

    【讨论】:

    • 是的,没错,我知道这一点。正如我所说,当突然间,部分 JSON 开始消失(而不是看起来损坏)时,调试损坏的传入编码变得非常困难。这比规避损坏的编码本身更容易发现错误
    • 包装或替换 json_decode() 用测试每个字符串的编码的东西,并在某个地方抱怨当任何字符串不是有效的 UTF-8 时你会实际看到它?
    【解决方案5】:

    您可以直接使用带有 JSON_UNESCAPED_UNICODE 选项 (>= PHP5.4.0) 的 json_encode,而不是使用 iconv 函数

    确保将“charset=utf-8”放在 php 文件的标题中:

    header('Content-Type: application/json; charset=utf-8');

    【讨论】:

    • 我看不出这有什么帮助 - 所有JSON_UNESCAPED_UNICODE 似乎都不会将Unicode 字符转换为\uxxxx 实体?并不是说遇到无效字符就不会产生空字符串。
    • 这对我很有用!我在另一个线程中看到了类似的建议,但我错过了我需要添加标题的事实,谢谢!
    【解决方案6】:

    为了获得关于 json 失败的信息性错误通知,我们使用这个帮助器:

    • 临时安装一个自定义错误处理程序来捕获用于编码/解码的 json 错误
    • 出错时抛出 RuntimeException
    <?php
    
    /**
     * usage:
     * $json = HelperJson::encode(['bla'=>'foo']);
     * $array = HelperJson::decode('{"bla":"foo"}');
     * 
     * throws exception on failure
     * 
     */
    class HelperJson {
    
        /**
         * @var array
         */
        static private $jsonErrors = [
                JSON_ERROR_NONE => '',
                JSON_ERROR_UTF8 => 'Malformed UTF-8 characters, possibly incorrectly encoded',
                JSON_ERROR_DEPTH => 'Maximum stack depth exceeded',
                JSON_ERROR_STATE_MISMATCH => 'Underflow or the modes mismatch',
                JSON_ERROR_CTRL_CHAR => 'Unexpected control character found',
                JSON_ERROR_SYNTAX => 'Syntax error, malformed JSON',
        ];
    
        /**
         * ! assoc ! (reverse logic to php function)
         * @param string $jsonString
         * @param bool $assoc
         * @throws RuntimeException
         * @return array|null
         */
        static public function decode($jsonString, $assoc=true){
    
            HelperJson_ErrorHandler::reset(); // siehe unten
            set_error_handler('HelperJson_ErrorHandler::handleError');
    
            $result = json_decode($jsonString, $assoc);
    
            $errStr = HelperJson_ErrorHandler::getErrstr();
            restore_error_handler();
    
            $jsonError = json_last_error();
            if( $jsonError!=JSON_ERROR_NONE ) {
                $errorMsg = isset(self::$jsonErrors[$jsonError]) ? self::$jsonErrors[$jsonError] : 'unknown error code: '.$jsonError;
                throw new \RuntimeException('json decoding error: '.$errorMsg.' JSON: '.substr($jsonString,0, 250));
            }
            if( $errStr!='' ){
                throw new \RuntimeException('json decoding problem: '.$errStr.' JSON: '.substr($jsonString,0, 250));
            }
            return $result;
        }
    
        /**
         * encode with error "throwing"
         * @param mixed $data
         * @param int $options   $options=JSON_PRESERVE_ZERO_FRACTION+JSON_UNESCAPED_SLASHES : 1024 + 64 = 1088
         * @return string
         * @throws \RuntimeException
         */
        static public function encode($data, $options=1088){
    
            HelperJson_ErrorHandler::reset();// scheint notwendg da sonst bei utf-8 problemen nur eine warnung geflogen ist, die hier aber nicht durchschlug, verdacht der error handler macht selbst was mit json und reset damit json_last_error
            set_error_handler('HelperJson_ErrorHandler::handleError');
    
            $result = json_encode($data, $options);
    
            $errStr = HelperJson_ErrorHandler::getErrstr();
            restore_error_handler();
    
            $jsonError = json_last_error();
            if( $jsonError!=JSON_ERROR_NONE ){
                $errorMsg = isset(self::$jsonErrors[$jsonError]) ? self::$jsonErrors[$jsonError] : 'unknown error code: '.$jsonError;
                throw new \RuntimeException('json encoding error: '.$errorMsg);
            }
            if( $errStr!='' ){
                throw new \RuntimeException('json encoding problem: '.$errStr);
            }
            return $result;
        }
    
    }
    
    /**
    
    HelperJson_ErrorHandler::install();
    preg_match('~a','');
    $errStr = HelperJson_ErrorHandler::getErrstr();
    HelperJson_ErrorHandler::remove();
    
     *
     */
    class HelperJson_ErrorHandler {
    
        static protected  $errno = 0;
        static protected  $errstr = '';
        static protected  $errfile = '';
        static protected  $errline = '';
        static protected  $errcontext = array();
    
        /**
         * @param int $errno
         * @param string $errstr
         * @param string $errfile
         * @param int $errline
         * @param array $errcontext
         * @return bool
         */
        static public function handleError($errno, $errstr, $errfile, $errline, $errcontext){
            self::$errno = $errno;
            self::$errstr = $errstr;
            self::$errfile = $errfile;
            self::$errline = $errline;
            self::$errcontext = $errcontext;
            return true;
        }
    
        /**
         * @return int
         */
        static public function getErrno(){
            return self::$errno;
        }
        /**
         * @return int
         */
        static public function getErrstr(){
            return self::$errstr;
        }
        /**
         * @return int
         */
        static public function getErrfile(){
            return self::$errfile;
        }
        /**
         * @return int
         */
        static public function getErrline(){
            return self::$errline;
        }
        /**
         * @return array
         */
        static public function getErrcontext(){
            return self::$errcontext;
        }
        /**
         * reset last error
         */
        static public function reset(){
            self::$errno = 0;
            self::$errstr = '';
            self::$errfile = '';
            self::$errline = 0;
            self::$errcontext = array();
        }
    
        /**
         * set black-hole error handler
         */
        static public function install(){
            self::reset();
            set_error_handler('HelperJson_ErrorHandler::handleError');
        }
    
        /**
         * restore previous error handler
         */
        static function remove(){
            restore_error_handler();
        }
    }
    
    

    【讨论】:

      猜你喜欢
      • 2017-11-16
      • 1970-01-01
      • 2017-06-06
      • 1970-01-01
      • 2015-06-16
      • 2022-08-19
      • 2014-04-16
      • 1970-01-01
      相关资源
      最近更新 更多