web-dev-qa-db-ja.com

json_encode()が無効な文字を含む文字列を削除しないようにする方法

無効な(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"]

:私はnot壊れた文字列をjson_encode()で機能させることを目指しています。エンコーディングエラーの診断を簡単にする方法を探しています。 null文字列はそのために役立ちません。

36
Pekka

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_IGNOREおよびJSON_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

46
goat
_$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()に実装する必要があります。

6
moubi

この関数は、すべての無効な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文書がUTF8であることが保証されていないため、私はExcel文書をjsonに変換した後に使用します。

無効な文字を表示されているが有効な文字に変換する特に賢明な方法はないと思います。無効な文字をユニコード 置換文字 であるU + FFFDで置き換えることもできますが、実際には、無効な文字をドロップするよりも優れたユーザーエクスペリエンスは提供されません。

5
Danack

あなたが扱っているすべての文字列のエンコーディングを知る必要があるか、苦痛の世界に入っています。

UTF-8は使いやすいエンコーディングです。また、JSONはUTF-8を使用するように定義されています(http://www.json.org/JSONRequest.html)。それで、なぜそれを使わないのですか?

短い答え:json_encode()が文字列を落とさないようにする方法は、文字列が有効なUTF-8であることを確認することです。

3
metamatt

Iconv関数を使用する代わりに、json_encodeとJSON_UNESCAPED_UNICODEオプション(> = PHP5.4.0)を直接使用できます。

Phpファイルのヘッダーに必ず「charset = utf-8」を入れてください。

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

2
CR7

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
Grain