Twitter の RateLimit Json を ServiceStack.Text で逆シリアル化しようとしています。JSON がプルダウンされるように、適切な DTO オブジェクトを作成しました (投稿の下部を参照)。System.Runtime.Serialization を使用して、フィールドの名前をよりわかりやすい名前に変更しました (これは、Twitter の他の多くの DTO オブジェクトで使用しています)。オブジェクトを逆シリアル化すると、RateLimitContext は適切に逆シリアル化されますが、Resources の下のすべてのアイテムは null に逆シリアル化されます。この特定のオブジェクトの DataMemberNames にはいくつかの「特殊な」文字が含まれているため、何か問題があると思われます。これは ServiceStack.Text のバグですか、それとも DTO に何か不足していますか?
[DataContract]
public class RateLimitResult
{
[DataMember(Name = "rate_limit_context")]
public RateLimitContextEntry RateLimitContext { get; set; }
[DataContract]
public class RateLimitContextEntry
{
[DataMember(Name = "access_token")]
public string AccessToken { get; set; }
}
[DataMember(Name = "resources")]
public ResourcesEntry Resources { get; set; }
[DataContract]
public class RateLimitEntry
{
[DataMember(Name = "remaining")]
public int Remaining { get; set; }
[DataMember(Name = "reset")]
public long Reset { get; set; }
[DataMember(Name = "limit")]
public int Limit { get; set; }
}
[DataContract]
public class HelpEntry
{
[DataMember(Name = "/help/privacy")]
public RateLimitEntry Privacy { get; set; }
[DataMember(Name = "/help/configuration")]
public RateLimitEntry Configuration { get; set; }
[DataMember(Name = "/help/tos")]
public RateLimitEntry Tos { get; set; }
[DataMember(Name = "/help/languages")]
public RateLimitEntry Languages { get; set; }
}
[DataContract]
public class StatusesEntry
{
[DataMember(Name = "/statuses/oembed")]
public RateLimitEntry Oembed { get; set; }
[DataMember(Name = "/statuses/user_timeline")]
public RateLimitEntry UserTimeline { get; set; }
[DataMember(Name = "/statuses/mentions_timeline")]
public RateLimitEntry MentionsTimeline { get; set; }
[DataMember(Name = "/statuses/home_timeline")]
public RateLimitEntry HomeTimeline { get; set; }
[DataMember(Name = "/statuses/show/:id")]
public RateLimitEntry ShowId { get; set; }
[DataMember(Name = "/statuses/retweets/:id")]
public RateLimitEntry RetweetsId { get; set; }
[DataMember(Name = "/statuses/retweets_of_me")]
public RateLimitEntry RetweetsOfMe { get; set; }
}
[DataContract]
public class UsersEntry
{
[DataMember(Name = "/users/search")]
public RateLimitEntry Search { get; set; }
[DataMember(Name = "/users/suggestions")]
public RateLimitEntry Suggestions { get; set; }
[DataMember(Name = "/users/contributors")]
public RateLimitEntry Contributors { get; set; }
[DataMember(Name = "/users/suggestions/:slug/members")]
public RateLimitEntry SuggestionsSlugMembers { get; set; }
[DataMember(Name = "/users/suggestions/:slug")]
public RateLimitEntry SuggestionsSlug { get; set; }
[DataMember(Name = "/users/contributees")]
public RateLimitEntry Contributees { get; set; }
[DataMember(Name = "/users/lookup")]
public RateLimitEntry Lookup { get; set; }
[DataMember(Name = "/users/profile_banner")]
public RateLimitEntry ProfileBanner { get; set; }
}
[DataContract]
public class SearchEntry
{
[DataMember(Name = "/search/tweets")]
public RateLimitEntry Tweets { get; set; }
}
[DataContract]
public class TrendsEntry
{
[DataMember(Name = "/trends/available")]
public RateLimitEntry Available { get; set; }
[DataMember(Name = "/trends/place")]
public RateLimitEntry Place { get; set; }
[DataMember(Name = "/trends/closest")]
public RateLimitEntry Closest { get; set; }
}
[DataContract]
public class SavedSearchesEntry
{
[DataMember(Name = "/saved_searches/destroy/:id")]
public RateLimitEntry DestroyId { get; set; }
[DataMember(Name = "/saved_searches/list")]
public RateLimitEntry List { get; set; }
[DataMember(Name = "/saved_searches/show/:id")]
public RateLimitEntry ShowId { get; set; }
}
[DataContract]
public class FavoritesEntry
{
[DataMember(Name = "/favorites/list")]
public RateLimitEntry List { get; set; }
}
[DataContract]
public class AccountEntry
{
[DataMember(Name = "/account/verify_credentials")]
public RateLimitEntry VerifyCredentials { get; set; }
[DataMember(Name = "/account/settings")]
public RateLimitEntry Settings { get; set; }
}
[DataContract]
public class DirectMessagesEntry
{
[DataMember(Name = "/direct_messages/show")]
public RateLimitEntry Show { get; set; }
[DataMember(Name = "/direct_messages/sent_and_received")]
public RateLimitEntry SentAndReceived { get; set; }
[DataMember(Name = "/direct_messages/sent")]
public RateLimitEntry Sent { get; set; }
[DataMember(Name = "/direct_messages")]
public RateLimitEntry Messages { get; set; }
}
[DataContract]
public class FriendsEntry
{
[DataMember(Name = "/friends/ids")]
public RateLimitEntry Ids { get; set; }
[DataMember(Name = "/friends/list")]
public RateLimitEntry List { get; set; }
}
[DataContract]
public class FollowersEntry
{
[DataMember(Name = "/followers/list")]
public RateLimitEntry List { get; set; }
[DataMember(Name = "/followers/ids")]
public RateLimitEntry Ids { get; set; }
}
[DataContract]
public class PromptsEntry
{
[DataMember(Name = "/prompts/record_event")]
public RateLimitEntry RecordEvent { get; set; }
[DataMember(Name = "/prompts/suggest")]
public RateLimitEntry Suggest { get; set; }
}
[DataContract]
public class GeoEntry
{
[DataMember(Name = "/geo/similar_places")]
public RateLimitEntry SimilarPlaces { get; set; }
[DataMember(Name = "/geo/search")]
public RateLimitEntry Search { get; set; }
[DataMember(Name = "/geo/reverse_geocode")]
public RateLimitEntry ReverseGeocode { get; set; }
[DataMember(Name = "/geo/id/:place_id")]
public RateLimitEntry PlaceId { get; set; }
}
[DataContract]
public class BlocksEntry
{
[DataMember(Name = "/blocks/ids")]
public RateLimitEntry Ids { get; set; }
[DataMember(Name = "/blocks/list")]
public RateLimitEntry List { get; set; }
}
[DataContract]
public class FriendshipsEntry
{
[DataMember(Name = "/friendships/incoming")]
public RateLimitEntry Incoming { get; set; }
[DataMember(Name = "/friendships/lookup")]
public RateLimitEntry Lookup { get; set; }
[DataMember(Name = "/friendships/outgoing")]
public RateLimitEntry Outgoing { get; set; }
[DataMember(Name = "/friendships/no_retweets/ids")]
public RateLimitEntry NoRetweetsIds { get; set; }
[DataMember(Name = "/friendships/show")]
public RateLimitEntry Show { get; set; }
}
[DataContract]
public class ApplicationEntry
{
[DataMember(Name = "/application/rate_limit_status")]
public RateLimitEntry RateLimitStatus { get; set; }
}
[DataContract]
public class ListsEntry
{
[DataMember(Name = "/lists/subscribers")]
public RateLimitEntry Subscribers { get; set; }
[DataMember(Name = "/lists/list")]
public RateLimitEntry List { get; set; }
[DataMember(Name = "/lists/memberships")]
public RateLimitEntry Memberships { get; set; }
[DataMember(Name = "/lists/ownerships")]
public RateLimitEntry Ownerships { get; set; }
[DataMember(Name = "/lists/subscriptions")]
public RateLimitEntry Subscriptions { get; set; }
[DataMember(Name = "/lists/members")]
public RateLimitEntry Members { get; set; }
[DataMember(Name = "/lists/subscribers/show")]
public RateLimitEntry ShowSubscribers { get; set; }
[DataMember(Name = "/lists/statuses")]
public RateLimitEntry Statuses { get; set; }
[DataMember(Name = "/lists/members/show")]
public RateLimitEntry ShowMembers { get; set; }
[DataMember(Name = "/lists/show")]
public RateLimitEntry Show { get; set; }
}
[DataContract]
public class ResourcesEntry
{
[DataMember(Name = "lists")]
public ListsEntry Lists { get; set; }
[DataMember(Name = "application")]
public ApplicationEntry Application { get; set; }
[DataMember(Name = "friendships")]
public FriendshipsEntry Friendships { get; set; }
[DataMember(Name = "blocks")]
public BlocksEntry Blocks { get; set; }
[DataMember(Name = "geo")]
public GeoEntry Geo { get; set; }
[DataMember(Name = "users")]
public UsersEntry Users { get; set; }
[DataMember(Name = "prompts")]
public PromptsEntry Prompts { get; set; }
[DataMember(Name = "followers")]
public FollowersEntry Followers { get; set; }
[DataMember(Name = "statuses")]
public StatusesEntry Statuses { get; set; }
[DataMember(Name = "help")]
public HelpEntry Help { get; set; }
[DataMember(Name = "friends")]
public FriendsEntry Friends { get; set; }
[DataMember(Name = "direct_messages")]
public DirectMessagesEntry DirectMessages { get; set; }
[DataMember(Name = "account")]
public AccountEntry Account { get; set; }
[DataMember(Name = "favorites")]
public FavoritesEntry Favorites { get; set; }
[DataMember(Name = "saved_searches")]
public SavedSearchesEntry SavedSearches { get; set; }
[DataMember(Name = "search")]
public SearchEntry Search { get; set; }
[DataMember(Name = "trends")]
public TrendsEntry Trends { get; set; }
}
}
API によって返されるサンプル Json を次に示します。
{
"rate_limit_context": {
"access_token": "ajksldjalsda-akjflkasjfltpowfaalksjdfAPQPIJFaflkjaq"
},
"resources": {
"lists": {
"/lists/subscribers": {
"limit": 180,
"remaining": 180,
"reset": 1380314146
},
"/lists/list": {
"limit": 15,
"remaining": 15,
"reset": 1380314146
},
"/lists/memberships": {
"limit": 15,
"remaining": 15,
"reset": 1380314146
},
"/lists/ownerships": {
"limit": 15,
"remaining": 15,
"reset": 1380314146
},
"/lists/subscriptions": {
"limit": 15,
"remaining": 15,
"reset": 1380314146
},
"/lists/members": {
"limit": 180,
"remaining": 180,
"reset": 1380314146
},
"/lists/subscribers/show": {
"limit": 15,
"remaining": 15,
"reset": 1380314146
},
"/lists/statuses": {
"limit": 180,
"remaining": 180,
"reset": 1380314146
},
"/lists/members/show": {
"limit": 15,
"remaining": 15,
"reset": 1380314146
},
"/lists/show": {
"limit": 15,
"remaining": 15,
"reset": 1380314146
}
},
"application": {
"/application/rate_limit_status": {
"limit": 180,
"remaining": 178,
"reset": 1380314022
}
},
"friendships": {
"/friendships/incoming": {
"limit": 15,
"remaining": 15,
"reset": 1380314146
},
"/friendships/lookup": {
"limit": 15,
"remaining": 15,
"reset": 1380314146
},
"/friendships/outgoing": {
"limit": 15,
"remaining": 15,
"reset": 1380314146
},
"/friendships/no_retweets/ids": {
"limit": 15,
"remaining": 15,
"reset": 1380314146
},
"/friendships/show": {
"limit": 180,
"remaining": 180,
"reset": 1380314146
}
},
"blocks": {
"/blocks/ids": {
"limit": 15,
"remaining": 15,
"reset": 1380314146
},
"/blocks/list": {
"limit": 15,
"remaining": 15,
"reset": 1380314146
}
},
"geo": {
"/geo/similar_places": {
"limit": 15,
"remaining": 15,
"reset": 1380314146
},
"/geo/search": {
"limit": 15,
"remaining": 15,
"reset": 1380314146
},
"/geo/reverse_geocode": {
"limit": 15,
"remaining": 15,
"reset": 1380314146
},
"/geo/id/:place_id": {
"limit": 15,
"remaining": 15,
"reset": 1380314146
}
},
"users": {
"/users/profile_banner": {
"limit": 180,
"remaining": 180,
"reset": 1380314146
},
"/users/suggestions/:slug/members": {
"limit": 15,
"remaining": 15,
"reset": 1380314146
},
"/users/show/:id": {
"limit": 180,
"remaining": 180,
"reset": 1380314146
},
"/users/suggestions": {
"limit": 15,
"remaining": 15,
"reset": 1380314146
},
"/users/lookup": {
"limit": 180,
"remaining": 180,
"reset": 1380314146
},
"/users/search": {
"limit": 180,
"remaining": 180,
"reset": 1380314146
},
"/users/contributors": {
"limit": 15,
"remaining": 15,
"reset": 1380314146
},
"/users/contributees": {
"limit": 15,
"remaining": 15,
"reset": 1380314146
},
"/users/suggestions/:slug": {
"limit": 15,
"remaining": 15,
"reset": 1380314146
}
},
"prompts": {
"/prompts/record_event": {
"limit": 15,
"remaining": 15,
"reset": 1380314146
},
"/prompts/suggest": {
"limit": 15,
"remaining": 15,
"reset": 1380314146
}
},
"followers": {
"/followers/list": {
"limit": 15,
"remaining": 15,
"reset": 1380314146
},
"/followers/ids": {
"limit": 15,
"remaining": 15,
"reset": 1380314146
}
},
"statuses": {
"/statuses/mentions_timeline": {
"limit": 15,
"remaining": 15,
"reset": 1380314146
},
"/statuses/show/:id": {
"limit": 180,
"remaining": 180,
"reset": 1380314146
},
"/statuses/oembed": {
"limit": 180,
"remaining": 180,
"reset": 1380314146
},
"/statuses/retweeters/ids": {
"limit": 15,
"remaining": 15,
"reset": 1380314146
},
"/statuses/home_timeline": {
"limit": 15,
"remaining": 15,
"reset": 1380314146
},
"/statuses/user_timeline": {
"limit": 180,
"remaining": 180,
"reset": 1380314146
},
"/statuses/retweets/:id": {
"limit": 15,
"remaining": 15,
"reset": 1380314146
},
"/statuses/retweets_of_me": {
"limit": 15,
"remaining": 15,
"reset": 1380314146
}
},
"help": {
"/help/privacy": {
"limit": 15,
"remaining": 15,
"reset": 1380314146
},
"/help/tos": {
"limit": 15,
"remaining": 15,
"reset": 1380314146
},
"/help/configuration": {
"limit": 15,
"remaining": 15,
"reset": 1380314146
},
"/help/languages": {
"limit": 15,
"remaining": 15,
"reset": 1380314146
}
},
"friends": {
"/friends/ids": {
"limit": 15,
"remaining": 15,
"reset": 1380314146
},
"/friends/list": {
"limit": 15,
"remaining": 15,
"reset": 1380314146
}
},
"direct_messages": {
"/direct_messages/show": {
"limit": 15,
"remaining": 15,
"reset": 1380314146
},
"/direct_messages/sent_and_received": {
"limit": 15,
"remaining": 15,
"reset": 1380314146
},
"/direct_messages/sent": {
"limit": 15,
"remaining": 15,
"reset": 1380314146
},
"/direct_messages": {
"limit": 15,
"remaining": 15,
"reset": 1380314146
}
},
"account": {
"/account/verify_credentials": {
"limit": 15,
"remaining": 15,
"reset": 1380314146
},
"/account/settings": {
"limit": 15,
"remaining": 15,
"reset": 1380314146
}
},
"favorites": {
"/favorites/list": {
"limit": 15,
"remaining": 15,
"reset": 1380314146
}
},
"saved_searches": {
"/saved_searches/destroy/:id": {
"limit": 15,
"remaining": 15,
"reset": 1380314146
},
"/saved_searches/list": {
"limit": 15,
"remaining": 15,
"reset": 1380314146
},
"/saved_searches/show/:id": {
"limit": 15,
"remaining": 15,
"reset": 1380314146
}
},
"search": {
"/search/tweets": {
"limit": 180,
"remaining": 180,
"reset": 1380314146
}
},
"trends": {
"/trends/available": {
"limit": 15,
"remaining": 15,
"reset": 1380314146
},
"/trends/place": {
"limit": 15,
"remaining": 15,
"reset": 1380314146
},
"/trends/closest": {
"limit": 15,
"remaining": 15,
"reset": 1380314146
}
}
}
}