如何防止json_encode()丢弃带有无效字符的字符串

时间:2011-01-11 23:12:40

标签: php utf-8 json

对于包含无效(非UTF-8)字符的字符串,是否有办法让json_encode()不返回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字符串对此没有帮助。

6 个答案:

答案 0 :(得分:41)

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

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

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的输出是否与json_encodes的有效utf8字符概念完全兼容,所以买家要注意......因为可能存在仍然失败的边缘情况。呃,我讨厌字符集问题。

修改
在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

答案 1 :(得分:6)

$s = iconv('UTF-8', 'UTF-8//IGNORE', $s);

这解决了这个问题。 我不确定为什么来自php的人不能通过修复json_encode()来改善生活。

无论如何使用上面的内容都允许json_encode()创建对象,即使数据包含特殊字符(例如瑞典字母)。

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

我在php(smarty)中使用它是这样的:

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

然后我将结果发送到javascript,并在我的文档中只发送innerHTML准备好的模板(html peace)。

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

答案 2 :(得分:5)

此函数将从字符串中删除所有无效的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 :(得分:3)

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

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

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

答案 4 :(得分:1)

您可以直接使用带有JSON_UNESCAPED_UNICODE选项的json_encode(> = PHP5.4.0)来代替使​​用iconv函数

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

  

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

答案 5 :(得分:0)

要获取有关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();
    }
}