PHP中file_get_contents函数获取带BOM的utf-8,然后json_decode() 返回null的问题

原文地址:http://levi.cg.am/archives/2563

GDC注:该篇文章很好,立马就解决了我的问题。建议看原文,复制过来,有些格式不太好看。


问题:用php读取文件中的json数据,怎么解析都是返回null。

1
{ "a" :1, "b" :2, "x" :[{ "c" :3},{ "d" :4},{ "e" :5}]}

读取文件,使用了file_get_contents函数。

1
2
3
4
$json = '{"a":1,"b":2,"x":[{"c":3},{"d":4},{"e":5}]}' ;
 
var_dump(json_decode( $json ));
var_dump(json_decode( $json , true));

如果直接在php中读取json字符串,是没有错的,怎么从文件读取就错了呢。

php5.2以后自带json_decode函数,但是对json文本串的格式要求非常严格。

很可能使用该函数得到的返回值是NULL

使用json_last_error()函数获取到的返回值是JSON_ERROR_SYNTAX(Syntax error)。

可以通过以下几个方式排错。

  1. json字符串必须以双引号包含
    1
    $output = str_replace ( "'" , '"', $output );
  2. json字符串必须是utf8编码
    1
    $output = iconv( 'gbk' , 'utf8' , $output );
  3. 不能有多余的逗号 如:[1,2,],用正则替换掉
    1
    preg_replace( '/,\s*([\]}])/m' , '$1' , $output )
  4. 数组对象是需要用print_r ,不能用echo
  5. 之后还有乱码情况 ..我的个案是这样的.
    1
    header( "Content-Type: text/html; charset=UTF-8" );

造成json_decode() 解析null的原因关键结果在是,

  • json文件是UTF-8格式
  • 带有BOM

修正后代码如下,即可正常解析

1
2
3
4
5
6
7
8
9
10
11
12
13
$dmText = file_get_contents ( AROOT . 'data' . DS . 'DMType.json.php' );
if (preg_match( '/^\xEF\xBB\xBF/' , $dmText ))
{
     $dmText = substr ( $dmText ,3);
}
 
//trim
$dmText = t( $dmText );
echo $dmText ;
 
/* create array list from comments */
$dmList = json_decode( $dmText ,true);    //当该参数为 TRUE 时,将返回 array 而非 object 。
var_dump( $dmList );

显示结果:

View Code 

{
    "success": "true",
    "total":"4",
    "items": [
        {"id":"1","c":"asdaEG","tb": "dm_suppliertype", "cn": "供应商类型","tips":"供应商类型"},
        {"id":"2","c":"adsafR","tb": "suppliertype2", "cn": "供应商类型2","tips":"供应商类型"},
        {"id":"3","c":"ada222","tb": "suppliertype3", "cn": "供应商类型3","tips":"供应商类型"},
        {"id":"4","c":"23jetG","tb": "suppliertype4", "cn": "供应商类型4","tips":"供应商类型"}
    ]
}array(3) {
  ["success"]=>
  string(4) "true"
  ["total"]=>
  string(1) "4"
  ["items"]=>
  array(4) {
    [0]=>
    array(5) {
      ["id"]=>
      string(1) "1"
      ["c"]=>
      string(6) "asdaEG"
      ["tb"]=>
      string(15) "dm_suppliertype"
      ["cn"]=>
      string(15) "供应商类型"
      ["tips"]=>
      string(15) "供应商类型"
    }
    [1]=>
    array(5) {
      ["id"]=>
      string(1) "2"
      ["c"]=>
      string(6) "adsafR"
      ["tb"]=>
      string(13) "suppliertype2"
      ["cn"]=>
      string(16) "供应商类型2"
      ["tips"]=>
      string(15) "供应商类型"
    }
    [2]=>
    array(5) {
      ["id"]=>
      string(1) "3"
      ["c"]=>
      string(6) "ada222"
      ["tb"]=>
      string(13) "suppliertype3"
      ["cn"]=>
      string(16) "供应商类型3"
      ["tips"]=>
      string(15) "供应商类型"
    }
    [3]=>
    array(5) {
      ["id"]=>
      string(1) "4"
      ["c"]=>
      string(6) "23jetG"
      ["tb"]=>
      string(13) "suppliertype4"
      ["cn"]=>
      string(16) "供应商类型4"
      ["tips"]=>
      string(15) "供应商类型"
    }
  }
}

