Java 的 RSS → JSON 輸出方案
2007 年 七月 24 日 (星期二) 12:29 pm分類:電腦
標籤: programming, Java, json, RSS
隨著 Ajax 的風行,JavaScript 鹹魚翻身,也無心插柳拱起了另一個格式:JSON。如今,JSON 有 RFC 4627 加持,不僅成為 RSS 之外另一個更易程式化的 mashup 媒介,更浸浸然成為繼 XML 之後另一個更輕量級的資料交換標準。
可惜的是,許多舊的資訊系統只提供 RSS 輸出,不提供 JSON。雖然我們可利用 Yahoo Pipes 組合出 RSS → JSON 的服務,但如果我們不想假手他人,想自己用 Java 寫這段轉換程式呢?
首先想到的就是掛著 JSON.org 名號的 “JSON in Java” 程式庫。它很輕巧,又是 Json-lib 的基礎,原本以為應該是很適合的工具;只可惜不夠強固,譬如說,拿 http://feeds.feedburner.com/william 餵給它,居然吐出一個 JSONException。天哪,我可不敢拿這玩意兒來做正事。
這世界本來就充斥著不標準的 RSS 輸出(君不見就連 Yahoo 這麼大的網站都有問題;詳見 Qing 的〈用 ROME parse Yahoo 新聞的 RSS 內容〉一文),all-in-one 的 RSS → JSON 方案可能也都有類似的容錯問題,畢竟 RSS parser 本身就已經是一大學問了。因此,我退而求其次,先找個好的 RSS parser,再設法串接合用的 JSON 輸出產生器。
在 Java 世界裡,ROME 是個具有準官方地位的 RSS/Atom 程式庫,容錯能力強,拿它做為 RSS parser 是再好也不過了:
import com.sun.syndication.feed.synd.SyndFeed;
import com.sun.syndication.io.SyndFeedInput;
import java.io.Reader;
//...
public SyndFeed parseRSS(Reader rss_reader)
{
SyndFeedInput input = new SyndFeedInput();
SyndFeed feed = input.build(rss_reader);
return feed;
}
XStream 則是廣受好評的 XML serialization 程式庫,極具彈性的架構,使得不只是 XML,只要是階層狀結構,都可納入它的 serialization 範圍。譬如說,只要掛上 Jettison,就可將 JSON 納入 XStream 體系:
import com.thoughtworks.xstream.XStream;
import com.thoughtworks.xstream.io.json.JettisonMappedXmlDriver;
//...
public String convertToJSON(Object rss_content)
{
XStream json_stream = new XStream(new JettisonMappedXmlDriver());
String json_content = json_stream.toXML(rss_content);
return json_content;
}
因此,ROME + XStream + Jettison 的組合,似乎是兼顧容錯及彈性的 RSS → JSON 解決方案。
剩下來的問題是:怎麼樣才能把 parseRSS() 的傳回值當成參數餵給 convertToJSON()?方法是:讓 XStream 認得 ROME 的 SyndFeed 及 SyndEntry 結構,以便進行 marshalling。
因此,我根據 XStream 提供的 Converter Tutorial 文件,寫了兩個轉換器。此刻我只需要用到 marshalling,所以省略掉 unmarshalling 的部份:
// ROME
import com.sun.syndication.feed.synd.SyndFeed;
import com.sun.syndication.feed.synd.SyndEntry;
// XStream converter
import com.thoughtworks.xstream.converters.Converter;
import com.thoughtworks.xstream.converters.MarshallingContext;
import com.thoughtworks.xstream.converters.UnmarshallingContext;
import com.thoughtworks.xstream.io.*;
import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.Locale;
import java.util.List;
//...
class SyndFeedConverter implements Converter {
public boolean canConvert(Class type) {
// SyndFeed and subclasses
return SyndFeed.class.isAssignableFrom(type);
}
public void marshal(Object value,
HierarchicalStreamWriter writer,
MarshallingContext context) {
SyndFeed channel = (SyndFeed) value;
String str;
if ((str = channel.getTitle()) != null) {
writer.startNode("title");
writer.setValue(str);
writer.endNode();
}
if ((str = channel.getLink()) != null) {
writer.startNode("link");
writer.setValue(str);
writer.endNode();
}
if ((str = channel.getDescription()) != null) {
writer.startNode("description");
writer.setValue(str);
writer.endNode();
}
// 其他你想處理的欄位,請依此類推...
List<SyndEntry> items = channel.getEntries();
if (items != null && items.size() > 0) {
// 交給 SyndEntryConverter 處理
context.convertAnother(items);
}
}
public Object unmarshal(HierarchicalStreamReader reader,
UnmarshallingContext context) {
return null;
}
}
//==============================================================//
class SyndEntryConverter implements Converter {
private static DateFormat date_formatter = new // RFC 822
SimpleDateFormat("EEE, dd MMM yyyy HH:mm:ss z", Locale.US);
public boolean canConvert(Class type) {
// SyndEntry and subclasses
return SyndEntry.class.isAssignableFrom(type);
}
public void marshal(Object value,
HierarchicalStreamWriter writer,
MarshallingContext context) {
SyndEntry item = (SyndEntry) value;
String str;
Date date;
if ((str = item.getTitle()) != null) {
writer.startNode("title");
writer.setValue(str);
writer.endNode();
}
if ((str = item.getLink()) != null) {
writer.startNode("link");
writer.setValue(str);
writer.endNode();
}
if ((date = item.getPublishedDate()) != null) {
writer.startNode("pubDate");
writer.setValue(date_formatter.format(date));
writer.endNode();
}
// 其他你想處理的欄位,請依此類推...
}
public Object unmarshal(HierarchicalStreamReader reader,
UnmarshallingContext context) {
return null;
}
}
別忘記在呼叫 XStream.toXML() 之前,先掛上這兩個 converter。為了 JSON 輸出美觀起見,也替它們設好 alias:
public String convertToJSON(Object rss_content)
{
XStream json_stream = new XStream(new JettisonMappedXmlDriver());
json_stream.alias("channel", SyndFeedImpl.class);
json_stream.alias("item", SyndEntryImpl.class);
json_stream.registerConverter(new SyndFeedConverter());
json_stream.registerConverter(new SyndEntryConverter());
String json_content = json_stream.toXML(rss_content);
return json_content;
}
大功告成。拿前面提過的 http://feeds.feedburner.com/william 餵給它試試看,得到的輸出會是:
{"channel":{
"title":"William's Blog",
"link":"http:\/\/william.cswiz.org\/blog",
"description":"readings, ideas, feelings, photos, etc.",
"item":[
{
"title":"黑米外掛:文章排行榜",
"link":"http:\/\/feeds.feedburner.com\/~r\/william\/~3\/128405511\/",
"description":
"楔子\n對同一個網站,各人有各自的使用方式,也各有不同的預期。..."
},
{
"title":"程式語言復習札記:null 判斷 ",
"link":"http:\/\/feeds.feedburner.com\/~r\/william\/~3\/127388841\/",
"pubDate":"Sun, 24 Jun 2007 08:51:54 CST",
"description":
"程式語言學得多了、用得久了,細節難免遺忘,甚至張冠李戴。..."
},
{
"title":"xmliconv:解決 Yahoo Pipes 中文編碼問題",
"link":"http:\/\/feeds.feedburner.com\/~r\/william\/~3\/124429564\/",
"pubDate":"Wed, 13 Jun 2007 16:19:15 CST",
"description":
"Yahoo Pipes 是個極富創意的服務,將客製化權力下放..."
},
[略]
}
]
}
}
我很滿意 ROME + XStream + Jettison 的組合,雖然整體組合有點複雜,但夠穩健,擴充性也夠。提供給各位朋友參考。


追蹤留言回應:以
引用通告 (trackback):![[add to funP]](http://william.cswiz.org/blog/wp-content/themes/william/images/add-funp.png)
![[add to HEMiDEMi]](http://www.hemidemi.com/sticker/user/roxytom.bluecircus.net.gif)
![[add to udn bookmark]](http://bookmark.udn.com/html/help/80_20_02.gif)

2007 年 七月 24日 於 7:03 pm
隨著 Ajax 的風行,JavaScript 鹹魚翻身,也無心插柳拱起了另一個格式:JSON。如今,JSON 有 RFC 4627 加持,不僅成為 RSS 之外另一個更易程式化的 mashup 媒介,更浸浸然成為繼 XML 之後另一個更輕量級的資料交換標準。
可惜的是,許多舊的資訊系統只提供 RSS 輸出,不提供 JSON。雖然我們可利用 Yahoo Pipes 組合出 RSS → JSON 的服務 [...]
2007 年 七月 25日 於 2:31 pm
我試著使用 “JSON in Java” 裡
org.json.XML.toJSONObject(java.lang.String string) 這個 method,
可以正常運作,
會不會是版本的問題呢?
2007 年 七月 25日 於 2:45 pm
從 2002 年之後,它們應該就沒再改版了。