源码分析系列之json_encode()如何转化一个对象

json_encode()如何转化一个对象? 使用 json_encode() 将数组 array 转化成 json 字符串我们都已经很熟悉了

那么使用 json_encode() 转化一个对象是什么样的过程呢?

初步测试

我们需要新建一个具有多种属性的对象

新建 JsonTest

class JsonTest
{
    public const TEST = 'c';
    public $a = 'a';
    public static $b = 'b';
    protected $e = 'e';
    private $d = 'd';

    protected function test1()
    {
        return __FUNCTION__;
    }

    private function test2()
    {
        return __FUNCTION__;
    }

    public function test3()
    {
        return __FUNCTION__;
    }

    public static function test4()
    {
        return __FUNCTION__;
    }
}

执行 打印结果

echo json_encode(new JsonTest());

输出

{ "a": "a" }

可以看得出,只有公开非静态的属性被打印出来了,其他东西(常量、私有变量、方法等等)都丢失了。

思考

在实际的应用中,多数情况下,我们的属性都是非公开的,但是我们又想在执行 json_encode() 的时候将它打印出来,该怎么办呢?

JsonSerializable

JsonSerializable 是一个 PHP 的 JSON 序列化接口

官方定义

JSON 序列化接口

(PHP 5 >= 5.4.0, PHP 7)

简介

实现 JsonSerializable 的类可以 在 json_encode() 时定制他们的 JSON 表示法。

接口摘要

JsonSerializable {
    /* 方法 */
    abstract public jsonSerialize ( void ) : mixed
}

可以看出 php 版本低于 5.4 是没有这个接口的

修改 JsonTest 继续测试

修改 JsonTest 让它实现 JsonSerializable,并为其写一个 jsonSerialize 方法

class JsonTest implements JsonSerializable
{
    public const TEST = 'c';
    public $a = 'a';
    public static $b = 'b';
    protected $e = 'e';
    private $d = 'd';

    protected function test1()
    {
        return __FUNCTION__;
    }

    private function test2()
    {
        return __FUNCTION__;
    }

    public function test3()
    {
        return __FUNCTION__;
    }

    public static function test4()
    {
        return __FUNCTION__;
    }

    public function jsonSerialize()
    {
        $json = array();
        foreach ($this as $key => $value) {
            $json[$key] = $value;
        }
        return $json;
    }
}

执行 打印结果

echo json_encode(new JsonTest());

输出

{ "a": "a", "e": "e", "d": "d" }

可以看得出,公开属性和私有属性都被打印出来了,方法,常量以及静态变量没有打印出来(这是因为类(class)中静态变量和常量的实现方式是所有对象共享的,并不具体属于某个类)

源码分析

这部分源码较多,我会按照源码中的 function 来一个一个进行分析,注意看代码块中的注释

里边对应有一些 option 的位运算,我先贴出来每个 option 常量对应的值, << 是左移

/* json_encode() options */
#define PHP_JSON_HEX_TAG                    (1<<0)
#define PHP_JSON_HEX_AMP                    (1<<1)
#define PHP_JSON_HEX_APOS                   (1<<2)
#define PHP_JSON_HEX_QUOT                   (1<<3)
#define PHP_JSON_FORCE_OBJECT               (1<<4)
#define PHP_JSON_NUMERIC_CHECK              (1<<5)
#define PHP_JSON_UNESCAPED_SLASHES          (1<<6)
#define PHP_JSON_PRETTY_PRINT               (1<<7)
#define PHP_JSON_UNESCAPED_UNICODE          (1<<8)
#define PHP_JSON_PARTIAL_OUTPUT_ON_ERROR    (1<<9)
#define PHP_JSON_PRESERVE_ZERO_FRACTION     (1<<10)
#define PHP_JSON_UNESCAPED_LINE_TERMINATORS (1<<11)

函数本身

PHP_JSON_API int php_json_encode(smart_str *buf, zval *val, int options) /* {{{ */
{
	return php_json_encode_ex(buf, val, options, JSON_G(encode_max_depth));
}
PHP_JSON_API int php_json_encode_ex(smart_str *buf, zval *val, int options, zend_long depth) /* {{{ */
{
	php_json_encoder encoder;
	int return_code;
    // 初始化,开辟内存空间
	php_json_encode_init(&encoder);
	encoder.max_depth = depth;
    // 真正用于编码的函数体
	return_code = php_json_encode_zval(buf, val, options, &encoder);
	JSON_G(error_code) = encoder.error_code;
	return return_code;
}
/* }}} */