附录:

1、PHP5中file_get_contents函数获取带BOM的utf-8文件内容时需注意

PHP5中的file_get_contents函数获取文件内容,实际是按二进制来读取的,所以,当你用file_get_contents去获取一个带BOM的UTF-8文件时,它并不会把UTF-8的BOM去掉,当你把读取的内容当作文本内容来进行一些操作时,可能会发生一些意想不到的结果。

这并不能算作一个BUG,因为file_get_contents函数读取文件的时候,是按二进制来读取的,读取到的内容是包含BOM的,而用户操作的时候,想当然的以为读取到的内容是不包含BOM的文本内容(如用记事本打开后看到的内容),因为BOM在编辑软件中是不可见的,只有在十六进制模式下才可以看见,问题也就出在这,实际上是由于“操作不统一”造成的。

当对UTF-8编码的文件进行操作时,如果要把读取的内容当作文本内容来处理,最好先对BOM进行一些处理,这个问题在PHP6中得到了解决(可以设置文本/二进制读取模式),有兴趣的朋友可以自己查找PHP6的手册。

一个较简单的解决方法:

1
2
3
4
5
6
7
8
<?php
$dataStr = file_get_contents ( 'test.txt' );
if (preg_match( '/^\xEF\xBB\xBF/' , $dataStr ))
{
$dataStr = substr ( $dataStr ,3);
}
//对$dataStr进行操作
?>

什么是BOM?

BOM是Byte Order Mark的缩写,即字节顺序标记,它是插入到UTF-8,UTF-16或UTF-32编码的Unicode文件开头的特殊标记,用来标识Unicode文件的编码类型。

几种编码对应的BOM:

EF BB BF        UTF-8
FE FF             UTF-16 (big-endian)
FF FE             UTF-16 (little-endian)
00 00 FE FF UTF-32 (big-endian)
FF FE 00 00 UTF-32 (little-endian)

对于UTF-8编码的文件而言,BOM标记是可有可无的,Windows自带的记事本文件在保存为UTF-8编码时,会自动加上BOM,现在一些编辑软件,可以在保存为UTF-8编码时可以选择是否带BOM保存。

对于PHP文件,在使用UTF-8编码时,最好都不要BOM保存。因为当你使用include/require/include_once/require_once这些函数去包含一个带BOM的文件时,你得到的网页,在某些兼容性不是很好的浏览器下,你会发现你的网页的实际显示效果跟预期的有细微的差别。

2、检测文件是否有bom头

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
<?php
//此文件用于快速测试UTF8编码的文件是不是加了BOM,并可自动移除
//By Bob Shen
 
$basedir = "." ;    //修改此行为需要检测的目录,点表示当前目录
$auto = 1;    //是否自动移除发现的BOM信息。1为是,0为否。
 
// 以下不用改动
if ( $dh = opendir( $basedir )) {
     while (( $file = readdir( $dh )) !== false) {
         if ( $file != '.' && $file != '..' && ! is_dir ( $basedir . "/" . $file )) echo "filename: $file " .checkBOM( "$basedir/$file" ). " <br>" ;
     }
     closedir ( $dh );
}
 
function checkBOM ( $filename ) {
     global $auto ;
     $contents = file_get_contents ( $filename );
     $charset [1] = substr ( $contents , 0, 1);
     $charset [2] = substr ( $contents , 1, 1);
     $charset [3] = substr ( $contents , 2, 1);
     if (ord( $charset [1]) == 239 && ord( $charset [2]) == 187 && ord( $charset [3]) == 191) {
         if ( $auto == 1) {
             $rest = substr ( $contents , 3);
             rewrite( $filename , $rest );
             return ( "<font color=red>BOM found, automatically removed.</font>" );
         } else {
             return ( "<font color=red>BOM found.</font>" );
         }
     }
     else return ( "BOM Not Found." );
}
 
function rewrite ( $filename , $data ) {
     $filenum = fopen ( $filename , "w" );
     flock ( $filenum , LOCK_EX);
     fwrite( $filenum , $data );
     fclose( $filenum );

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值