首頁  >  文章  >  後端開發  >  聊聊PHP中與JSON相關的函數

聊聊PHP中與JSON相關的函數

青灯夜游
青灯夜游轉載
2021-09-07 19:41:533237瀏覽

 PHP中要怎麼操作JSON?這篇文章帶大家深入學習PHP中與JSON相關的函數,介紹一下使用這些函數需要注意的一些地方,希望對大家有幫助!

聊聊PHP中與JSON相關的函數

在我們當年剛上班的那個年代,還全是 XML 的天下,但現在 JSON 資料格式已經是各種應用傳輸的事實標準了。近幾年開始學習程式設計開發的同學可能完全沒有接觸過使用 XML 來進行資料傳輸。當然,時代是一直在進步的,JSON 相比 XML 來說,更方便快捷,可讀性更高。但其實從語意的角度來說,XML 的表現形式更強。

話不多說,在 PHP 中操作 JSON 其實很簡單,大家最常用的無非也就是 json_encode() 和 json_decode() 這兩個函數。它們有一些需要注意的地方,也有一些好玩的地方。今天,我們就來深入地再學習一下。

JSON 編碼

首先,我們準備一個陣列,用於我們後面編碼的運算。

$data = [
    'id' => 1,
    'name' => '测试情况',
    'cat' => [
        '学生 & "在职"',
    ],
    'number' => "123123123",
    'edu' => [
        [
            &#39;name&#39; => &#39;<b>中学</b>&#39;,
            &#39;date&#39; => &#39;2015-2018&#39;,
        ],
        [
            &#39;name&#39; => &#39;<b>大学</b>&#39;,
            &#39;date&#39; => &#39;2018-2022&#39;,
        ],
    ],
];

非常簡單地數組,其實也沒有什麼特別的東西,只是有資料的嵌套,有一些中文和特殊符號而已。對於普通的 JSON 編碼來說,直接使用 json_encode() 就可以了。

$json1 = json_encode($data);
var_dump($json1);
// string(215) "{"id":1,"name":"\u6d4b\u8bd5\u60c5\u51b5","cat":["\u5b66\u751f & \"\u5728\u804c\""],"number":"123123123","edu":[{"name":"<b>\u4e2d\u5b66<\/b>","date":"2015-2018"},{"name":"<b>\u5927\u5b66<\/b>","date":"2018-2022"}]}"

中文處理

上面編碼後的 JSON 資料發現了什麼問題沒?沒錯,相信不少人一眼就會看出,中文字元全被轉換成了 \uxxxx 這種格式。這其實是在預設情況下,json_encode() 函數都會將這些多位元組字元轉換成 Unicode 格式的內容。我們直接在 json_encode() 後面增加一個常數參數就可以解決這個問題,讓中文字元正常地顯示出來。

$json1 = json_encode($data, JSON_UNESCAPED_UNICODE);
var_dump($json1);
// string(179) "{"id":1,"name":"测试情况","cat":["学生 & \"在职\""],"number":"123123123","edu":[{"name":"<b>中学<\/b>","date":"2015-2018"},{"name":"<b>大学<\/b>","date":"2018-2022"}]}"

當然,只是這樣就太沒意思了。因為我曾經在面試的時候就有一位面試官問過我,如果解決這種問題,而且不用這個常數參數。大家可以先不看下面的程式碼,思考一下自己有什麼解決方案嗎?

function t($data)
{
    foreach ($data as $k => $d) {
        if (is_object($d)) {
            $d = (array) $d;
        }
        if (is_array($d)) {
            $data[$k] = t($d);
        } else {
            $data[$k] = urlencode($d);
        }
    }
    return $data;
}
$newData = t($data);

$json1 = json_encode($newData);
var_dump(urldecode($json1));
// string(177) "{"id":"1","name":"测试情况","cat":["学生 & "在职""],"number":"123123123","edu":[{"name":"<b>中学</b>","date":"2015-2018"},{"name":"<b>大学</b>","date":"2018-2022"}]}"

其實就是一個很簡單地解決方案,遞歸地將資料中所有欄位內容轉換成 urlencode() 編碼,然後再使用 json_encode() 編碼,完成之後再使用 urldecode() 反解出來。是不是有點意思?其實這是不少老程式設計師的一個小技巧,因為 JSON_UNESCAPED_UNICODE 這個常數是在 PHP5.4 之後才有的,之前的話如果想讓編碼後的資料直接顯示中文,就只能這樣操作了。

