深刻学习PHP中的JSON相干函数

在咱们当年刚刚下班的那个年代,还全是 XML 的天下,但当初 JSON 数据格式曾经是各种利用传输的事实标准了。最近几年开始学习编程开发的同学可能都齐全没有接触过应用 XML 来进行数据传输。当然,时代是始终在提高的,JSON 相比 XML 来说,更加地方便快捷,可读性更高。但其实从语义的角度来说,XML 的表现形式更强。

话不多说,在 PHP 中操作 JSON 其实非常简单,大家最罕用的无非也就是 json_encode() 和 json_decode() 这两个函数。它们有一些须要留神的中央,也有一些好玩的中央。明天,咱们就来深刻地再学习一下。

JSON 编码

首先,咱们筹备一个数组,用于咱们前面编码的操作。

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

非常简单地数组,其实也没有什么特地的货色,只是有数据的嵌套,有一些中文和特殊符号而已。对于一般的 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"}]}"

这一堆参数其实是针对的咱们数据中的一些特殊符号,比如说 & 符、<> 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('{"a":1321231231231231231231231231231231231231231231231231231231231231231231233}', true));// array(1) {//     ["a"]=>//     float(1.3212312312312E+72)//   }var_dump(json_decode('{"a":1321231231231231231231231231231231231231231231231231231231231231231231233}', 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)); // NULLvar_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.3var_dump(json_encode($data, JSON_THROW_ON_ERROR));// Fatal error: Uncaught JsonException: Inf and NaN cannot be JSON encodedvar_dump(json_decode('', 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('a' => 1, 'b' => 3, 'c' => 4))) . "\n";print "Int -> " . json_encode(new jsontest(5)) . "\n";print "String -> " . json_encode(new jsontest('Hello, World!')) . "\n";print "Object -> " . json_encode(new jsontest((object) array('a' => 1, 'b' => 3, 'c' => 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 = ['学生'];        }        if (!$edu) {            $this->edu = new stdClass;        }        $this->number = '学号:' . (!$number ? mt_rand() : $number);        if ($this->id == 2) {            return [                $this->id,                $this->name,                $this->cat,                $this->number,                $this->edu,            ];        }        return [            'id' => $this->id,            'name' => $this->name,            'cat' => $this->cat,            'number' => $this->number,            'edu' => $this->edu,        ];    }}var_dump(json_encode(new Student(1, '测试一'), JSON_UNESCAPED_UNICODE));// string(82) "{"id":1,"name":"测试一","cat":["学生"],"number":"学号:14017495","edu":{}}"var_dump(json_encode([new Student(1, '测试一'), new Student(2, '测试二')], 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

各自媒体平台均可搜寻【硬核项目经理】