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

ohfgkhjo  于 2023-01-14  发布在  其他
关注(0)|答案(8)|浏览(205)

有没有办法防止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"]

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

q35jwt9p

q35jwt9p1#

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

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

    • 解决方法:**

在将字符串传递给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
在php7.3+中,有一个新的标记JSON_THROW_ON_ERROR,参见http://php.net/manual/en/class.jsonexception.php

sq1bmfud

sq1bmfud2#

此函数将删除字符串中所有无效的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,但这并没有提供比丢弃无效字符更好的用户体验。

qnakjoqk

qnakjoqk3#

$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()中实现,以便允许它与任何编码一起工作。

nxowjjhe

nxowjjhe4#

你需要知道你正在处理的所有字符串的编码,否则你将进入一个痛苦的世界。
UTF-8是一种易于使用的编码方式。另外,JSON被定义为使用UTF-8(http://www.json.org/JSONRequest.html)。那么为什么不使用它呢?
简短回答:避免json_encode()丢弃字符串的方法是确保它们是有效的UTF-8。

h6my8fg2

h6my8fg25#

除了使用iconv函数,您可以直接使用json_encode和JSON_UNESCAPED_UNICODE选项(〉= PHP5.4.0)
确保你在php文件的头中输入了"charset = utf-8":
header('内容类型:应用程序/json;字符集= utf-8');

mwngjboj

mwngjboj6#

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

flseospp7#

WordPress有一个JSON的 Package 器来防止这个问题,你可以看看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以获取更详细的代码。

unhi4e5o

unhi4e5o8#

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

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

通过https://alvinalexander.com/php/how-to-remove-non-printable-characters-in-string-regex/求解

相关问题