Branches | New JSON - a4lg/CoreTweet GitHub Wiki

New JSON Branch (破棄)

敗因

DataContractJsonSerializer は最新バージョンにおいて長いフォーマットと短いフォーマットの辞書シリアライズフォーマットがあり、Twitter のフォーマットと同じ短いフォーマットでシリアライズできるのは .NET Framework 4.5 以降 (DataContractJsonSerializerSettings クラスを持つ) だということが判明。Mono 2.10 の同クラスは .NET 3.5 準拠なので、それが新しいバージョンに移行する頃には JSON.NET の問題の原因だった BigInteger.Parse も実装されている。つまり、わざわざ移行する必要がない (げんなり)。

以下は実装中メモの墓場。

概要

New JSON branch では本家 CoreTweet で使われている JSON パーサー (deserializer) を JSON.NET (Newtonking.Json) から .NET Framework 3.5 以降で標準の System.Runtime.Serialization.Json.DataContractJsonSerializer に変更することを目論んでいます。

移行したくなる理由

  • .NET 3.5 以降で標準機能となっている
  • JSON.NET の直観的でない機能がバグの少ないデシリアライズを阻害してしまっている
  • 本家 CoreTweet がサポートしている Mono において、JSON.NET の新しいバージョンが動作しない (活発にメンテナンスされているプロジェクトが活発にメンテナンスされていないコードに依存してしまっているのが現状)

移行したくならない理由

  • JSON.NET のページによれば、DataContractJsonSerializer は若干遅いらしい
  • パフォーマンスを改善しようとすると内部 API の多くをかなり変更する必要がある (外部的にはあまり関係ないが、内部的には互換性が損なわれる)
  • JSON.NET や .NET Framework 4.5 以降 (と Silverlight) で 標準となった System.Json と比べると JSON そのものを柔軟に扱う機能に乏しい (そのため、小さなラッパークラスに頼る必要がある)

進捗

  • CoreTweet から抜き出したクラス類を書き換えたものを単体テストする : 扱いが複雑な User Stream 除いて成功
  • CoreTweet 全体の書き換え : インタフェース類の大部分の書き換えが必要 (進行中)
  • GitHub でのブランチ作成 : 書き換えた CoreTweet 全体のコンパイルが (クオリティはともかく) 成功するくらい進行したら (未到達)

変更予定 (ないし変更中)

全体

  • Stream → string → object のデシリアライズフローを、Stream → object に単純化する
    DataContractJsonDeserializer は Stream を直接受け付け、一時オブジェクトとしての string を必要としない。これは非同期性能の向上やメモリ効率向上に関する潜在的な好影響がある。ただ簡単のために (あるいはテストのために)、string → Stream → object のデシリアライズフローを提供する内部 API は追加しても良いかもしれない。
  • JSON path 関連をラッパークラスを利用するように変更
    これは DataContractJsonDeserializer が生の JSON path を扱えない仕様への対処 (改善というよりは workaround)。ただこの変更に伴って構築しなくて済むオブジェクトがあるため、比較してオブジェクト効率が悪くなるとは言えない。

ITwitterResponse 関連の変更

  • リクエスト関連の内部 API において ITwitterResponse を強制
    ITwitterResponse にレスポンス共通のインタフェースをまとめる仮定をつけるとコードがかなり整理できる。そのため、リクエスト関連の API において ITwitterResponse を強制する。これにより上述のラッパークラスもうまく活用できるようになる。
  • ITwitterResponse.Json メンバーの (一時的な) 削除
    string に変換してからではなく Stream のままデシリアライズするフローに変更される都合上、Json メンバー (レスポンスの生 JSON 文字列) はそのままの形では実現できない。その代わりとなるトレース用機能は検討中。

リクエスト関連の内部 API 変更

  • List と Dictionary を返す専用の API を削除
    ListedResponse と DictionaryResponse の機能は魅力だが、一方で JSON そのものを柔軟に扱う仕組みが無い都合上オブジェクトのラップが一部に必要となる。そのため、List や Dictionary を専用で返す API は削除し、汎用 API のテンプレートパラメータに ListedResponse, DictionaryResponse を与えることで当面の対処。これで見づらくなるようなら別案を考える。

問題 : Primitive type 形式の JSON との変換を行うラッパーの存在

DataContract の中で唯一移植可能な方法で JSON との相互変換ができないのが、JSON のプリミティブ型にマップされている型である。CoreTweet では DateTimeOffset が該当する (もしかすると Uri も該当するかもしれないが、現状問題が発生したことがない)。 このような型を .NET の型と結びつけるには、

  • private なシリアライズ用プロパティ (public なオブジェクトをシリアライズまたは逆するためのラッパー)
  • public なオブジェクトプロパティ

が必要となる。例えばこんな感じ。

static class DateTimeOffsetSurrogate
{
	public static string GetSurrogateObject(DateTimeOffset offset)
	{
		throw new NotImplementedException();
	}
	public static DateTimeOffset GetRealObject(string data)
	{
		{
			long epoch;
			if (long.TryParse(data, out epoch))
				return new DateTimeOffset(1970, 1, 1, 0, 0, 0, 0, TimeSpan.Zero).AddSeconds((double)epoch);
		}
		throw new InvalidCastException();
	}
}

[DataContract]
class SampleData2
{
	[DataMember(Name = "created_at")]
	private string CreatedAtSurrogate
	{
		get { return DateTimeOffsetSurrogate.GetSurrogateObject(CreatedAt); }
		set { CreatedAt = DateTimeOffsetSurrogate.GetRealObject(value); }
	}
	public DateTimeOffset CreatedAt { get; set; }
}

このような種類のプロパティ (といっても多分 DateTimeOffset くらい?) あたり 5 行追加されるのは、個人的には許容範囲とはいえ多少ウザい。ちなみに移植性を考えずに ".NET Framework" (Windows ストアアプリを除く!) だけで良いのなら、IDataContractSurrogate というインタフェースを実装してそれを使えば良い。これなら DateTimeOffset とそれをラップするオブジェクトを簡単に書き換えることができる。