可以看出真正的编码函数是 php_json_encode_zval()

php_json_encode_zval()

smart_str_appendl() 是一个拼接字符串的函数,第三个参数是字符串的长度

buf 就是最终要返回的 json 字符串

int php_json_encode_zval(smart_str *buf, zval *val, int options, php_json_encoder *encoder) /* {{{ */
{
again:
	switch (Z_TYPE_P(val))
	{
		case IS_NULL:
			smart_str_appendl(buf, "null", 4);
			break;
		case IS_TRUE:
			smart_str_appendl(buf, "true", 4);
			break;
		case IS_FALSE:
			smart_str_appendl(buf, "false", 5);
			break;
		case IS_LONG:
			smart_str_append_long(buf, Z_LVAL_P(val));
			break;
		case IS_DOUBLE:
			if (php_json_is_valid_double(Z_DVAL_P(val))) {
				php_json_encode_double(buf, Z_DVAL_P(val), options);
			} else {
				encoder->error_code = PHP_JSON_ERROR_INF_OR_NAN;
				smart_str_appendc(buf, '0');
			}
			break;
		case IS_STRING:
			return php_json_escape_string(buf, Z_STRVAL_P(val), Z_STRLEN_P(val), options, encoder);
		case IS_OBJECT:
            // 如果对象实现了JsonSerializable,就将对象中的jsonSerialize()返回的结果进行编码
			if (instanceof_function(Z_OBJCE_P(val), php_json_serializable_ce)) {
				return php_json_encode_serializable_object(buf, val, options, encoder);
			}
            // 如果对象没有实现了JsonSerializable,就执行下边的这个php_json_encode_array()
			/* fallthrough -- Non-serializable object */
		case IS_ARRAY:
            // 解析数组
			return php_json_encode_array(buf, val, options, encoder);
		case IS_REFERENCE:
            //忽略引用
			val = Z_REFVAL_P(val);
			goto again;
		default:
			encoder->error_code = PHP_JSON_ERROR_UNSUPPORTED_TYPE;
			if (options & PHP_JSON_PARTIAL_OUTPUT_ON_ERROR) {
				smart_str_appendl(buf, "null", 4);
			}
			return FAILURE;
	}
	return SUCCESS;
}
/* }}} */

php_json_encode_array()

这个函数递归编码数组及没有实现JsonSerializable()的对象(只编码对象的公开属性)

