傳送與接收 JSON



XML可用來表現階層性資料,然而建立與處理XML DOM較為複雜,在Web應用程式中,許多時候並不需要用到XML的複雜階層性,此時通常會採用JSON作為資料交換格式。

JSON全名JavaScript Object Notation,為JavaScript物件實字(Object literal)的子集,你可以在 http://www.json.org/ 找到詳細的JSON格式說明。大致而言,與物件實字格式類似,主要注意的是JSON:
  • 名稱為字串,必須用"雙引號包括
  • 值可以是"雙引號包括的字串,或者是數字、true、false、null、物件或陣列。
  • 不支援JavaScript的Data、Error、規則表示式或函式表示。

舉個例子來說,下面是個物件實字:
var obj = {
    name : 'Justin',
    age : 35,
    childs : [ { name : 'hamimi', age : 3} ]
};

若使用JSON表示,則是如下:
var json = '{"name":"Justin","age":35,"childs":[{"name":"hamimi","age":3}]}';

若為排版會比較容易觀察:
{
    "name":"Justin",
    "age":35,
    "childs":[
        {
            "name":"hamimi",
            "age":3
        }
    ]
}


你可以傳送JSON字串給伺服端,要建立JSON字串很簡單,在Firefox 3.1、Internet Explorer 8以上,支援簡單的JSON處理程式庫,如果要從物件建立JSON字串,只要使用JSON.stringify()。例如:
var obj = {
    name : 'Justin',
    age : 35,
    childs : [ { name : 'hamimi', age : 3} ]
};
var json = JSON.stringify(obj);

這樣就可以得到方才所示範的JSON字串,如果要用非同步物件傳送JSON,則可以如下:
var request = xhr(); // xhr() 會建立非同步物件
request.onreadystatechange = handleStateChange; // handleStateChange 參考至函式
request.open('POST', url);
request.setRequestHeader('Content-Type', 'application/json');
request.send(json);

請求標頭'Content-Type'建議設為'application/json'。當然,伺服端要能夠剖析JSON字串以取出資料,但無需親自撰寫,http://www.json.org/ 網站中提供許多語言實作的JSON剖析器,可協助你剖析JSON取得結果。

伺服端可以傳回JSON字串,可以使用eval()將JSON字串計值(evaluate)為JavaScript物件。例如:
var obj = eval(json);

然而eval()也會運算傳入的JavaScript程式碼,因此並不建議直接用eval(),如果只是要計值JSON為JavaScript物件,可以使用JSON.parse()。例如:
var obj = JSON.parse(json);

如果是Firefox 3.1、Internet Explorer 8以外沒有內建JSON支援的瀏覽器,可以在 http://www.json.org/ 下載 json2.js(還有其它檔案,你可以看看上頭的README說明),將之包括在網頁中,即可獲得上述的相關JSON方法:
<script type="text/javascript" src="json2.js">

如果遇到瀏覽器已內建JSON支援,json2.js什麼都不作。以下是個使用JSON的示範,會提示可搜尋的選項(沒有真的有搜尋功能就是了),如果有符合的選項,則會以JSON的格式傳回字串陣列,例如'["caterpillar", "ceo"]'這樣的格式(範例的伺服端上可搜尋的字串有"caterpillar"、"car"、"ceo"、"c++"、"justin"、"java"、"javascript"),你必須知道搜尋輸入方塊的位置,以讓選項對齊在輸入方塊下方,這可以參考 存取元素位置 第三個範例:
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Strict//EN">
<html>
<head>
<meta content="text/html; charset=UTF-8" http-equiv="content-type">
<style type="text/css">
div {
color: #ffffff;
background-color: #ff0000;
border-width: 1px;
border-color: black;
border-style: solid;
position: absolute;
}
</style>
<script type="text/javascript" src="js/json2.js"></script>
<script type="text/javascript">
window.onload = function() {
function offset(element) {
var x = 0;
var y = 0;
while(element) {
x += element.offsetLeft;
y += element.offsetTop;
element = element.offsetParent;
}
return {
x: x,
y: y,
toString: function() {
return '(' + this.x + ', ' + this.y + ')';
}
};
}
var xhr = window.XMLHttpRequest &&
(window.location.protocol !== 'file:'
|| !window.ActiveXObject) ?
function() {
return new XMLHttpRequest();
} :
function() {
try {
return new ActiveXObject('Microsoft.XMLHTTP');
} catch(e) {
throw new Error('XMLHttpRequest not supported');
}
};

function param(obj) {
var pairs = [];
for(var name in obj) {
var pair = encodeURIComponent(name) + '=' +
encodeURIComponent(obj[name]);
pairs.push(pair.replace('/%20/g', '+'));
}
return pairs.join('&');
}

function ajax(option) {
option.type = option.type || 'GET';
option.header = option.header || {
'Content-Type':'application/x-www-form-urlencoded'};
option.callback = option.callback || function() {};

if(!option.url) {
return;
}

var request = xhr();
request.onreadystatechange = function() {
option.callback.call(request, request);
};

var body = null;
var url = option.url;
if(option.data) {
if(option.type === 'POST') {
body = param(option.data);
}
else {
url = option.url + '?' + param(option.data)
+ '&time=' + new Date().getTime();
}
}

request.open(option.type, url);
for(var name in option.header) {
request.setRequestHeader(
name, option.header[name]);
}
request.send(body);
}

var search = document.getElementById('search');
search.onkeyup = function() {
// 移除選項的<div>容器
var divs = document.getElementsByTagName('div');
for(var i = 0; i < divs.length; i++) {
document.body.removeChild(divs[i]);
}
// 沒有輸入值,直接結束
if(search.value === '') {
return;
}
// 發出非同步請求,取得可能的選項,以JSON的字串陣列格式傳回
ajax({
url : 'JSON-1.php',
data : {keyword : search.value},
callback: function(request) {
if(request.readyState === 4) {
if(request.status === 200) {
// 剖析JSON
var keywords = JSON.parse(
request.responseText);
// 字串陣列長度不為0時加以處理
if(keywords.length !== 0) {
var innerHTML = '';
for(var i = 0;
i < keywords.length; i++) {
innerHTML +=
(keywords[i] + '<br>');
}
// 建立容納選項的<div>
var div =
document.createElement('div');
div.innerHTML = innerHTML;
// 設定<div>的位置
var xy = offset(search);
div.style.left = xy.x + 'px';
div.style.top =
xy.y + search.offsetHeight + 'px';
div.style.width =
search.offsetWidth + 'px';
// 附加至DOM樹
document.body.appendChild(div);
}
}
}
}
});
};
};
</script>
</head>
<body>
<hr>
搜尋:<input id="search" type="text">
</body>
</html>

這個例子沒有考慮使用者打字速度,因此每鍵一個字就會發出一次請求,你可以試著設定時間,例如一秒才發出一次,以避免使用者打字速度過快,頻繁發出請求的問題。

另外,對於每個選項,你還可以加上滑鼠點選事件,在使用者點選項目時,將項目值設定至輸入方塊,並移除<div>,這些額外工作,可以自行練習看看。