1 主题
主要从源码角度分析php的 json_encode && json_decode
php版本: 5.5.23
2 基本概念
首先,json串的 key必须是字符串
, int类型的key会被转换成字符串
如果传入的是浮点数,则会被取整,变成字符串,或者key 为数组之类的,则该key会被过滤
其次,php的json函数只支持utf字符,不支持gbk字符
3 json_encode
php源码的实现其实很简单,就是 遍历传入的参数(一般是array),找出 key && value,拼接成字符串
3.1 Description
函数的原型如下,有两个可选参数
-
string json_encode ( mixed $value [, int $options = 0 [, int $depth = 512 ]] )
value
: 这个就是要进行encode的参数,支持任何类型的变量,一般是数组
depth
: 限制最大的层数
options
: 一些特殊需求,比如可以设置是否对某些特殊字符进行特殊编码
- JSON_HEX_AMP : 在encode的时候把
&
变成\u0026
- JSON_HEX_APOS : 在encode的时候把
'
变成\u0027
- JSON_HEX_QUOT : 在encode的时候把
"
变成\u0022
- JSON_FORCE_OBJECT : 强制输出为object形式,针对非关联数组的时候
比如 arr = array(‘x’) , json_encode(arr) => [“x”], json_encode($arr, JSON_FORCE_OBJECT ) => {“0”:”x”} - JSON_NUMERIC_CHECK : 在encode 的时候,把整数字符串变成整数
- JSON_PRETTY_PRINT : 使输出的json串更可读
- JSON_UNESCAPED_SLASHES : 不对
/
进行转义 - JSON_UNESCAPED_UNICODE : 使多字节更可读,默认是转换成
\uXXXX
- JSON_PRESERVE_ZERO_FRACTION : 确保浮点数被encode成浮点数,这个选项在5.6.6里才被添加
例如: json_encode(12.0) => 12, json_encode(12.0, JSON_PRESERVE_ZERO_FRACTION) => 12.0
3.2 源码
3.2.1 主体
json_encode的入口很简单,接受三个参数,然后调用 php_json_encode 进行处理,结果存储在 buf 里, php自己封装了一套字符串处理的函数
static PHP_FUNCTION(json_encode)
{
zval* parameter;
smart_str buf = {0};
long options = 0;
long depth = JSON_PARSER_DEFAULT_DEPTH;
//step1 取参数
if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "z|ll", ¶meter, &options, &depth) == FAILURE) {
return;
}
JSON_G(error_code) = PHP_JSON_ERROR_NONE;
JSON_G(encode_max_depth) = depth;
//step2 调用处理函数
php_json_encode(&buf, parameter, options TSRMLS_CC);
//step3 设置返回的内容
if (JSON_G(error_code) != PHP_JSON_ERROR_NONE && !(options & PHP_JSON_PARTIAL_OUTPUT_ON_ERROR)) {
ZVAL_FALSE(return_value);
} else {
ZVAL_STRINGL(return_value, buf.c, buf.len, 1);
}
smart_str_free(&buf);
}
3.2.2 主要处理流程
在 php_json_encode
里主要是根据输入参数 val的类型
进行处理,并把结果 append到buf
里,具体有这么几种情况:
对
NULL BOOL LONG
的处理比较简单,直接转换成相应的字符串append到buf里对
DOUBLE
浮点数类型的,会判断是否越界,同时还会截断取前面EG(precision)位
,precision 在php.ini里可以设置,默认是14位
对
STRING
字符串类型的,会调用json_escape_string
进行处理,比如加转义字符,特殊字符是否需要替换之类的-
对
OBJECT
这种对象类型的,会先判断一下该对象是否implements
了JsonSerializable
这个抽象类,并重载了jsonSerialize
函数如果是的话,则调用类自定义的 jsonSerialize 函数进行encode,否则调用 jsonencode_array 进行处理,具体可以参考
中文版 <[http://www.laruence.com/2011/10/10/2204.html](http://www.laruence.com/2011/10/10/2204.html)>
或者英文版 <[http://schlueters.de/blog/archives/135-Jason,-let-me-help-you!.html](http://schlueters.de/blog/archives/135-Jason,-let-me-help-you!.html)>
_ 里关于JsonSerializable接口
的介绍,这个是5.4才加入的新功能 对
ARRAY
这种数组类型的,则会调用json_encode_array
进行处理,在 jsonencode_array 里会遍历数组,先插入 array 的key,然后再插入value(其实是通过调用php_json_encode,相互递归调用--||)
PHP_JSON_API void php_json_encode(smart_str* buf, zval* val, int options TSRMLS_DC)
{
switch (Z_TYPE_P(val))
{
case IS_NULL: //NULL 类型
smart_str_appendl(buf, "null", 4);
break;
case IS_BOOL: //BOOL 类型
if (Z_BVAL_P(val)) {
smart_str_appendl(buf, "true", 4);
} else {
smart_str_appendl(buf, "false", 5);
}
break;
case IS_LONG: //整数
smart_str_append_long(buf, Z_LVAL_P(val));
break;
case IS_DOUBLE: //浮点数
{
char* d = NULL;
int len;
double dbl = Z_DVAL_P(val);
if (!zend_isinf(dbl) && !zend_isnan(dbl)) { //判断是否异常
len = spprintf(&d, 0, "%.*k", (int) EG(precision), dbl);
smart_str_appendl(buf, d, len);
efree(d);
} else {
JSON_G(error_code) = PHP_JSON_ERROR_INF_OR_NAN;
smart_str_appendc(buf, '0');
}
}
break;
case IS_STRING: //字符串类型
json_escape_string(buf, Z_STRVAL_P(val), Z_STRLEN_P(val), options TSRMLS_CC);
break;
case IS_OBJECT: //对象类型,先判断类是否implements了JsonSerializable这个抽象类,并重载了jsonSerialize函数
if (instanceof_function(Z_OBJCE_P(val), php_json_serializable_ce TSRMLS_CC)) {
json_encode_serializable_object(buf, val, options TSRMLS_CC); //使用对象的 jsonSerialize函数 进行encode
break;
}
//没有定义的话,则调用 json_encode_array 函数 encode 这个对象
// fallthrough -- Non-serializable object
case IS_ARRAY: //数组对象
json_encode_array(buf, &val, options TSRMLS_CC);
break;
default: //出错
JSON_G(error_code) = PHP_JSON_ERROR_UNSUPPORTED_TYPE;
smart_str_appendl(buf, "null", 4);
break;
}
return;
}
3.2.3 对对象的处理
之前也说了,对 OBJECT
这种对象类型的,会先判断一下该对象是否 implements
了 JsonSerializable
这个抽象类,并重载了 jsonSerialize
函数
如果是的话,则调用类自定义的 jsonSerialize 函数进行encode,否则调用 json_encode_array 进行处理
具体细节可以参考 中文版 <[http://www.laruence.com/2011/10/10/2204.html](http://www.laruence.com/2011/10/10/2204.html)>
* 或者 英文版 <[http://schlueters.de/blog/archives/135-Jason,-let-me-help-you!.html](http://schlueters.de/blog/archives/135-Jason,-let-me-help-you!.html)>
* 里关于 JsonSerializable接口
的介绍,这个是5.4才加入的新功能
再从源码角度看看是如何调用对象自定义的 jsonSerialize
函数的
static void json_encode_serializable_object(smart_str* buf, zval* val, int options TSRMLS_DC)
{
zend_class_entry* ce = Z_OBJCE_P(val);
zval * retval = NULL, fname;
HashTable* myht;
if (Z_TYPE_P(val) == IS_ARRAY) {
myht = HASH_OF(val);
} else {
myht = Z_OBJPROP_P(val);
}
if (myht && myht->nApplyCount > 1) {
JSON_G(error_code) = PHP_JSON_ERROR_RECURSION;
smart_str_appendl(buf, "null", 4);
return;
}
ZVAL_STRING(&fname, "jsonSerialize", 0);
//调用 jsonSerialize 函数
if (FAILURE == call_user_function_ex(EG(function_table), &val, &fname, &retval, 0, NULL, 1, NULL TSRMLS_CC) || !retval) {
zend_throw_exception_ex(NULL, 0 TSRMLS_CC, "Failed calling %s::jsonSerialize()", ce->name);
smart_str_appendl(buf, "null", sizeof("null") - 1);
return;
}
if (EG(exception)) {//调用中是否抛异常
// Error already raised
zval_ptr_dtor(&retval);
smart_str_appendl(buf, "null", sizeof("null") - 1);
return;
}
if ((Z_TYPE_P(retval) == IS_OBJECT) &&
(Z_OBJ_HANDLE_P(retval) == Z_OBJ_HANDLE_P(val))) { //如果返回的是一个对象,同时返回结果还是要encode的对象,则直接调用 json_encode_array 进行处理
// Handle the case where jsonSerialize does: return $this; by going straight to encode array
json_encode_array(buf, &retval, options TSRMLS_CC);
} else {
// All other types, encode as normal
php_json_encode(buf, retval, options TSRMLS_CC);//否则继续调用 php_json_encode 递归处理
}
zval_ptr_dtor(&retval);
}
3.2.4 对数组的处理
我们再来看一下如何对数组进行encode
之前也说过了对 ARRAY 这种数组类型的,则会调用 json_encode_array 进行处理,在 json_encode_array 里会遍历数组,先插入 array 的key,然后再插入value(其实是通过调用php_json_encode,相互递归调用)
具体实现上有两个步骤
首先判断一下是以对象形式(k1:v1, k2:v2…)输出,还是以数组形式输出(k1,k2…)
如果val的类型非数组,则以对象形式输出
否则如果val的类型是数组,看一下 options 参数是否有设置强制以对象形式输出
如果没有的话,则要看val的内容判断了,通过 json_determine_array_type 函数进行判断,判断val是否是关联性数组
遍历数组,根据以对象形式输出还是数组形式输出进行处理
以数组形式输出,插入分隔符 , ,然后调用 php_json_encode 插入value
以对象形式输出,插入key(如果key非字符串,则强制转换为整数( 如果key非字符串,非整数,那么呵呵 ),再转换为字符串插入),然后调用 php_json_encode 插入value
static void json_encode_array(smart_str* buf, zval** val, int options TSRMLS_DC)
{
//判断是以对象形式(k1:v1, k2:v2...)输出,还是以数组形式输出(k1,k2...)
if (Z_TYPE_PP(val) == IS_ARRAY) {
r = (options & PHP_JSON_FORCE_OBJECT) ? PHP_JSON_OUTPUT_OBJECT : json_determine_array_type(val TSRMLS_CC);
} else {
r = PHP_JSON_OUTPUT_OBJECT;
}
//..........................
//循环处理每个key && value
zend_hash_internal_pointer_reset_ex(myht, &pos);
for (;; zend_hash_move_forward_ex(myht, &pos)) {
i = zend_hash_get_current_key_ex(myht, &key, &key_len, &index, 0, &pos);
if (i == HASH_KEY_NON_EXISTENT)
break;
if (zend_hash_get_current_data_ex(myht, (void ** ) &data, &pos) == SUCCESS) {
if (r == PHP_JSON_OUTPUT_ARRAY) { //以数组形式输出,不需要插入key,直接调用php_json_encode插入value
//..................
php_json_encode(buf, * data, options TSRMLS_CC); //数组形式,没有key(其实是0,1,2这种数字),则直接调用 php_json_encode插入value
} else if (r == PHP_JSON_OUTPUT_OBJECT) { //以对象形式输出,先插入key(对key是否为string做一些处理),然后调用php_json_encode插入value
if (i == HASH_KEY_IS_STRING) { //如果key为字符串类型的,则直接插入
//..................
json_escape_string(buf, key, key_len - 1, options & ~PHP_JSON_NUMERIC_CHECK TSRMLS_CC);//插入key
//..................
php_json_encode(buf, * data, options TSRMLS_CC);//调用php_json_encode插入value
} else {
//..................
smart_str_append_long(buf, (long) index);//把key转换为long,再转换为string类型的,然后插入,但是如果key非字符串,非整数,那么呵呵
//..................
php_json_encode(buf, * data, options TSRMLS_CC);//调用php_json_encode插入value
}
}
}
}
//..................
}
记得以前有一个题目,就是给定一个array,从php代码上怎么判断是关联型的还是非关联型的
http://stackoverflow.com/questions/173400/how-to-check-if-php-array-is-associative-or-sequential
再来看一下php内部是怎么判断一个数组是关联型的还是非关联型的
做法其实也是一样的,遍历每个key,判断是否等于对应的下标
static int json_determine_array_type(zval** val TSRMLS_DC)
{
int i;
HashTable* myht = HASH_OF(* val);
i = myht ? zend_hash_num_elements(myht) : 0;
if (i > 0) {
char* key;
ulong index, idx;
uint key_len;
HashPosition pos;
zend_hash_internal_pointer_reset_ex(myht, &pos);
idx = 0;
for (;; zend_hash_move_forward_ex(myht, &pos)) {
i = zend_hash_get_current_key_ex(myht, &key, &key_len, &index, 0, &pos);
if (i == HASH_KEY_NON_EXISTENT) {
break;
}
if (i == HASH_KEY_IS_STRING) {
return 1;
} else {
if (index != idx) {
return 1;
}
}
idx++;
}
}
return PHP_JSON_OUTPUT_ARRAY;
}
4 json_decode
4.1 Description
函数的原型如下,有两个可选参数
mixed json_decode ( string json [, boolassoc = false [, int depth = 512 [, intoptions = 0 ]]] )
json : 这个就是要进行decode的json字符串
assoc : 当该参数为 true 时,将返回 array 而非 object
depth : 递归处理的层数
options : 一些特殊需求,目前只有一个
JSON_BIGINT_AS_STRING : 当value是大整数的时候,如果设置了这个选项,则> value的类型是字符串,避免精度缺失,默认是浮点数
json = '12345678901234567890'; var_dump(json_decode(json));
var_dump(json_decode($json, false, 512, JSON_BIGINT_AS_STRING));
输出为::
float(1.2345678901235E+19)
string(20) "12345678901234567890"
4.2 源码
4.2.1 主体
json_decode主函数,其实就接受四个参数,然后调用 php_json_decode_ex
进行处理
static PHP_FUNCTION(json_decode) {
char * str;
int str_len;
zend_bool assoc = 0; // return JS objects as PHP objects by default
long depth = JSON_PARSER_DEFAULT_DEPTH;
long options = 0;
if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "s|bll", &str,&str_len, &assoc, &depth, &options) == FAILURE) {
return;
}
JSON_G(error_code) = 0;
if (!str_len) {
RETURN_NULL();
}
// For BC reasons, the bool $assoc overrides the long $options bit for PHP_JSON_OBJECT_AS_ARRAY
if (assoc) {
options ||= PHP_JSON_OBJECT_AS_ARRAY;
} else {
options &= ~PHP_JSON_OBJECT_AS_ARRAY;
}
php_json_decode_ex(return_value, str, str_len, options, depth TSRMLS_CC);
}
让我们来看看在 php_json_decode_ex
里都做了什么事情
其实主要包括三个步骤
- 判断是否为utf8编码,如果是的话,同时转换为utf16
- 先调用
parse_JSON_ex
去解析这个json串,这个是整个解析的核心部分 - 如果失败,则尽量而为去解析这个非标准的json串
PHP_JSON_API void php_json_decode_ex(zval * return_value, char * str, int str_len, int options, long depth TSRMLS_DC)
{
int utf16_len;
zval * z;
unsigned short * utf16;
JSON_parser jp;
//step1 判断是否是utf8字符
utf16 = (unsigned short * ) safe_emalloc((str_len+1), sizeof(unsigned short), 1);
utf16_len = json_utf8_to_utf16(utf16, str, str_len);
//...........
ALLOC_INIT_ZVAL(z);
jp = new_JSON_parser(depth);
//step2 进行处理
if (parse_JSON_ex(jp, z, utf16, utf16_len, options TSRMLS_CC)) {
* return_value = * z;
}
else //step3 对一些不规范的json串,尽力而为去decode,比如输入的字符串是一个整数,两边没有被 " 包含
{
//去掉左右两边多余的 空格 table 换行 回车
//.......
RETVAL_NULL();
//处理NULL or BOOL类型
if (trim_len == 4) {
if (!strncasecmp(trim, "null", trim_len)) {
jp->error_code = PHP_JSON_ERROR_NONE;
RETVAL_NULL();
} else if (!strncasecmp(trim, "true", trim_len)) {
RETVAL_BOOL(1);
}
} else if (trim_len == 5 && !strncasecmp(trim, "false", trim_len)) {
RETVAL_BOOL(0);
}
//处理数字
if ((type = is_numeric_string_ex(trim, trim_len, &p, &d, 0, &overflow_info)) != 0) {
if (type == IS_LONG) {
RETVAL_LONG(p); //返回整数
} else if (type == IS_DOUBLE) { //返回浮点数
//.......
}
}
//返回错误
if (Z_TYPE_P(return_value) != IS_NULL) {
jp->error_code = PHP_JSON_ERROR_NONE;
}
zval_dtor(z);
}
FREE_ZVAL(z);
efree(utf16);
JSON_G(error_code) = jp->error_code;
free_JSON_parser(jp);
}
4.2.2 json解析器
来看看php是如何解析json串的
首先来看看解析的时候,需要用到的这个结构体
typedef struct JSON_parser_struct {
int state; //当前的状态
int depth; //最大的深度
int top; //当前处于第几层
int error_code; //错误码
int* stack; //记录每一层正在解析的状态,取值有MODE_ARRAY, MODE_DONE, MODE_KEY, MODE_OBJECT
zval ** the_zstack; //记录每一层正在decode中的值的stack,如果 depth 小于 JSON_PARSER_DEFAULT_DEPTH,则用 the_static_zstack,否则重新申请内存
zval * the_static_zstack[JSON_PARSER_DEFAULT_DEPTH]; // 默认使用的stack
} * JSON_parser;
主要是通过状态机来处理的
int parse_JSON_ex(JSON_parser jp, zval * z, unsigned short utf16_json[], int length, int options TSRMLS_DC)
{
//...............
for (the_index = 0; the_index < length; the_index += 1) {
next_char = utf16_json[the_index];
//....................
next_state = state_transition_table[jp->state][next_class];
if (next_state >= 0) {
//...............
//主要是获取每个key or value的值,存储在buf变量里
jp->state = next_state;
} else {
//主要是处理json里的特殊标志
// { or [ : 一个层次的开始标识
// } or ] : 一个层次的结束标志
// " :一个字符串取值的开始或者结束标志
// : : 一个value的取值开始标志,当然这个value 可以是任何类型的
// , : 另外一个k/v or k的开始标志
switch (next_state) {
case -9: // empty }
if (!pop(jp, MODE_KEY)) {
FREE_BUFFERS();
return false;
}
jp->state = OK;
break;
case -8: // } 一个层次的结束标志
//...................
jp->state = OK;
break;
case -7: // ]
{
//...................
jp->state = OK;
}
break;
case -6: // {
//...................
break;
case -5: // [
//...................
break;
case -4: // "
//...................
break;
case -3: // ,
//...................
break;
case -2: // :
//...................
default: // syntax error
{
jp->error_code = PHP_JSON_ERROR_SYNTAX;
FREE_BUFFERS();
return false;
}
}
}
}
FREE_BUFFERS();
if (jp->state == OK && pop(jp, MODE_DONE)) {
return true;
}
jp->error_code = PHP_JSON_ERROR_SYNTAX;
return false;
}