static int php_json_encode_array(smart_str *buf, zval *val, int options, php_json_encoder *encoder) /* {{{ */
{
	int i, r, need_comma = 0;
	HashTable *myht;
	// r用来表示输出 `json` 的结构类型是数组还是对象
    // 只有自然排序的数组(['a','b','c'])才有可能被输出为数组(考虑option可能为JSON_FORCE_OBJECT)
	if (Z_TYPE_P(val) == IS_ARRAY) {
        // 如果是数组
		myht = Z_ARRVAL_P(val);
     	// options中有JSON_FORCE_OBJECT 就强制输出对象,否则就判断数组是不是自然数组
		r = (options & PHP_JSON_FORCE_OBJECT) ? PHP_JSON_OUTPUT_OBJECT : php_json_determine_array_type(val);
	} else {
		myht = Z_OBJPROP_P(val);
        //对象就是输出对象
		r = PHP_JSON_OUTPUT_OBJECT;
	}
	if (myht && ZEND_HASH_GET_APPLY_COUNT(myht) > 0) {
		encoder->error_code = PHP_JSON_ERROR_RECURSION;
		smart_str_appendl(buf, "null", 4);
		return FAILURE;
	}
	PHP_JSON_HASH_APPLY_PROTECTION_INC(myht);
	if (r == PHP_JSON_OUTPUT_ARRAY) {
        //输出为数组 就用 [ 做开头
		smart_str_appendc(buf, '[');
	} else {
        //输出为对象 就用 { 做开头
		smart_str_appendc(buf, '{');
	}
    // 当前递归的深度
	++encoder->depth;
    // zend_hash_num_elements 返回哈希表中元素的数量
	i = myht ? zend_hash_num_elements(myht) : 0;
	if (i > 0) {
		zend_string *key;
		zval *data;
		zend_ulong index;
        //遍历当前维度的数组 如果当前元素不是数组
		ZEND_HASH_FOREACH_KEY_VAL_IND(myht, index, key, data) {
            // ↓ begin 从这里开始都是判断key怎么处理以及元素末尾怎么处理  ↓↓↓↓
			if (r == PHP_JSON_OUTPUT_ARRAY) {
                //need_comma初始值是0
				if (need_comma) {
					smart_str_appendc(buf, ',');
				} else {
					need_comma = 1;
				}
				//这两个方法是option中有JSON_PRETTY_PRINT的时候才会执行的
				php_json_pretty_print_char(buf, options, '\n');
				php_json_pretty_print_indent(buf, options, encoder);
			} else if (r == PHP_JSON_OUTPUT_OBJECT) {
				if (key) {
					if (ZSTR_VAL(key)[0] == '\0' && ZSTR_LEN(key) > 0 && Z_TYPE_P(val) == IS_OBJECT) {
                        //跳过受保护的属性和私有属性
						/* Skip protected and private members. */
						continue;
					}
 					//need_comma初始值是0
					if (need_comma) {
						smart_str_appendc(buf, ',');
					} else {
						need_comma = 1;
					}
					php_json_pretty_print_char(buf, options, '\n');
					php_json_pretty_print_indent(buf, options, encoder);
                    // 处理字符串属性的key(例如判断key中的中文或者特殊字符的处理)
					if (php_json_escape_string(buf, ZSTR_VAL(key), ZSTR_LEN(key),
								options & ~PHP_JSON_NUMERIC_CHECK, encoder) == FAILURE &&
							(options & PHP_JSON_PARTIAL_OUTPUT_ON_ERROR) &&
							buf->s) {
						ZSTR_LEN(buf->s) -= 4;
						smart_str_appendl(buf, "\"\"", 2);
					}
				} else {
					if (need_comma) {
						smart_str_appendc(buf, ',');
					} else {
						need_comma = 1;
					}
					php_json_pretty_print_char(buf, options, '\n');
					php_json_pretty_print_indent(buf, options, encoder);
					smart_str_appendc(buf, '"');
					smart_str_append_long(buf, (zend_long) index);
					smart_str_appendc(buf, '"');
				}
				smart_str_appendc(buf, ':');
				php_json_pretty_print_char(buf, options, ' ');
			}
			// ↑ end 从这里之前都是判断key怎么处理以及元素末尾怎么处理  ↑↑↑↑
            //继续调用对普通元素编码的 php_json_encode_zval() (实现数组和对象的递归闭环)
			if (php_json_encode_zval(buf, data, options, encoder) == FAILURE &&
					!(options & PHP_JSON_PARTIAL_OUTPUT_ON_ERROR)) {
				PHP_JSON_HASH_APPLY_PROTECTION_DEC(myht);
				return FAILURE;
			}
		} ZEND_HASH_FOREACH_END();
	}
	PHP_JSON_HASH_APPLY_PROTECTION_DEC(myht);
    // 当前深度是否到达了设定的最大深度(默认512)
	if (encoder->depth > encoder->max_depth) {
		encoder->error_code = PHP_JSON_ERROR_DEPTH;
		if (!(options & PHP_JSON_PARTIAL_OUTPUT_ON_ERROR)) {
			return FAILURE;
		}
	}
	--encoder->depth;
	/* Only keep closing bracket on same line for empty arrays/objects */
	if (need_comma) {
		php_json_pretty_print_char(buf, options, '\n');
		php_json_pretty_print_indent(buf, options, encoder);
	}
	if (r == PHP_JSON_OUTPUT_ARRAY) {
		smart_str_appendc(buf, ']');
	} else {
		smart_str_appendc(buf, '}');
	}
	return SUCCESS;
}
/* }}} */

分析

看了源码,我得出了一些结论。

  • 只有 null,布尔值,浮点数,整数,字符串才会被直接编码
  • 对象要么实现 JsonSerializable 并定义一个 jsonSerialize() ,要么就被当成一个数组,只会被处理公开非静态属性
  • json_encode() 并不会直接编码数组和对象,而是会递归遍历出所有可遍历的元素,并处理 key
  • 源码中 php_json_encode_zval() 和 php_json_encode_array() 的相互调用,实现了数组和对象遍历的闭环
  • 引用不会被编码

另外,关于 json_encode() 的 options ,我觉得这里处理的技巧非常有趣,巧妙利用位运算来区别多个常量,有兴趣的慢慢看看研究研究。(提示,将 options 每个常量转成二进制来看,json_encode() 接受多个 option 是按位或 | )

Demo

>>> $a = [1,2,3,4];
=> [
     1,
     2,
     3,
     4,
   ]
