2010年1月18日月曜日

第2回 データを分解してみる

第1回の説明が少々長くなりすぎた気がしますので、
今回から少し簡潔に書いていきたいと思います。
わからないところは適宜検索したり、参考書などを見たり、質問したりしてください。

第1回で取得したのは1つの長いテキストデータだったので
今回はこれを分解します。
TwitterはXML形式とJSON形式で取得でき、前回はXML形式で取得しましたが、
Search APIとかはXML形式に対応してないので、今回からJSON形式にします。

まず前回のコードのURLの語尾を.jsonに換えて再実行してみてくだだい。
string url =  "http://twitter.com/statuses/user_timeline/yuki1090.json";

すると同じようなデータが異なるフォーマットで得られたと思います。
適当に改行とインデントを入れると次のようになります。



[
{"truncated":false,
  "created_at":"Mon Jan 11 15:26:14 +0000 2010",
・・・略・・・
   "user":{
    "profile_background_color":"1A1B1F",
・・・略・・・
    "screen_name":"yuki1090",
・・・略・・・
   },
・・・略・・・
   "text":"つぶやいた内容のUnicode文字列(読めない)"
},
{"truncated":false,
・・・以下略




{}で囲まれたところが一つのオブジェクトです。
つまり
[
   {つぶやき
       {つぶやいたユーザ情報}
   }
,
   {次のつぶやき
      {つぶやいたユーザ情報}
   }
, ・・・以下繰り返し

の構造になっていることがわかります。
これをクラスで表現します。

前準備として、プロジェクトの「参照設定」を右クリックして、「参照の追加」→「.Netタブ」
・System.ServiceModel.Web
・System.Runtime.Serialization
を追加します。
※Webの方が選択肢に出ない場合は、プロジェクトのプロパティで
対象のフレームワークがClientProfileになっていないか確認してください。
ClientProfileでは出ません。

今回はつぶやいた内容のTextと投稿ユーザ情報のscreen_nameを利用します。
つぶやきを表すStatusクラスとユーザ情報を表すUserクラスを以下のように作ります。




using System.Runtime.Serialization;

namespace WTwitter.Model.Twitter {
 /// <summary>
 /// TwitterのJson形式データ
 /// </summary>
 [DataContract]
 class Status {
  [DataMember(Name = "text")]
  public string Text {
   set;
   get;
  }

  [DataMember(Name="user")]
  public User User {
   set;
   get;
  }

  public override string ToString() {
   return User.ScreenName + ":" + Text;
  }
 }
}






using System.Runtime.Serialization;

namespace WTwitter.Model.Twitter {
 /// <summary>
 /// TwitterのJson形式のユーザ情報
 /// </summary>
 [DataContract]
 class User {
  [DataMember(Name = "screen_name")]
  public string ScreenName {
   get;
   set;
  }
 }
}





StatusクラスがUser情報を持っていると言うことが表現できていると思います。
この情報を使って表示するようにWindow1クラスのWindow_Loadedメソッドを書き直します。



  private void Window_Loaded(object sender, RoutedEventArgs e) {
   //取得するURL
   string url =  "http://twitter.com/statuses/user_timeline/yuki1090.json";
  
   //HTTP用の要求と応答
   HttpWebRequest request = HttpWebRequest.Create(url) as HttpWebRequest;
   HttpWebResponse response = request.GetResponse() as HttpWebResponse;

   //Json形式でデータを取得する
   DataContractJsonSerializer serializer = new DataContractJsonSerializer(typeof(List));
   var stream = response.GetResponseStream();
   var result = serializer.ReadObject(stream) as List;

   response.Close();

   //取得した結果を1つ1行でTextboxに表示
   foreach (var item in result) {
    ContentTextBox.AppendText(item.ToString() + Environment.NewLine);
   }
  }



今回のソースコード
http://wtwitter.codeplex.com/SourceControl/changeset/view/37634

ソースだけでわかった人は以下読まなくてもOKです。

■解説

・プログラムのデータをファイルやネットのデータとして送受信するには
データを文字列に変換(シリアライズ)、文字からデータへ復元(デシリアライズ)します。
今回はJson形式で受信したテキストを分解ために
DataContractJsonSerializerを使います。
このクラスを使うと、変換を簡単にできます。
・シリアライザを使うときにはデータ形式を表すクラスに
[DataContract]
の属性を付けます。
またシリアライズ/デシリアライズの対象のクラス変数やプロパティに
[DataMember(Name = "???")]
を付けます。Name=???はJsonのどの部分に相当するかを表します。
・TwitterAPIの仕様のXMLのサンプルを見るとわかりますが、
1つのつぶやきはStatusという名前になっているので今回のクラス名もそうしました。

・Window1クラスの説明
-DataContractJsonSerializer serializer = new DataContractJsonSerializer(typeof(List));
シリアライズ対象がStatusのリストであることを伝えて、インスタンス化します。
-var result = serializer.ReadObject(stream) as List;
ストリームからシリアライザを利用してデータを読み込みます。
 as Listの部分で取得した結果をListに変換して
変数resultに入れています。
-取得したresultには一連のStatusが入っていますので、
それぞれに対してToString()で文字列化してテキストボックスに追加していきます。

9 件のコメント:

  1. すいません。教えてください。

    >foreach ステートメントは
    >'System.Windows.Documents.List'が
    >'GetEnumerator' のパブリック定義を
    >含んでいないため、型
    > 'System.Windows.Documents.List'
    >の変数に対して使用できません。

    って出てくるんですが、なにか根本的に間違っているような気がします。どうしたらいいんでしょう・・・。

    返信削除
  2. すいません、教えてください。
    ソースコードを見ていて分かったんですけど、
    Status.cs
    User.cs
    ってどうやってファイルを作ったらいいんでしょうか?

    返信削除
  3. 今回のソースコード
    http://wtwitter.codeplex.com/SourceControl/changeset/view/37634
    は見てもらってますか?
    どこかのUsingが足りないのかもしれません。
    System.Windows.Documents.Listが予備されるのがおかしいので、
    上記の不要なUsingがあって
    System.Collections.Generic.List
    に対するUsingが足りないのではないかと推測します。

    返信削除
  4. ファイルの追加は、ソリューションエクスプローラで
    右クリックして→追加→クラス
    が早いかなと思います。

    返信削除
  5. しのさんが指摘しているforeachのエラーは、Listの部分を適宜Listに変更することで解決しました。
    「今回のソースコード」の方ではそのようになっているようですが、このページではそのようにはなっていないようです。

    あと少し気になったのはSystem.ServiceModel.Webが参照の追加に出なかったことですね。
    私はVisualStudio2010を使っているのですが、デフォルトでは.NET がClient Profileになっていたのでそこを変更する必要があるようです。

    返信削除
  6. Status.csではset; get;の順になっているのに対して、
    User.csではget; set;の順になっているのは何か理由があるのでしょうか?

    返信削除
  7. setとgetの順番はまったく関係ないです。
    動作には影響しません。
    単に統一していないだけです。

    Client Profileの件は本文に注釈をつけました。
    ありがとうございました。

    返信削除
  8. あ、訂正です
    誤)Listの部分を適宜Listに変更することで解決しました。
    正)Listの部分を適宜List<Status>に変更することで解決しました。

    返信削除
  9. ありがとうございます。
    <>で囲まれている部分は、blogに貼り付けるツールを通した段階で
    消えちゃったのかもしれません。。

    返信削除