當然,現在已經是 PHP8 時代了,早就已經不需要這麼麻煩地操作了,不過也不能排除有些面試館仗著自己是老碼農故意出些這樣的題目。大家了解下,知道有這麼回事就可以了,畢竟在實際的專案開發中,使用PHP5.4 以下版本的系統可能還真是非常少了(這樣的公司不去也罷,技術更新得太慢了) 。

其它參數

除了JSON_UNESCAPED_UNICODE 之外,我們還有許多的常數參數可以使用,而且這個參數是可以並行操作的,也就是可以多個常數參數共同生效。

$json1 = json_encode($data, JSON_UNESCAPED_UNICODE | JSON_HEX_TAG | JSON_HEX_AMP | JSON_NUMERIC_CHECK | JSON_HEX_QUOT);
var_dump($json1);
// string(230) "{"id":1,"name":"测试情况","cat":["学生 \u0026 \u0022在职\u0022"],"number":123123123,"edu":[{"name":"\u003Cb\u003E中学\u003C\/b\u003E","date":"2015-2018"},{"name":"\u003Cb\u003E大学\u003C\/b\u003E","date":"2018-2022"}]}"

這一堆參數其實是針對的我們資料中的一些特殊符號,比如說 & 符、a8093152e673feb7aba1828c43532094 HTML 標籤等。當然,還有一些常數參數沒有全部展示出來,大家可以自己查閱官方手冊中的說明。

另外,json_encode() 還有第三個參數,代表的是迭代的層級。例如我們上面的這個資料是多維數組,它有三層,所以我們至少要給 3 才能正常解析。下面程式碼我們只是給了一個 1 ,所以回傳的內容就是 false 。也就是無法編碼成功。預設情況下,這個參數的值是 512 。

var_dump(json_encode($data, JSON_UNESCAPED_UNICODE, 1)); // bool(false)

物件及格式處理

預設情況下,json_encode() 會根據資料的型別進行編碼,所以如果是陣列的話,那麼它編碼之後的內容就是JSON 的陣列格式,這時我們也可以加入一個JSON_FORCE_OBJECT ,讓它將一個陣列以物件的形式進行編碼。

$data = [];
var_dump(json_encode($data)); // string(2) "[]"
var_dump(json_encode($data, JSON_FORCE_OBJECT)); // string(2) "{}"

之前在講數學相關函數的時候我們有學習過,如果資料中有NAN 這種資料的話,json_encode() 是無法編碼的,其實我們可以加入一個JSON_PARTIAL_OUTPUT_ON_ERROR ,對一些不可編碼的值進行替換。下面的程式碼中,我們就可以使用它讓 NAN 替換成 0 。

$data = NAN;
var_dump(json_encode($data)); // bool(false)
var_dump(json_encode($data, JSON_PARTIAL_OUTPUT_ON_ERROR)); // 0

物件編碼的屬性問題

對於物件來說,JSON 編碼後的內容就和序列化一樣,只會有物件的屬性而不會有方法。畢竟 JSON 最大的用處就是用於資料傳輸的,方法對於資料傳輸來說沒有什麼實際的作用。而屬性也會根據它的封裝情況有所不同,只會編碼公共的,也就是 public 的屬性。

$data = new class
{
    private $a = 1;
    protected $b = 2;
    public $c = 3;

    public function x(){
        
    }
};
var_dump(json_encode($data)); // string(7) "{"c":3}"

从这段测试代码中可以看出,protected 、 private 属性以及那个方法都不会被编码。

JSON 解码

对于 JSON 解码来说,其实更简单一些,因为 json_decode() 的常量参数没有那么多。

var_dump(json_decode($json1));
// object(stdClass)#1 (5) {
//     ["id"]=>
//     int(1)
//     ["name"]=>
//     string(12) "测试情况"
//     ["cat"]=>
// ……
// ……

var_dump(json_decode($json1, true));
// array(5) {
//     ["id"]=>
//     int(1)
//     ["name"]=>
//     string(12) "测试情况"
//     ["cat"]=>
// ……
// ……