>>> json_encode($a);
=> "[1,2,3,4]"
>>> json_encode((object)$a);
=> "{"0":1,"1":2,"2":3,"3":4}"
>>> json_encode($a,JSON_FORCE_OBJECT);
=> "{"0":1,"1":2,"2":3,"3":4}"
>>> json_encode($a,JSON_FORCE_OBJECT|JSON_PRETTY_PRINT);
=> """
   {\n
       "0": 1,\n
       "1": 2,\n
       "2": 3,\n
       "3": 4\n
   }
   """
$arr = array(
    '0'=>'a','1'=>'b','2'=>'c','3'=>'d'
);
echo json_encode($arr);

但是结果是

["a","b","c","d"]

需求是要返回JSON对象,是这样似的

{"0":"a","1":"b","2":"c","3":"d"}

You can do it,you nee add

$arr = array(
    '0'=>'a','1'=>'b','2'=>'c','3'=>'d'
);
echo json_encode((object)$arr);

输出结果

{"0":"a","1":"b","2":"c","3":"d"}

以上就是源码分析系列之json_encode()如何转化一个对象的详细内容,更多关于源码json_encode()的资料请关注我们其它相关文章!

(0)

相关推荐

  • PHP json_encode中文乱码解决方法

    相信很多人在使用Ajax与后台php页面进行交互的时候都碰到过中文乱码的问题.JSON作为一种轻量级的数据交换格式,备受亲睐,但是用PHP作为后台交互,容易出现中文乱码的问题.JSON和js一样,对于客户端的字符都是以UTF8的形式进行处理的,也就是说,使用JSON作为提交和接收的数据格式时字符都采用UTF8编码处理,当我们的页面编码和数据库编码不是采用UTF8的时候,就极容易出现中文乱码的问题.解决办法自然是在用js或者PHP处理JSON数据的时候都采用UTF8的形式. PHP5.2或以上的版

  • 关于php unset对json_encode的影响详解

    前言 PHP 中有个释放变量的语句叫做unset(从PHP4开始unset已经不再是一个函数了,而是一个语句),本文主要给大家介绍了关于php unset对json_encode影响的相关内容,下面话不多说了,来一起看看详细的介绍吧 先运行一段php代码: $a = Array(0=>'hello world', 1=>'girl', 2=>'boy'); var_dump(json_encode($a)); unset($a[1]); var_dump(json_encode($a))

  • php json_encode与json_decode详解及实例

    一.json_encode() 该函数主要用来将数组和对象,转换为json格式.先看一个数组转换的例子: $arr = array ('a'=>1,'b'=>2,'c'=>3,'d'=>4,'e'=>5); echo json_encode($arr); 结果为 {"a":1,"b":2,"c":3,"d":4,"e":5} 再看一个对象转换的例子: $obj->body

  • 基于php解决json_encode中文UNICODE转码问题

    用PHP的json_encode来处理中文的时候, 中文都会被编码, 变成不可读的, 类似"\u***"的格式,如果想汉字不进行转码,这里提供三种方法 1.升级PHP,在PHP5.4, 这个问题终于得以解决, Json新增了一个选项: JSON_UNESCAPED_UNICODE, 故名思议, 就是说, Json不要编码Unicode. <?php echo json_encode("中文", JSON_UNESCAPED_UNICODE); //"

  • php中json_encode不兼容JSON_UNESCAPED_UNICODE的解决方案

    PHP5.4才支持JSON_UNESCAPED_UNICODE这个参数,此参数是让中文字符在json_encode的时候不用转义,减少数据传输量.但在PHP5.3中,就得自己写个函数来实现,以下就是解决方法: /** * 对变量进行 JSON 编码 * @param mixed value 待编码的 value ,除了resource 类型之外,可以为任何数据类型,该函数只能接受 UTF-8 编码的数据 * @return string 返回 value 值的 JSON 形式 */ functi

  • php让json_encode不自动转义斜杠“/”的方法

    hp中怎么让json_encode不自动转义斜杠"/"?下面本篇文章给大家介绍一下PHP中让json_encode不自动转义斜杠"/"的方法. 最近将使用爬虫爬取的链接保存到 mysql 数据库中时,发现我将链接使用 json_encode 保存时候,在数据库中却显示了转义字符,我并不需要这转义的,看起来不清晰而且占用存储空间. 后来发现在默认的情况之下使用 json_encode 对数组进行 json 格式的转换时候会自动的将数据中含有斜杠的字符串进行转义,但是我

  • Thinkphp 框架基础之源码获取、环境要求与目录结构分析

    本文实例讲述了Thinkphp 框架基础之源码获取.环境要求与目录结构.分享给大家供大家参考,具体如下: 获取ThinkPHP 获取ThinkPHP的方式很多,官方网站(http://thinkphp.cn)是最好的下载和文档获取来源. 官网提供了稳定版本的下载:http://thinkphp.cn/down/framework.html 如果你希望保持最新的更新,可以通过github获取当前最新的版本(完整版). Git获取地址列表(你可以选择一个最快的地址): Github: https:/

  • PHP中让json_encode不自动转义斜杠“/”的方法

    前言 最近将使用爬虫爬取的链接保存到 mysql 数据库中时,发现我将链接使用 json_encode 保存时候,在数据库中却显示了转义字符,我并不需要这转义的,看起来不清晰而且占用存储空间. 后来发现在默认的情况之下使用 json_encode 对数组进行 json 格式的转换时候会自动的将数据中含有斜杠的字符串进行转义,但是我们往往有的时候不需要药对它们进行转义的,本文说说如何使用 json_encode 不自动转义斜杠. 对于如下数组 $a,现有两种办法解决: $a = array( 'h

  • java解析php函数json_encode unicode 编码问题

    android开发中在和服务器端接口对接时出现编码问题,从服务器端获取到的数据是 "\u8bbe\u59071ID-\u8bbe\u59071\u540d\u79f0;\u8bbe\u59073id-\u8bbe\u59073\u540d\u79f0;\u8bbe\u59077id-\u8bbe\u59077\u540d\u79f0" 接口是通过php函数中json_encode进行编码后返回的,在客户端通过java.net.URLdecoder.decode()解码不管用,但是直接将

  • PHP自动生成缩略图函数的源码示例

    一个简单但功能比较完善的自动生成缩略图的函数,可以按需要对图片进行缩放.裁切.锁定宽或高.使用空白填充 以下为源码,比较简单,相信很容易看明白,记得打开 GD 库的支持哦: <?php /** * 生成缩略图 * @param string 源图绝对完整地址{带文件名及后缀名} * @param string 目标图绝对完整地址{带文件名及后缀名} * @param int 缩略图宽{值设为0时目标高度不能为0,目标宽度为源图宽*(目标高度/源图高)} * @param int 缩略图高{值设为

  • 浅析PHP中json_encode与json_decode的区别

    一.json_encode() 对变量进行JSON编码 语法:json_encode($value[,$options=0]) 注意:  1.$value为要编码的值,且该函数只对UTF8编码的数据有效:              2.options:由以下常量组成的二进制掩码:JSON_HEX_QUOT, JSON_HEX_TAG, JSON_HEX_AMP, JSON_HEX_APOS,JSON_NUMERIC_CHECK,JSON_PRETTY_PRINT, JSON_UNESCAPED_

  • php源码的安装方法和实例

    在官网下载源码包:https://www.php.net/downloads.php 步骤: 1.解压 命令:tar -xjvf php.tar.bz2 2.configure configure工具是一个shell脚本,在配置编译前需要gcc.autoconfig工具. 可以通过./configure --help 查看配置参数 进入解压后的php目录,编译源码: ./configure --prefix=/home/php (--prefix指定安装php路径) 3.make 执行编译构建命

  • php源码的使用方法讲解

    PHP程序都要用MYSQL,如果没有MYSQL,就不能用它们. 第一:配置数据库信息,改成自己所需的: 第二:导入数据库: 第三:安装wamp5 输入 http://127.0.0.1/自己的文件名. 1:如果是php源码,在本地电脑使用时.要先安装phpnow环境套件包(下附),里面包含了php+mysql等,也是用迅雷搜索下载.那个套件安装很傻瓜化,不用我说了. 2:把所有的源码复制到套件安装目录下的htdocs文件夹里,使用方法如上面的3. 3:如果打不开,请先确定你的数据库是需要导入的吗

  • PHP后台备份MySQL数据库的源码实例

    PHP 备份 mysql 数据库的源代码,在完善的 PHP+Mysql 项目中,在后台都会有备份 Mysql 数据库的功能,有了这个功能,对于一些不便自己写shell脚本备份的VPS来说,就不用使用 FTP 或者使用 mysql 的管理工具进行 mysql 数据库备份下载,非常方便. 下面是一个php数据库备份的源代码,大家也可以根据自己的需求进行修改. <?php // 备份数据库 $host = "localhost"; $user = "root"; /

随机推荐