在我们当年刚刚上班的那个年代,还全是 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)); // 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('', 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

===============

关注公众号:【硬核项目经理】获取最新文章

添加微信/QQ好友:【xiaoyuezigonggong/149844827】免费得PHP、项目管理学习资料

知乎、B站、公众号、抖音、头条、CSDN、博客园、开源中国、segmentfault、简书、微博搜索【硬核项目经理】

B站ID:482780532

深入学习PHP中的JSON相关函数的更多相关文章

  1. 学习PHP中的iconv扩展相关函数

    想必 iconv 这个扩展的相关函数大家多少都接触过,做为 PHP 的默认扩展它已经存在了很久,也是我们在操作字符编码时经常会使用的函数.不过除了 iconv() 这个函数外,你还知道它的其它函数吗? ...

  2. (数据科学学习手札125)在Python中操纵json数据的最佳方式

    本文示例代码及文件已上传至我的Github仓库https://github.com/CNFeffery/DataScienceStudyNotes 1 简介 在日常使用Python的过程中,我们经常会 ...

  3. Json学习总结(1)——Java和JavaScript中使用Json方法大全

    摘要:JSON(JavaScript Object Notation) 是一种轻量级的数据交换格式.它基于ECMAScript的一个子集. JSON采用完全独立于语言的文本格式,但是也使用了类似于C语 ...

  4. Python中的json学习

    p.p1 { margin: 0; font: 14px ".PingFang SC"; color: rgba(53, 53, 53, 1) } p.p2 { margin: 0 ...

  5. Android学习笔记之Fast Json的使用

    PS:最近这两天发现了Fast Json 感觉实在是强大.. 学习内容: 1.什么是Fast Json 2.如何使用Fast Json 3.Fast Json的相关原理 4.Fast Json的优势, ...

  6. Ajax中的JSON格式与php传输过程的浅析

    在Ajax中的JSON格式与php传输过程中有哪些要注意的小地方呢? 先来看一下简单通用的JSON与php传输数据的代码 HTML文件: <input type="button&quo ...

  7. JavaWeb中使用JSON

    前言: 最近也是期末了,有好多好多文档和实验报告要交,所以都没啥时间写文,这段时间清闲了,来补一下之前学习时遗漏的一些知识树,话说就没人吐槽这个JSON图标好丑吗? 什么是JSON JSON 指的是 ...

  8. Expo大作战(五)--expo中app.json 文件的配置信息

    简要:本系列文章讲会对expo进行全面的介绍,本人从2017年6月份接触expo以来,对expo的研究断断续续,一路走来将近10个月,废话不多说,接下来你看到内容,讲全部来与官网 我猜去全部机翻+个人 ...

  9. 如何理解并学习javascript中的面向对象(OOP) [转]

    如果你想让你的javascript代码变得更加优美,性能更加卓越.或者,你想像jQuery的作者一样,写出属于自己优秀的类库(哪怕是基于 jquery的插件).那么,你请务必要学习javascript ...

随机推荐

  1. Send Excerpts from Jenkins Console Output as Email Contents

    Sometimes we need to send some excerpts from Jenkins console output (job logs) as email, such as tes ...

  2. Spring学习02(DI依赖注入)

    5.依赖注入(Dependency Injection,DI) 5.1 概念 依赖 : 指Bean对象的创建依赖于容器 . Bean对象的依赖资源 . 注入 : 指Bean对象所依赖的资源 , 由容器 ...

  3. 把对象交给spring管理的3种方法及经典应用

    背景 先说一说什么叫把对象交给spring管理.它区别于把类交给spring管理.在spring里采用注解方式@Service.@Component这些,实际上管理的是类,把这些类交给spring来负 ...

  4. SpringBoot - 集成Auth0 JWT

    目录 前言 session认证与Token认证 session认证 Token认证 JWT简介 JWT定义 JWT数据结构 JWT的类库 具体实现 JWT配置 JWT工具类 测试接口 前言 说说JWT ...

  5. sqli-labs lesson 23

    less 23: 这里通过验证?id=1'# 发现还是报错 观察代码: 这里涉及一个函数mixed preg_replace(mixed $pattern,mixed $replacement,mix ...

  6. Sqli-Labs less13-16

    less-13 首先,输入用户名和密码,发现只有成功和失败两种显示,没有数据回显: 然后我们抓包拿到数据: 我们通过上述观察,已经知道这是典型的盲注,可以采用布尔盲注或者时间盲注. 构造注入语句:un ...

  7. ReentrantLock可重入锁lock,tryLock的区别

    void lock(); Acquires the lock. Acquires the lock if it is not held by another thread and returns im ...

  8. HBuilder mui 手机app开发 Android手机app开发 ios手机app开发

    经过一段时间的学习,做公司项目,对mui框架有了更加深入完整的了解,其实刚开始接触HBuilder中的mui框架只是简单的了解,并没有深入的研究,后来由于工作的需求,不得不深入研究,并运用的项目中去. ...

  9. 重新整理数据结构与算法(c#)—— 图的深度遍历和广度遍历[十一]

    参考网址:https://www.cnblogs.com/aoximin/p/13162635.html 前言 简介图: 在数据的逻辑结构D=(KR)中,如果K中结点对于关系R的前趋和后继的个数不加限 ...

  10. 根据当前设备的宽度,动态计算出rem的换算比例,实现页面中元素的等比缩放

    ~function anonymous(window){ //根据当前设备的宽度,动态计算出rem的换算比例,实现页面中元素的等比缩放 let computedREM = function compu ...