如何防止 json_encode() 删除包含无效字符的字符串

How to keep json_encode() from dropping strings with invalid characters

提问人:Pekka 提问时间:1/12/2011 最后编辑:Pekka 更新时间:1/18/2023 访问量:48956

问:

有没有办法防止返回包含无效(非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

PHP UTF-8 JSON

评论

0赞 Matt Ball 1/12/2011
字符串是否仅在您时无效?"Düsseldorf"utf8_decode()
0赞 Pekka 1/12/2011
@Matt不,这只是一个示例,用于创建断开的字符串供应答者测试
0赞 Gumbo 1/12/2011
因此,您得到的一些 JSON 数据可能包含无效的 UTF-8 字符串?
1赞 cjimti 1/12/2011
我只是为我的 json 解码器编写了一个包装器,它首先使用 mb_detect_encoding($str) 检查字符串。
1赞 mario 1/12/2011
upgradephp.berlios.de 中有一个 json_encode() 实现 - 它一开始就不太关心字符集。但我想 ZendF 的那个可以很容易地改编。

答:

3赞 metamatt 1/12/2011 #1

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

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

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

评论

1赞 Pekka 1/12/2011
是的,没错,我知道这一点。正如我所说,当突然间,JSON 的某些部分开始消失(而不是看起来损坏)时,调试损坏的传入编码变得非常困难。这更多的是为了更容易地发现错误,而不是规避损坏的编码本身
0赞 metamatt 1/12/2011
用测试每个字符串的编码的东西包装或替换 json_decode(),并在任何字符串无效时抱怨您实际上会看到它 UTF-8?
52赞 goat 1/12/2011 #2

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

下面是一些演示这一点的代码:

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() 不应该得到任何它会窒息和失败的东西。我还没有验证带有标志的 iconv 的输出是否与有效 utf8 字符的概念完全兼容json_encodes所以买家要小心......因为可能存在它仍然失败的边缘情况。呃,我讨厌字符集问题。//IGNORE

在 php 7.2+ 中编辑
,似乎有一些新的标志: 和
目前还没有太多的文档,但就目前而言,此测试应该可以帮助您了解预期行为: https://github.com/php/php-src/blob/master/ext/json/tests/json_encode_invalid_utf8.phpt
json_encodeJSON_INVALID_UTF8_IGNOREJSON_INVALID_UTF8_SUBSTITUTE

而且,在php 7.3+中,有新的标志。查看 http://php.net/manual/en/class.jsonexception.phpJSON_THROW_ON_ERROR

评论

0赞 Pekka 1/12/2011
有趣,听起来很奇怪!我明天会研究这个问题。警告对我来说就足够了
0赞 Pekka 1/12/2011
iconv() 的想法看起来很有趣,可能会起作用。我明天也会尝试。
0赞 Pekka 1/14/2011
这对我有用。在json_encoding数据之前,我现在正在处理数据。iconv()
1赞 Ry- 3/6/2012
@Pekka:我刚刚遇到这个,现在可能不相关了,但是 ing 数组中的所有内容都可以工作,而不是使用 .utf8_encodeiconv
1赞 Ross 5/14/2014
@Pekka - 我认为utf8_encode是为了明确地从 iso88591 更改为 utf8。根据以下 php.net,iICV更普遍适用: php.net/manual/en/function.utf8-encode.php
6赞 moubi 2/25/2011 #3
$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 和文档中的现成模板(html peace)。innerHTML

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

评论

0赞 Ryan 1/4/2019
这对我的情况没有帮助:仍然显示而不是“。json_encode(iconv('UTF-8', 'UTF-8//IGNORE', 'I’m gonna'))\u2019
3赞 CR7 3/7/2013 #4

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

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

header('内容类型:application/json; charset=utf-8');

评论

0赞 Pekka 3/7/2013
我不明白这会有什么帮助 - 似乎所有的工作都不会将 Unicode 字符转换为实体?这并不意味着在遇到无效字符时不会导致空字符串。JSON_UNESCAPED_UNICODE\uxxxx
0赞 Wingman1487 4/27/2015
这对我来说效果很好!我在另一个线程中看到了类似的建议,但错过了我需要添加标题的事实,谢谢!
7赞 Danack 3/8/2013 #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 替换字符),但这实际上并没有提供比仅删除无效字符更好的用户体验。

0赞 Grain 3/13/2019 #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();
    }
}

0赞 Lucas Bustamante 10/5/2022 #7

WordPress 有一个围绕 JSON 的包装器可以防止此问题,您可以查看 wp_json_encode 的源代码,但归结为:

$data = [ utf8_decode("Düsseldorf"), "Washington", "Nairobi" ];

foreach ( $data as &$string ) {
  $encoding = mb_detect_encoding( $string, mb_detect_order(), true );
  if ( $encoding ) {
      return mb_convert_encoding( $string, 'UTF-8', $encoding );
  } else {
      return mb_convert_encoding( $string, 'UTF-8', 'UTF-8' );
  }
}

json_encode( $data );

// Result: ["D?sseldorf","Washington","Nairobi"]

对于递归数组、对象或可以包含非标量的数据,请查看_wp_json_sanity_check以获取更详细的代码。

0赞 Cristiano G Carvalho 1/12/2023 #8

从字符串中删除不可打印的字符

$result = preg_replace('/[[:^print:]]/', “”, $string);

https://alvinalexander.com/php/how-to-remove-non-printable-characters-in-string-regex/ 的安慰