# Javascript/PHP/Python/JSON Encoding Summary#
项目中UI采用Javascript(前端) + PHP(中间层) + Python(后端) + Memcached(存储),中间使用JSON传递数据。
遇到一些编码的问题,索性在此总结一下。整套流程中间和最终存储数据均采用UTF-8。
## 1. Unicode, UTF-8, UTF-16, UTF-32, UCS-2, UCS-4区别 ##
### 1.1 UTF-8: 使用1到4个字节的变长编码 ###
000000-00007F(128个编码) 0zzzzzzz ASCII编码
000080-0007FF(1920个编码) 110yyyyy(C0-DF) 10zzzzzz(80-BF) 由110开始,接着1个10开始
000800-00D7FF
00E000-00FFFF(61440个编码) 1110xxxx(E0-FF) 10yyyyyy 10zzzzzz 由1110开始,接着2个10开始
010000-10FFFF(1048576个编码) 11110www(F0-F7) 10xxxxxx 10yyyyyy 10zzzzzz 由11110开始,接着3个10开始
对于UTF-8编码中的任意字节B,如果B的第一位为0,则B为ASCII码,并且B独立的表示一个字符;
如果B的第一位为1,第二位为0,则B为一个非ASCII字符(该字符由多个字节表示)中的一个字节,并且不为字符的第一个字节编码;
如果B的前两位为1,第三位为0,则B为一个非ASCII字符(该字符由多个字节表示)中的第一个字节,并且该字符由两个字节表示;
如果B的前三位为1,第四位为0,则B为一个非ASCII字符(该字符由多个字节表示)中的第一个字节,并且该字符由三个字节表示;
如果B的前四位为1,第五位为0,则B为一个非ASCII字符(该字符由多个字节表示)中的第一个字节,并且该字符由四个字节表示;
因此,对UTF-8编码中的任意字节,根据第一位,可判断是否为ASCII字符;根据前二位,可判断该字节是否为一个字符编码的第一个字节;
根据前四位(如果前两位均为1),可确定该字节为字符编码的第一个字节,并且可判断对应的字符由几个字节表示;根据前五位(如果前四位为1),可判断编码是否有错误或数据传输过程中是否有错误。
### 1.2 UTF-16 ###
U+0000-U+FFFF xxxxxxxx xxxxxxxx 0-65535(1个基本平面) 2字节
U+10000-U+10FFFF 110110yyyyyyyyyy 110111xxxxxxxxxx 65536-1114111(16个辅助平面) 4字节
存储时存在字节序问题,分为UTF-16LE和UTF-16BE,可以通过BOM头来区分。
### 1.3 UCS-2 ###
它是UTF-16的子集,在没有辅助平面时二者等价。
### 1.4 UTF-32/UCS-4 ###
U+00000000-U+7FFFFFFF 占用4个字节,高字节首位为0,0~2^31
## 2. 网页编码 ##
设定网页的charset为utf-8,这样用户提交的输入都是utf-8编码。
Charset的指定:
1. 在response header中设置content-type
Content-Type: text/html;charset=utf-8
在PHP中可以使用如下函数实现,
header("Content-Type: text/html; charset=utf-8");
2. 在page中设置meta标签
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
因为标签包含在html页面中,因此用户通过保存网页也能正常工作
3. 比较好的做法时同时设置header和meta标签,并保证两者一致。
## 3. 正向处理(Javascript->PHP->Python->Memcached)
### 3.1 Javascript Object处理 ###
经过指定charset为UTF-8后,从表单中获取的输入的编码都是UTF-8编码。
Javascript在构造string对象时会按照UCS-2编码,所以此时用户输入的UTF-8编码数据会转换为UCS-2。
最终表单提交时,提交的字符串会根据网页设置的编码转化成UTF-8提交到服务器端。
Javascript中JSON和字符串间的转换可以采用下面的函数实现。
假设Javasciprt中的Object某个key的value是字符串,将这个object转化成string,此时的string是unicode(UCS-2)编码
// Convert JSON to String
function jsonToString(obj) {
var THIS = this;
switch(typeof(obj)){
case 'string':
// 转义'"', '\'两个字符
return '"' + obj.replace(/(["\\])/g, '\\$1') + '"';
case 'array':
return '[' + obj.map(THIS.jsonToString).join(',') + ']';
case 'object':
if(obj instanceof Array){
var strArr = [];
var len = obj.length;
for(var i=0; i<len; i++){
strArr.push(THIS.jsonToString(obj[i]));
}
return '[' + strArr.join(',') + ']';
}else if(obj==null){
return 'null';
}else{
var string = [];
for (var property in obj) string.push(THIS.jsonToString(property) + ':' + THIS.jsonToString(obj[property]));
return '{' + string.join(',') + '}';
}
case 'number':
return obj;
case false:
return obj;
}
}
可以使用如下代码验证string的编码:
// Get the code point of javascript string
var jsonString = jsonToString(obj);
var codePoints = "";
for(var i = 0; i < jsonString.length; i++) {
codePoints += jsonString.charCodeAt(i) + " ";
}
### 3.2 PHP处理 ###
当PHP处理的request的charset是UTF-8时,PHP获取的字符串就是UTF-8编码。
对于content-type:application/x-www-form-urlencoded时,PHP获取的post表单数据时经过url encode的,从$_POST获取的数据已经经过url decode。
获取post数据(JSON),然后调用json_decode进行解码,json_decode接收的参数必须是UTF-8编码(不支持BOM),decode之后的对象中的字符串仍然是UTF-8编码。
### 3.3 Python处理 ###
Python收到utf-8的输入,经过json.loads输出json对象,对象中所有的key和string value全部是unicode编码。
具体Python使用哪种unicode编码由编译选项决定。
>> sys.maxunicode
如果是65535是UCS-2,1114111为UCS-4。
>>> obj = json.loads('{"a": "aaa", "b": "排序"}')
>>> obj
{u'a': u'aaa', u'b': u'\u6392\u5e8f'}
json对象dump成字符串时,默认会decode成ascii格式。对多字节unicode编码会decode成如下格式。
>>> objstr = json.dumps(obj)
>>> objstr
'{"a": "aaa", "b": "\\u6392\\u5e8f"}'
>>> type(objstr)
<type 'str'>
>>> print(objstr)
{"a": "aaa", "b": "\u6392\u5e8f"}
通过参数ensure_ascii=False,可以decode成unicode字符串。
>>> objstr = json.dumps(obj, ensure_ascii=False)
>>> print(objstr)
{"a": "aaa", "b": "排序"}
>>> type(objstr)
<type 'unicode'>
>>> objstr.encode('utf-8')
'{"a": "aaa", "b": "\xe6\x8e\x92\xe5\xba\x8f"}'
### 3.4 Memcached ###
通过Python的json.dumps调用后的JSON字符串,会被存储到Memcached。后台应用需要使用Memcached中的数据时,可以直接读出然后进行JSON解析。
比如[Jansson](http://www.digip.org/jansson/),可以很好的进行JSON解析,但它只接受UTF-8编码的字符串输入。以下两种格式JSON都能很好支持,输出是UTF-8编码的字符串。
{"a": "aaa", "b": "\u6392\u5e8f"}
'{"a": "aaa", "b": "\xe6\x8e\x92\xe5\xba\x8f"}'
## 4. 反方向处理(Memcached->Python->PHP->Javascript) ##
### 4.1 Python ###
从Memcached读取数据,经过处理后,使用json.dumps将json对象转换成ascii格式的字符串,多字节unicode以\u形式编码
> {"a": "aaa", "b": "\u6392\u5e8f"}
将ascii格式字符串传递给PHP。
### 4.2 PHP ###
PHP将对应的ascii格式字符串返回给客户端Javascript。
### 4.3 Javascript ###
Javascript使用如下函数将string转换成Javascript对象。
// Convert String to JSON
function stringToJSON(obj) {
return eval('(' + obj + ')');
}
## 5. Javascript UTF-16 support issue ##
Javascript字符串默认使用unicode(UCS-2)编码,每个字符2个字节表示,比如
> var data = "hello";
字符串的length计算的是多少个2字节。所以对于UTF-16中高位的编码支持存在问题。
以下是work around方法:
String.prototype.getFullCharLength=function() {
return this.length-this.split(/[\uD800-\uDBFF][\uDC00-\uDFFF]/g).length+1;
};
String.fromFullCharCode=function() {
var chars= [];
for (var i= 0; i<arguments.length; i++) {
var n= arguments[i];
if (n<0x10000)
chars.push(String.fromCharCode(n));
else
chars.push(String.fromCharCode(
0xD800+((n-0x10000)&0x3FF),
0xDC00+(((n-0x10000)>>10)&0x3FF)
));
}
return chars.join('');
};
## 6. PHP string encoding ##
PHP字符串就是多个字节的组合,不包含任何编码信息。
strlen计算的是字节数,mbstring模块支持多字节的编码,比如mb_strlen。
## 7. python string encoding ##
和PHP类似,可以对字节串进行编码转换。
## Reference ##
* [How to find out if python is compiled with ucs2 or ucs4](http://stackoverflow.com/questions/1446347/how-to-find-out-if-python-is-compiled-with-ucs-2-or-ucs-4)
* [http://stackoverflow.com/questions/3744721/javascript-strings-outside-of-the-bmp](http://stackoverflow.com/questions/3744721/javascript-strings-outside-of-the-bmp)
* [http://terenceyim.wordpress.com/category/programming/javascript/](http://terenceyim.wordpress.com/category/programming/javascript/)
* [https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/String/charAt](https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/String/charAt)
* [http://stackoverflow.com/questions/8715980/javascript-strings-utf-16-vs-ucs-2](http://stackoverflow.com/questions/8715980/javascript-strings-utf-16-vs-ucs-2)
* [How does PHP internally represent strings](http://programmers.stackexchange.com/questions/151293/how-does-php-internally-represent-strings)
项目中UI采用Javascript(前端) + PHP(中间层) + Python(后端) + Memcached(存储),中间使用JSON传递数据。
遇到一些编码的问题,索性在此总结一下。整套流程中间和最终存储数据均采用UTF-8。
## 1. Unicode, UTF-8, UTF-16, UTF-32, UCS-2, UCS-4区别 ##
### 1.1 UTF-8: 使用1到4个字节的变长编码 ###
000000-00007F(128个编码) 0zzzzzzz ASCII编码
000080-0007FF(1920个编码) 110yyyyy(C0-DF) 10zzzzzz(80-BF) 由110开始,接着1个10开始
000800-00D7FF
00E000-00FFFF(61440个编码) 1110xxxx(E0-FF) 10yyyyyy 10zzzzzz 由1110开始,接着2个10开始
010000-10FFFF(1048576个编码) 11110www(F0-F7) 10xxxxxx 10yyyyyy 10zzzzzz 由11110开始,接着3个10开始
对于UTF-8编码中的任意字节B,如果B的第一位为0,则B为ASCII码,并且B独立的表示一个字符;
如果B的第一位为1,第二位为0,则B为一个非ASCII字符(该字符由多个字节表示)中的一个字节,并且不为字符的第一个字节编码;
如果B的前两位为1,第三位为0,则B为一个非ASCII字符(该字符由多个字节表示)中的第一个字节,并且该字符由两个字节表示;
如果B的前三位为1,第四位为0,则B为一个非ASCII字符(该字符由多个字节表示)中的第一个字节,并且该字符由三个字节表示;
如果B的前四位为1,第五位为0,则B为一个非ASCII字符(该字符由多个字节表示)中的第一个字节,并且该字符由四个字节表示;
因此,对UTF-8编码中的任意字节,根据第一位,可判断是否为ASCII字符;根据前二位,可判断该字节是否为一个字符编码的第一个字节;
根据前四位(如果前两位均为1),可确定该字节为字符编码的第一个字节,并且可判断对应的字符由几个字节表示;根据前五位(如果前四位为1),可判断编码是否有错误或数据传输过程中是否有错误。
### 1.2 UTF-16 ###
U+0000-U+FFFF xxxxxxxx xxxxxxxx 0-65535(1个基本平面) 2字节
U+10000-U+10FFFF 110110yyyyyyyyyy 110111xxxxxxxxxx 65536-1114111(16个辅助平面) 4字节
存储时存在字节序问题,分为UTF-16LE和UTF-16BE,可以通过BOM头来区分。
### 1.3 UCS-2 ###
它是UTF-16的子集,在没有辅助平面时二者等价。
### 1.4 UTF-32/UCS-4 ###
U+00000000-U+7FFFFFFF 占用4个字节,高字节首位为0,0~2^31
## 2. 网页编码 ##
设定网页的charset为utf-8,这样用户提交的输入都是utf-8编码。
Charset的指定:
1. 在response header中设置content-type
Content-Type: text/html;charset=utf-8
在PHP中可以使用如下函数实现,
header("Content-Type: text/html; charset=utf-8");
2. 在page中设置meta标签
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
因为标签包含在html页面中,因此用户通过保存网页也能正常工作
3. 比较好的做法时同时设置header和meta标签,并保证两者一致。
## 3. 正向处理(Javascript->PHP->Python->Memcached)
### 3.1 Javascript Object处理 ###
经过指定charset为UTF-8后,从表单中获取的输入的编码都是UTF-8编码。
Javascript在构造string对象时会按照UCS-2编码,所以此时用户输入的UTF-8编码数据会转换为UCS-2。
最终表单提交时,提交的字符串会根据网页设置的编码转化成UTF-8提交到服务器端。
Javascript中JSON和字符串间的转换可以采用下面的函数实现。
假设Javasciprt中的Object某个key的value是字符串,将这个object转化成string,此时的string是unicode(UCS-2)编码
// Convert JSON to String
function jsonToString(obj) {
var THIS = this;
switch(typeof(obj)){
case 'string':
// 转义'"', '\'两个字符
return '"' + obj.replace(/(["\\])/g, '\\$1') + '"';
case 'array':
return '[' + obj.map(THIS.jsonToString).join(',') + ']';
case 'object':
if(obj instanceof Array){
var strArr = [];
var len = obj.length;
for(var i=0; i<len; i++){
strArr.push(THIS.jsonToString(obj[i]));
}
return '[' + strArr.join(',') + ']';
}else if(obj==null){
return 'null';
}else{
var string = [];
for (var property in obj) string.push(THIS.jsonToString(property) + ':' + THIS.jsonToString(obj[property]));
return '{' + string.join(',') + '}';
}
case 'number':
return obj;
case false:
return obj;
}
}
可以使用如下代码验证string的编码:
// Get the code point of javascript string
var jsonString = jsonToString(obj);
var codePoints = "";
for(var i = 0; i < jsonString.length; i++) {
codePoints += jsonString.charCodeAt(i) + " ";
}
### 3.2 PHP处理 ###
当PHP处理的request的charset是UTF-8时,PHP获取的字符串就是UTF-8编码。
对于content-type:application/x-www-form-urlencoded时,PHP获取的post表单数据时经过url encode的,从$_POST获取的数据已经经过url decode。
获取post数据(JSON),然后调用json_decode进行解码,json_decode接收的参数必须是UTF-8编码(不支持BOM),decode之后的对象中的字符串仍然是UTF-8编码。
### 3.3 Python处理 ###
Python收到utf-8的输入,经过json.loads输出json对象,对象中所有的key和string value全部是unicode编码。
具体Python使用哪种unicode编码由编译选项决定。
>> sys.maxunicode
如果是65535是UCS-2,1114111为UCS-4。
>>> obj = json.loads('{"a": "aaa", "b": "排序"}')
>>> obj
{u'a': u'aaa', u'b': u'\u6392\u5e8f'}
json对象dump成字符串时,默认会decode成ascii格式。对多字节unicode编码会decode成如下格式。
>>> objstr = json.dumps(obj)
>>> objstr
'{"a": "aaa", "b": "\\u6392\\u5e8f"}'
>>> type(objstr)
<type 'str'>
>>> print(objstr)
{"a": "aaa", "b": "\u6392\u5e8f"}
通过参数ensure_ascii=False,可以decode成unicode字符串。
>>> objstr = json.dumps(obj, ensure_ascii=False)
>>> print(objstr)
{"a": "aaa", "b": "排序"}
>>> type(objstr)
<type 'unicode'>
>>> objstr.encode('utf-8')
'{"a": "aaa", "b": "\xe6\x8e\x92\xe5\xba\x8f"}'
### 3.4 Memcached ###
通过Python的json.dumps调用后的JSON字符串,会被存储到Memcached。后台应用需要使用Memcached中的数据时,可以直接读出然后进行JSON解析。
比如[Jansson](http://www.digip.org/jansson/),可以很好的进行JSON解析,但它只接受UTF-8编码的字符串输入。以下两种格式JSON都能很好支持,输出是UTF-8编码的字符串。
{"a": "aaa", "b": "\u6392\u5e8f"}
'{"a": "aaa", "b": "\xe6\x8e\x92\xe5\xba\x8f"}'
## 4. 反方向处理(Memcached->Python->PHP->Javascript) ##
### 4.1 Python ###
从Memcached读取数据,经过处理后,使用json.dumps将json对象转换成ascii格式的字符串,多字节unicode以\u形式编码
> {"a": "aaa", "b": "\u6392\u5e8f"}
将ascii格式字符串传递给PHP。
### 4.2 PHP ###
PHP将对应的ascii格式字符串返回给客户端Javascript。
### 4.3 Javascript ###
Javascript使用如下函数将string转换成Javascript对象。
// Convert String to JSON
function stringToJSON(obj) {
return eval('(' + obj + ')');
}
## 5. Javascript UTF-16 support issue ##
Javascript字符串默认使用unicode(UCS-2)编码,每个字符2个字节表示,比如
> var data = "hello";
字符串的length计算的是多少个2字节。所以对于UTF-16中高位的编码支持存在问题。
以下是work around方法:
String.prototype.getFullCharLength=function() {
return this.length-this.split(/[\uD800-\uDBFF][\uDC00-\uDFFF]/g).length+1;
};
String.fromFullCharCode=function() {
var chars= [];
for (var i= 0; i<arguments.length; i++) {
var n= arguments[i];
if (n<0x10000)
chars.push(String.fromCharCode(n));
else
chars.push(String.fromCharCode(
0xD800+((n-0x10000)&0x3FF),
0xDC00+(((n-0x10000)>>10)&0x3FF)
));
}
return chars.join('');
};
## 6. PHP string encoding ##
PHP字符串就是多个字节的组合,不包含任何编码信息。
strlen计算的是字节数,mbstring模块支持多字节的编码,比如mb_strlen。
## 7. python string encoding ##
和PHP类似,可以对字节串进行编码转换。
## Reference ##
* [How to find out if python is compiled with ucs2 or ucs4](http://stackoverflow.com/questions/1446347/how-to-find-out-if-python-is-compiled-with-ucs-2-or-ucs-4)
* [http://stackoverflow.com/questions/3744721/javascript-strings-outside-of-the-bmp](http://stackoverflow.com/questions/3744721/javascript-strings-outside-of-the-bmp)
* [http://terenceyim.wordpress.com/category/programming/javascript/](http://terenceyim.wordpress.com/category/programming/javascript/)
* [https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/String/charAt](https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/String/charAt)
* [http://stackoverflow.com/questions/8715980/javascript-strings-utf-16-vs-ucs-2](http://stackoverflow.com/questions/8715980/javascript-strings-utf-16-vs-ucs-2)
* [How does PHP internally represent strings](http://programmers.stackexchange.com/questions/151293/how-does-php-internally-represent-strings)