创意电子

标题: 深入学习PHP中的JSON相干函数 [打印本页]

作者: 硬核项目经理    时间: 2021-9-11 09:02
标题: 深入学习PHP中的JSON相干函数
在我们当年刚刚上班的那个年代,还满是 XML 的天下,但现在 JSON 数据格式已经是各种应用传输的事实尺度了。迩来几年开始学习编程开辟的同学可能都完全没有打仗过使用 XML 来举行数据传输。当然,期间是一直在进步的,JSON 相比 XML 来说,更加地方便快捷,可读性更高。但实在从语义的角度来说,XML 的表现形式更强。

话不多说,在 PHP 中操作 JSON 实在非常简单,大家最常用的无非也就是 json_encode() 和 json_decode() 这两个函数。它们有一些须要留意的地方,也有一些好玩的地方。本日,我们就来深入地再学习一下。

JSON 编码

起首,我们准备一个数组,用于我们背面编码的操作。

$data = [    'id' => 1,    'name' => '测试情况',    'cat' => [        '学生 & "在职"',    ],    'number' => "123123123",    'edu' => [        [            'name' => '中学',            'date' => '2015-2018',        ],        [            'name' => '大学',            '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","date":"2015-2018"},{"name":"<b>\u5927\u5b66","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>中学","date":"2015-2018"},{"name":"<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":"中学","date":"2015-2018"},{"name":"大学","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(&#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)); // 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(&#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://wxcydz.cc/) Powered by Discuz! X3.4