
1 主題
主要從源碼角度分析php的 json_encode && json_decode
php版本: 5.5.23
2 基本概念
首先,json串的 key必須是字符串 , int類型的key會(huì)被轉(zhuǎn)換成字符串
如果傳入的是浮點(diǎn)數(shù),則會(huì)被取整,變成字符串,或者key 為數(shù)組之類的,則該key會(huì)被過濾
其次,php的json函數(shù)只支持utf字符,不支持gbk字符
3 json_encode
php源碼的實(shí)現(xiàn)其實(shí)很簡(jiǎn)單,就是 遍歷傳入的參數(shù)(一般是array),找出 key && value,拼接成字符串
3.1 Description
函數(shù)的原型如下,有兩個(gè)可選參數(shù)
-
string json_encode ( mixed $value [, int $options = 0 [, int $depth = 512 ]] )
value: 這個(gè)就是要進(jìn)行encode的參數(shù),支持任何類型的變量,一般是數(shù)組
depth: 限制最大的層數(shù)
options: 一些特殊需求,比如可以設(shè)置是否對(duì)某些特殊字符進(jìn)行特殊編碼
- JSON_HEX_AMP : 在encode的時(shí)候把
&變成\u0026 - JSON_HEX_APOS : 在encode的時(shí)候把
'變成\u0027 - JSON_HEX_QUOT : 在encode的時(shí)候把
"變成\u0022 - JSON_FORCE_OBJECT : 強(qiáng)制輸出為object形式,針對(duì)非關(guān)聯(lián)數(shù)組的時(shí)候
比如 arr = array(‘x’) , json_encode(arr) => [“x”], json_encode($arr, JSON_FORCE_OBJECT ) => {“0”:”x”} - JSON_NUMERIC_CHECK : 在encode 的時(shí)候,把整數(shù)字符串變成整數(shù)
- JSON_PRETTY_PRINT : 使輸出的json串更可讀
- JSON_UNESCAPED_SLASHES : 不對(duì)
/進(jìn)行轉(zhuǎn)義 - JSON_UNESCAPED_UNICODE : 使多字節(jié)更可讀,默認(rèn)是轉(zhuǎn)換成
\uXXXX - JSON_PRESERVE_ZERO_FRACTION : 確保浮點(diǎn)數(shù)被encode成浮點(diǎn)數(shù),這個(gè)選項(xiàng)在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的入口很簡(jiǎn)單,接受三個(gè)參數(shù),然后調(diào)用 php_json_encode 進(jìn)行處理,結(jié)果存儲(chǔ)在 buf 里, php自己封裝了一套字符串處理的函數(shù)
static PHP_FUNCTION(json_encode)
{
zval* parameter;
smart_str buf = {0};
long options = 0;
long depth = JSON_PARSER_DEFAULT_DEPTH;
//step1 取參數(shù)
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 調(diào)用處理函數(shù)
php_json_encode(&buf, parameter, options TSRMLS_CC);
//step3 設(shè)置返回的內(nèi)容
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 里主要是根據(jù)輸入?yún)?shù) val的類型 進(jìn)行處理,并把結(jié)果 append到buf 里,具體有這么幾種情況:
對(duì)
NULL BOOL LONG的處理比較簡(jiǎn)單,直接轉(zhuǎn)換成相應(yīng)的字符串a(chǎn)ppend到buf里對(duì)
DOUBLE浮點(diǎn)數(shù)類型的,會(huì)判斷是否越界,同時(shí)還會(huì)截?cái)嗳∏懊鍱G(precision)位,precision 在php.ini里可以設(shè)置,默認(rèn)是14位對(duì)
STRING字符串類型的,會(huì)調(diào)用json_escape_string進(jìn)行處理,比如加轉(zhuǎn)義字符,特殊字符是否需要替換之類的-
對(duì)
OBJECT這種對(duì)象類型的,會(huì)先判斷一下該對(duì)象是否implements了JsonSerializable這個(gè)抽象類,并重載了jsonSerialize函數(shù)如果是的話,則調(diào)用類自定義的 jsonSerialize 函數(shù)進(jìn)行encode,否則調(diào)用 jsonencode_array 進(jìn)行處理,具體可以參考
中文版 <[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)>_ 里關(guān)于JsonSerializable接口的介紹,這個(gè)是5.4才加入的新功能 對(duì)
ARRAY這種數(shù)組類型的,則會(huì)調(diào)用json_encode_array進(jìn)行處理,在 jsonencode_array 里會(huì)遍歷數(shù)組,先插入 array 的key,然后再插入value(其實(shí)是通過調(diào)用php_json_encode,相互遞歸調(diào)用--||)
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: //整數(shù)
smart_str_append_long(buf, Z_LVAL_P(val));
break;
case IS_DOUBLE: //浮點(diǎn)數(shù)
{
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: //對(duì)象類型,先判斷類是否implements了JsonSerializable這個(gè)抽象類,并重載了jsonSerialize函數(shù)
if (instanceof_function(Z_OBJCE_P(val), php_json_serializable_ce TSRMLS_CC)) {
json_encode_serializable_object(buf, val, options TSRMLS_CC); //使用對(duì)象的 jsonSerialize函數(shù) 進(jìn)行encode
break;
}
//沒有定義的話,則調(diào)用 json_encode_array 函數(shù) encode 這個(gè)對(duì)象
// fallthrough -- Non-serializable object
case IS_ARRAY: //數(shù)組對(duì)象
json_encode_array(buf, &val, options TSRMLS_CC);
break;
default: //出錯(cuò)
JSON_G(error_code) = PHP_JSON_ERROR_UNSUPPORTED_TYPE;
smart_str_appendl(buf, "null", 4);
break;
}
return;
}
3.2.3 對(duì)對(duì)象的處理
之前也說了,對(duì) OBJECT 這種對(duì)象類型的,會(huì)先判斷一下該對(duì)象是否 implements 了 JsonSerializable 這個(gè)抽象類,并重載了 jsonSerialize 函數(shù)
如果是的話,則調(diào)用類自定義的 jsonSerialize 函數(shù)進(jìn)行encode,否則調(diào)用 json_encode_array 進(jìn)行處理
具體細(xì)節(jié)可以參考 中文版 <[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)>* 里關(guān)于 JsonSerializable接口 的介紹,這個(gè)是5.4才加入的新功能
再?gòu)脑创a角度看看是如何調(diào)用對(duì)象自定義的 jsonSerialize 函數(shù)的
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);
//調(diào)用 jsonSerialize 函數(shù)
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)) {//調(diào)用中是否拋異常
// 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))) { //如果返回的是一個(gè)對(duì)象,同時(shí)返回結(jié)果還是要encode的對(duì)象,則直接調(diào)用 json_encode_array 進(jìn)行處理
// 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);//否則繼續(xù)調(diào)用 php_json_encode 遞歸處理
}
zval_ptr_dtor(&retval);
}
3.2.4 對(duì)數(shù)組的處理
我們?cè)賮砜匆幌氯绾螌?duì)數(shù)組進(jìn)行encode
之前也說過了對(duì) ARRAY 這種數(shù)組類型的,則會(huì)調(diào)用 json_encode_array 進(jìn)行處理,在 json_encode_array 里會(huì)遍歷數(shù)組,先插入 array 的key,然后再插入value(其實(shí)是通過調(diào)用php_json_encode,相互遞歸調(diào)用)
具體實(shí)現(xiàn)上有兩個(gè)步驟
首先判斷一下是以對(duì)象形式(k1:v1, k2:v2…)輸出,還是以數(shù)組形式輸出(k1,k2…)
如果val的類型非數(shù)組,則以對(duì)象形式輸出
否則如果val的類型是數(shù)組,看一下 options 參數(shù)是否有設(shè)置強(qiáng)制以對(duì)象形式輸出
如果沒有的話,則要看val的內(nèi)容判斷了,通過 json_determine_array_type 函數(shù)進(jìn)行判斷,判斷val是否是關(guān)聯(lián)性數(shù)組
遍歷數(shù)組,根據(jù)以對(duì)象形式輸出還是數(shù)組形式輸出進(jìn)行處理
以數(shù)組形式輸出,插入分隔符 , ,然后調(diào)用 php_json_encode 插入value
以對(duì)象形式輸出,插入key(如果key非字符串,則強(qiáng)制轉(zhuǎn)換為整數(shù)( 如果key非字符串,非整數(shù),那么呵呵 ),再轉(zhuǎn)換為字符串插入),然后調(diào)用 php_json_encode 插入value
static void json_encode_array(smart_str* buf, zval** val, int options TSRMLS_DC)
{
//判斷是以對(duì)象形式(k1:v1, k2:v2...)輸出,還是以數(shù)組形式輸出(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;
}
//..........................
//循環(huán)處理每個(gè)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) { //以數(shù)組形式輸出,不需要插入key,直接調(diào)用php_json_encode插入value
//..................
php_json_encode(buf, * data, options TSRMLS_CC); //數(shù)組形式,沒有key(其實(shí)是0,1,2這種數(shù)字),則直接調(diào)用 php_json_encode插入value
} else if (r == PHP_JSON_OUTPUT_OBJECT) { //以對(duì)象形式輸出,先插入key(對(duì)key是否為string做一些處理),然后調(diào)用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);//調(diào)用php_json_encode插入value
} else {
//..................
smart_str_append_long(buf, (long) index);//把key轉(zhuǎn)換為long,再轉(zhuǎn)換為string類型的,然后插入,但是如果key非字符串,非整數(shù),那么呵呵
//..................
php_json_encode(buf, * data, options TSRMLS_CC);//調(diào)用php_json_encode插入value
}
}
}
}
//..................
}
記得以前有一個(gè)題目,就是給定一個(gè)array,從php代碼上怎么判斷是關(guān)聯(lián)型的還是非關(guān)聯(lián)型的
http://stackoverflow.com/questions/173400/how-to-check-if-php-array-is-associative-or-sequential
再來看一下php內(nèi)部是怎么判斷一個(gè)數(shù)組是關(guān)聯(lián)型的還是非關(guān)聯(lián)型的
做法其實(shí)也是一樣的,遍歷每個(gè)key,判斷是否等于對(duì)應(yīng)的下標(biāo)
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
函數(shù)的原型如下,有兩個(gè)可選參數(shù)
mixed json_decode ( string json [, boolassoc = false [, int depth = 512 [, intoptions = 0 ]]] )
json : 這個(gè)就是要進(jìn)行decode的json字符串
assoc : 當(dāng)該參數(shù)為 true 時(shí),將返回 array 而非 object
depth : 遞歸處理的層數(shù)
options : 一些特殊需求,目前只有一個(gè)
JSON_BIGINT_AS_STRING : 當(dāng)value是大整數(shù)的時(shí)候,如果設(shè)置了這個(gè)選項(xiàng),則> value的類型是字符串,避免精度缺失,默認(rèn)是浮點(diǎn)數(shù)
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主函數(shù),其實(shí)就接受四個(gè)參數(shù),然后調(diào)用 php_json_decode_ex 進(jìn)行處理
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 里都做了什么事情
其實(shí)主要包括三個(gè)步驟
- 判斷是否為utf8編碼,如果是的話,同時(shí)轉(zhuǎn)換為utf16
- 先調(diào)用
parse_JSON_ex去解析這個(gè)json串,這個(gè)是整個(gè)解析的核心部分 - 如果失敗,則盡量而為去解析這個(gè)非標(biāo)準(zhǔn)的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 進(jìn)行處理
if (parse_JSON_ex(jp, z, utf16, utf16_len, options TSRMLS_CC)) {
* return_value = * z;
}
else //step3 對(duì)一些不規(guī)范的json串,盡力而為去decode,比如輸入的字符串是一個(gè)整數(shù),兩邊沒有被 " 包含
{
//去掉左右兩邊多余的 空格 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);
}
//處理數(shù)字
if ((type = is_numeric_string_ex(trim, trim_len, &p, &d, 0, &overflow_info)) != 0) {
if (type == IS_LONG) {
RETVAL_LONG(p); //返回整數(shù)
} else if (type == IS_DOUBLE) { //返回浮點(diǎn)數(shù)
//.......
}
}
//返回錯(cuò)誤
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串的
首先來看看解析的時(shí)候,需要用到的這個(gè)結(jié)構(gòu)體
typedef struct JSON_parser_struct {int state; //當(dāng)前的狀態(tài)int depth; //最大的深度int top; //當(dāng)前處于第幾層int error_code; //錯(cuò)誤碼int* stack; //記錄每一層正在解析的狀態(tài),取值有MODE_ARRAY, MODE_DONE, MODE_KEY, MODE_OBJECTzval ** the_zstack; //記錄每一層正在decode中的值的stack,如果 depth 小于 JSON_PARSER_DEFAULT_DEPTH,則用 the_static_zstack,否則重新申請(qǐng)內(nèi)存zval * the_static_zstack[JSON_PARSER_DEFAULT_DEPTH]; // 默認(rèn)使用的stack} * JSON_parser;
主要是通過狀態(tài)機(jī)來處理的
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) {
//...............
//主要是獲取每個(gè)key or value的值,存儲(chǔ)在buf變量里
jp->state = next_state;
} else {
//主要是處理json里的特殊標(biāo)志
// { or [ : 一個(gè)層次的開始標(biāo)識(shí)
// } or ] : 一個(gè)層次的結(jié)束標(biāo)志
// " :一個(gè)字符串取值的開始或者結(jié)束標(biāo)志
// : : 一個(gè)value的取值開始標(biāo)志,當(dāng)然這個(gè)value 可以是任何類型的
// , : 另外一個(gè)k/v or k的開始標(biāo)志
switch (next_state) {
case -9: // empty }
if (!pop(jp, MODE_KEY)) {
FREE_BUFFERS();
return false;
}
jp->state = OK;
break;
case -8: // } 一個(gè)層次的結(jié)束標(biāo)志
//...................
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;
}