提问人:Pekka 提问时间:1/12/2011 最后编辑:Pekka 更新时间:1/18/2023 访问量:48956
如何防止 json_encode() 删除包含无效字符的字符串
How to keep json_encode() from dropping strings with invalid characters
问:
有没有办法防止返回包含无效(非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
答:
你需要知道你正在处理的所有字符串的编码,否则你正在进入一个痛苦的世界。
UTF-8 是一种易于使用的编码。此外,JSON 被定义为使用 UTF-8 (http://www.json.org/JSONRequest.html)。那么为什么不使用它呢?
简短的回答:避免 json_encode() 删除字符串的方法是确保它们是有效的 UTF-8。
评论
PHP 确实会尝试喷出错误,但前提是您关闭display_errors。这很奇怪,因为该设置仅用于控制是否将错误打印到标准输出,而不是是否触发错误。我想强调的是,当你打开时,即使你可能会看到各种其他的 php 错误,php 不仅会隐藏这个错误,甚至不会触发它。这意味着它不会出现在任何错误日志中,也不会调用任何自定义error_handlers。错误永远不会发生。display_errors
display_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.phptjson_encode
JSON_INVALID_UTF8_IGNORE
JSON_INVALID_UTF8_SUBSTITUTE
而且,在php 7.3+中,有新的标志。查看 http://php.net/manual/en/class.jsonexception.phpJSON_THROW_ON_ERROR
评论
iconv()
utf8_encode
iconv
$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()
评论
json_encode(iconv('UTF-8', 'UTF-8//IGNORE', 'I’m gonna'))
\u2019
您可以直接使用带有 JSON_UNESCAPED_UNICODE 选项的 json_encode,而不是使用 iconv 函数 ( >= PHP5.4.0 )
确保将“charset=utf-8”放在 php 文件的标题中:
header('内容类型:application/json; charset=utf-8');
评论
JSON_UNESCAPED_UNICODE
\uxxxx
此函数将从字符串中删除所有无效的 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 替换字符),但这实际上并没有提供比仅删除无效字符更好的用户体验。
为了获取有关 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();
}
}
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以获取更详细的代码。
从字符串中删除不可打印的字符
$result = preg_replace('/[[:^print:]]/', “”, $string);
https://alvinalexander.com/php/how-to-remove-non-printable-characters-in-string-regex/ 的安慰
评论
"Düsseldorf"
utf8_decode()