Java 的 RSS → JSON 輸出方案

2007 年 七月 24 日 (星期二) 12:29 pm
分類:電腦
標籤:, , ,

隨著 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 的 SyndFeedSyndEntry 結構,以便進行 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 的組合,雖然整體組合有點複雜,但夠穩健,擴充性也夠。提供給各位朋友參考。


◤建議您一併閱讀以下文章:


3 項留言回應 給 “Java 的 RSS → JSON 輸出方案”

  1. 1 University Update - AJAX - Java 的 RSS → JSON 輸出方案 引用:

    隨著 Ajax 的風行,JavaScript 鹹魚翻身,也無心插柳拱起了另一個格式:JSON。如今,JSON 有 RFC 4627 加持,不僅成為 RSS 之外另一個更易程式化的 mashup 媒介,更浸浸然成為繼 XML 之後另一個更輕量級的資料交換標準。

    可惜的是,許多舊的資訊系統只提供 RSS 輸出,不提供 JSON。雖然我們可利用 Yahoo Pipes 組合出 RSS → JSON 的服務 [...]

  2. 2 Lu, Jye 留言:

    我試著使用 “JSON in Java” 裡
    org.json.XML.toJSONObject(java.lang.String string) 這個 method,
    可以正常運作,
    會不會是版本的問題呢?

  3. 3 william 留言:

    從 2002 年之後,它們應該就沒再改版了。

留言回應

[檢核碼]  


Allowed XHTML tags: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>

本站已啟用 spam 防護機制。為避免系統誤判,請在按下按鈕之前,先備份您的留言,以防不測。如果您一直無法順利留言,請改用 email 方式。
此外,如果您想留的言與本篇文章及討論串無關,也請轉而點選這裡。謝謝您!