首先还是看下它的第二个参数。这个参数的作用其实从代码中就可以看出来,如果不填这个参数,也就是默认情况下它的值是 false ,那么解码出来的数据是对象格式的。而我们将这具参数设置为 true 的话,那么解码后的结果就会是数组格式的。这个也是大家非常常用的功能,就不多做解释了。

var_dump(json_decode(&#39;{"a":1321231231231231231231231231231231231231231231231231231231231231231231233}&#39;, true));
// array(1) {
//     ["a"]=>
//     float(1.3212312312312E+72)
//   }

var_dump(json_decode(&#39;{"a":1321231231231231231231231231231231231231231231231231231231231231231231233}&#39;, true, 512, JSON_BIGINT_AS_STRING));
// array(1) {
//     ["a"]=>
//     string(73) "1321231231231231231231231231231231231231231231231231231231231231231231233"
//   }

对于这种非常长的数字格式的数据来说,如果直接 json_decode() 解码的话,它会直接转换成 科学计数法 。我们可以直接使用一个 JSON_BIGINT_AS_STRING 常量参数,将这种数据在解码的时候直接转换成字符串,其实也就是保留了数据的原始样貌。注意,这里 json_decode() 函数的参数因为有那个转换对象为数组的参数存在,所以它有四个参数,第三个参数是迭代深度,第四个就是定义这些格式化常量值的。而且它和 json_encode() 是反过来的,迭代深度参数在前,格式常量参数在后面,这里一定要注意哦!

如果数据是错误的,那么 json_decode() 会返回 NULL 。

var_dump(json_decode("", true)); // NULL
var_dump(json_decode("{a:1}", true)); // NULL

错误处理

上面两段代码中我们都演示了如果编码或解码的数据有问题会出现什么情况,比如 json_encode() 会返回 false ,json_decode() 会返回 NULL 。但是具体的原因呢?

$data = NAN;
var_dump(json_encode($data)); // bool(false)
var_dump(json_last_error()); // int(7)
var_dump(json_last_error_msg()); // string(34) "Inf and NaN cannot be JSON encoded"

没错,json_last_error() 和 json_last_error_msg() 就是返回 JSON 操作时的错误信息的。也就是说,json_encode() 和 json_decode() 在正常情况下是不会报错的,我们如果要获得错误信息,就得使用这两个函数来获取。这一点也是不少新手小同学没有注意过的地方,没错误信息,不抛出异常问题对我们的开发调试其实是非常不友好的。因为很可能找了半天都不知道问题出在哪里。

在 PHP7.3 之后,新增加了一个常量参数,可以让我们的 json_encode() 和 json_decode() 在编解码错误的时候抛出异常,这样我们就可以快速地定位问题了,现在如果大家的系统运行环境是 PHP7.3 以上的话,非常推荐使用这个常量参数让系统来抛出异常。

// php7.3
var_dump(json_encode($data, JSON_THROW_ON_ERROR));
// Fatal error: Uncaught JsonException: Inf and NaN cannot be JSON encoded

var_dump(json_decode(&#39;&#39;, true, 512, JSON_THROW_ON_ERROR));
// PHP Fatal error:  Uncaught JsonException: Syntax error

JSON_THROW_ON_ERROR 是对 json_encode() 和 json_decode() 都起效的。同样,只要设定了这个常量参数,我们就可以使用 try...catch 来进行捕获了。

try {
    var_dump(json_encode($data, JSON_THROW_ON_ERROR));
} catch (JsonException $e) {
    var_dump($e->getMessage()); // string(34) "Inf and NaN cannot be JSON encoded"
}

JSON 序列化接口

在之前的文章中,我们学习过 使用Serializable接口来自定义PHP中类的序列化 。也就是说,通过 Serializable 接口我们可以自定义序列化的格式内容。而对于 JSON 来说,同样也提供了一个 JsonSerializable 接口来实现我自定义 JSON 编码时的对象格式内容。

class jsontest implements JsonSerializable
{
    public function __construct($value)
    {$this->value = $value;}
    public function jsonSerialize()
    {return $this->value;}
}

print "Null -> " . json_encode(new jsontest(null)) . "\n";
print "Array -> " . json_encode(new jsontest(array(1, 2, 3))) . "\n";
print "Assoc. -> " . json_encode(new jsontest(array(&#39;a&#39; => 1, &#39;b&#39; => 3, &#39;c&#39; => 4))) . "\n";
print "Int -> " . json_encode(new jsontest(5)) . "\n";
print "String -> " . json_encode(new jsontest(&#39;Hello, World!&#39;)) . "\n";
print "Object -> " . json_encode(new jsontest((object) array(&#39;a&#39; => 1, &#39;b&#39; => 3, &#39;c&#39; => 4))) . "\n";
// Null -> null
// Array -> [1,2,3]
// Assoc. -> {"a":1,"b":3,"c":4}
// Int -> 5
// String -> "Hello, World!"
// Object -> {"a":1,"b":3,"c":4}

这是一个小的示例,只需要实现 JsonSerializable 接口中的 jsonSerialize() 方法并返回内容就可以实现这个 jsontest 对象的 JSON 编码格式的指定。这里我们只是简单地返回了数据的内容,其实和普通的 json_encode() 没什么太大的区别。下面我们通过一个复杂的例子看一下。

class Student implements JsonSerializable
{
    private $id;
    private $name;
    private $cat;
    private $number;
    private $edu;
    public function __construct($id, $name, $cat = null, $number = null, $edu = null)
    {
        $this->id = $id;
        $this->name = $name;
        $this->cat = $cat;
        $this->number = $number;
        $this->edu = $edu;

    }
    public function jsonSerialize()
    {
        if (!$cat) {
            $this->cat = [&#39;学生&#39;];
        }
        if (!$edu) {
            $this->edu = new stdClass;
        }
        $this->number = &#39;学号:&#39; . (!$number ? mt_rand() : $number);
        if ($this->id == 2) {
            return [
                $this->id,
                $this->name,
                $this->cat,
                $this->number,
                $this->edu,
            ];
        }
        return [
            &#39;id&#39; => $this->id,
            &#39;name&#39; => $this->name,
            &#39;cat&#39; => $this->cat,
            &#39;number&#39; => $this->number,
            &#39;edu&#39; => $this->edu,
        ];
    }
}

var_dump(json_encode(new Student(1, &#39;测试一&#39;), JSON_UNESCAPED_UNICODE));
// string(82) "{"id":1,"name":"测试一","cat":["学生"],"number":"学号:14017495","edu":{}}"

var_dump(json_encode([new Student(1, &#39;测试一&#39;), new Student(2, &#39;测试二&#39;)], JSON_UNESCAPED_UNICODE));
// string(137) "[{"id":1,"name":"测试一","cat":["学生"],"number":"学号:1713936069","edu":{}},[2,"测试二",["学生"],"学号:499173036",{}]]"

在这个例子中,我们在 jsonSerialize() 做了一些操作。如果数据没有传值,比如为 null 的情况下就给一个默认值。然后在 id 为 2 的情况下返回一个普通数组。大家可以看到最后一段注释中的第二条数据的格式。

这个接口是不是很有意思,相信大家可能对上面的 json_encode() 和 json_decode() 非常熟悉了,但这个接口估计不少人真的是没接触过,是不是非常有意思。

总结

果然,什么事情都怕深挖。不学不知道,一学吓一跳,平常天天用得这么简单的 JSON 操作的相关函数其实还有很多好用的功能是我们不知道的。当然,最主要的还是看看文档,弄明白并且记住一些非常好用的常量参数,另外,抛出异常的功能也是这篇文章的重点内容,建议版本达到的朋友最好都能使用 JSON_THROW_ON_ERROR 来让错误及时抛出,及时发现哦!

测试代码:

https://github.com/zhangyue0503/dev-blog/blob/master/php/202012/source/11.深入学习PHP中的JSON相关函数.php

参考文档:

https://www.php.net/manual/zh/book.json.php

本文转载自:https://juejin.cn/post/7001652041814638600

作者:硬核项目经理

推薦學習:《PHP影片教學

以上是聊聊PHP中與JSON相關的函數的詳細內容。更多資訊請關注PHP中文網其他相關文章!

陳述:
本文轉載於:juejin.cn。如有侵權,請聯絡admin@php.cn刪除