I am trying to deserialize JSON returned by the Yahoo! fantasy API, using this resource:
http://fantasysports.yahooapis.com/fantasy/v2/game/nfl/players;start=0;count=1?format=json
RestSharp is throwing the following exception when attempting to deserialize the Player
object (which is an array of arrays):
Unable to cast object of type 'RestSharp.JsonArray' to type 'System.Collections.Generic.IDictionary`2[System.String,System.Object]'
If I exclude the Player
property from the PlayerRoot
class, the deserialization is successful (but of course lacking most of the useful information).
I'm using a hand-coded object model; the Paste Special-->Paste JSON as Classes
action in Visual Studio does not work for the JSON shown below, but it does appear to be valid JSON.
Is there something wrong with my object model? My assumption is that something about the player
element (the nested array) is blowing up the RestSharp JSON deserializer.
Client code
public void GetPlayers()
{
int start = 0;
int count = 1;
var request =
new RestRequest("game/{gameType}/players;start={start};count={count}",
Method.GET);
request.AddUrlSegment("gameType", _gameType);
request.AddUrlSegment("start", start.ToString());
request.AddUrlSegment("count", count.ToString());
// This extension adds "?format=json" to the query string
request.AddJsonParam();
var response = _client.Execute<FantasyModel>(request);
var data = (FantasyModel)response.Data;
}
Model
public class FantasyModel
{
public FantasyContent FantasyContent { get; set; }
}
public class FantasyContent
{
[DeserializeAs(Name = "xml:lang")]
public string Language { get; set; }
[DeserializeAs(Name = "yahoo:uri")]
public string YahooUri { get; set; }
public string Time { get; set; }
public string Copyright { get; set; }
public string RefreshRate { get; set; }
public List<Game> Game { get; set; }
}
public class Game
{
public string GameKey { get; set; }
public string GameId { get; set; }
public string Name { get; set; }
public string Code { get; set; }
public string Type { get; set; }
public string Url { get; set; }
public string Season { get; set; }
public StatCategories StatCategories { get; set; }
public Players Players { get; set; }
}
public class Players
{
[DeserializeAs(Name = "0")]
public PlayerRoot PlayerRoot { get; set; }
public int Count { get; set; }
}
public class PlayerRoot
{
public PlayerRoot()
{
Player = new List<List<Player>>();
}
public List<List<Player>> Player { get; set; }
}
public class Player
{
public Player()
{
Name = new PlayerName();
ByeWeeks = new ByeWeeks();
Headshot = new Headshot();
EligiblePositions = new List<EligiblePosition>();
}
public string PlayerKey { get; set; }
public string PlayerId { get; set; }
public PlayerName Name { get; set; }
public string EditorialPlayerKey { get; set; }
public string EditorialTeamKey { get; set; }
public string EditorialTeamFullName { get; set; }
public string EditorialTeamAbbr { get; set; }
public ByeWeeks ByeWeeks { get; set; }
public string UniformNumber { get; set; }
public string DisplayPosition { get; set; }
public Headshot Headshot { get; set; }
public string ImageUrl { get; set; }
public string IsUndroppable { get; set; }
public string PositionType { get; set; }
public List<EligiblePosition> EligiblePositions { get; set; }
}
public class PlayerName
{
public string Full { get; set; }
public string First { get; set; }
public string Last { get; set; }
public string AsciiFirst { get; set; }
public string AsciiLast { get; set; }
}
public class ByeWeeks
{
public string Week { get; set; }
}
public class Headshot
{
public string Url { get; set; }
public string Size { get; set; }
}
public class EligiblePosition
{
public string Position { get; set; }
}
JSON
{
"fantasy_content": {
"xml:lang": "en-US",
"yahoo:uri": "\/fantasy\/v2\/game\/nfl\/players;count=1;start=0",
"game": [
{
"game_key": "331",
"game_id": "331",
"name": "Football",
"code": "nfl",
"type": "full",
"url": "http:\/\/football.fantasysports.yahoo.com\/f1",
"season": "2014"
},
{
"players": {
"0": {
"player": [
[
{
"player_key": "331.p.8850"
},
{
"player_id": "8850"
},
{
"name": {
"full": "Jamaal Charles",
"first": "Jamaal",
"last": "Charles",
"ascii_first": "Jamaal",
"ascii_last": "Charles"
}
},
{
"editorial_player_key": "nfl.p.8850"
},
{
"editorial_team_key": "nfl.t.12"
},
{
"editorial_team_full_name": "Kansas City Chiefs"
},
{
"editorial_team_abbr": "KC"
},
{
"bye_weeks": {
"week": "6"
}
},
{
"uniform_number": "25"
},
{
"display_position": "RB"
},
{
"headshot": {
"url": "http:\/\/l.yimg.com\/iu\/api\/res\/1.2\/084uD0cG9qCYdPSjJLX9.A--\/YXBwaWQ9eXZpZGVvO2NoPTg2MDtjcj0xO2N3PTY1OTtkeD0xO2R5PTE7Zmk9dWxjcm9wO2g9NjA7cT0xMDA7dz00Ng--\/http:\/\/l.yimg.com\/j\/assets\/i\/us\/sp\/v\/nfl\/players_l\/20120913\/8850.jpg",
"size": "small"
},
"image_url": "http:\/\/l.yimg.com\/iu\/api\/res\/1.2\/084uD0cG9qCYdPSjJLX9.A--\/YXBwaWQ9eXZpZGVvO2NoPTg2MDtjcj0xO2N3PTY1OTtkeD0xO2R5PTE7Zmk9dWxjcm9wO2g9NjA7cT0xMDA7dz00Ng--\/http:\/\/l.yimg.com\/j\/assets\/i\/us\/sp\/v\/nfl\/players_l\/20120913\/8850.jpg"
},
{
"is_undroppable": "0"
},
{
"position_type": "O"
},
{
"eligible_positions": [
{
"position": "RB"
}
]
},
[ ],
[ ]
]
]
},
"count": 1
}
}
],
"time": "215.82293510437ms",
"copyright": "Data provided by Yahoo! and STATS, LLC",
"refresh_rate": "60"
}
}
One note: the "0"
object in the JSON above would be repeated n
times, game/{gameType}/players;start={start};count={count}
, where n = {count}
. The resulting JSON would look something like:
{
"fantasy_content": {
...
"players": {
"0": { }
"1": { }
"2": { }
...
For the purposes of this question, I'm only bringing back one object ({count} = 1
).
Edit
Upon further review, the player
object should be List<List<object>>
or object[][]
, as each attribute of a player is its own element within the array.
I'm still not sure how to deserialize the thing using the RestSharp
deserializer, but I am able to get most of what I need by using Json.NET
.
I was not able to deserialize this JSON using RestSharp's built in deserializer. I think that Yahoo is returning some fairly strange JSON here (the players
object is essentially a dictionary of a list of lists of objects), and that strangeness is making automatic deserialization difficult.
I solved this using Json.NET
and a CustomCreationConverter
. I do not particularly like this solution, and I think there's a better way to do it, but on a tight deadline this is what I came up with (error handling/logging/etc. removed for brevity):
New Client Code
var request =
new RestRequest("game/{gameType}/players;start={start};count={count}", Method.GET);
request.AddUrlSegment("gameType", _gameType);
request.AddUrlSegment("start", start.ToString());
request.AddUrlSegment("count", count.ToString());
request.AddJsonParam();
var response = _client.Execute(request);
var json = JObject.Parse(response.Content);
var playersJson = json["fantasy_content"]["game"][1]["players"];
// Remove the count element
playersJson.Last.Remove();
var players = JsonConvert.DeserializeObject<Dictionary<string, Player>>(
playersJson.ToString(), new JsonPlayerConverter());
New Player class
public class Player
{
public Player()
{
Name = new PlayerName();
ByeWeeks = new ByeWeeks();
Headshot = new Headshot();
EligiblePositions = new List<EligiblePosition>();
}
[JsonProperty(PropertyName = "player_key")]
public string PlayerKey { get; set; }
[JsonProperty(PropertyName = "player_id")]
public string PlayerId { get; set; }
[JsonProperty(PropertyName = "name")]
public PlayerName Name { get; set; }
[JsonProperty(PropertyName = "editorial_player_key")]
public string EditorialPlayerKey { get; set; }
[JsonProperty(PropertyName = "editorial_team_key")]
public string EditorialTeamKey { get; set; }
[JsonProperty(PropertyName = "editorial_team_full_name")]
public string EditorialTeamFullName { get; set; }
[JsonProperty(PropertyName = "editorial_team_abbr")]
public string EditorialTeamAbbr { get; set; }
[JsonProperty(PropertyName = "bye_weeks")]
public ByeWeeks ByeWeeks { get; set; }
[JsonProperty(PropertyName = "uniform_number")]
public string UniformNumber { get; set; }
[JsonProperty(PropertyName = "display_position")]
public string DisplayPosition { get; set; }
[JsonProperty(PropertyName = "headshot")]
public Headshot Headshot { get; set; }
[JsonProperty(PropertyName = "image_url")]
public string ImageUrl { get; set; }
[JsonProperty(PropertyName = "is_undroppable")]
public string IsUndroppable { get; set; }
[JsonProperty(PropertyName = "position_type")]
public string PositionType { get; set; }
[JsonProperty(PropertyName = "eligible_positions")]
public List<EligiblePosition> EligiblePositions { get; set; }
}
CustomCreationConverter
public class JsonPlayerConverter : CustomCreationConverter<Player>
{
public override Player Create(Type objectType)
{
throw new NotImplementedException();
}
public Player Create(Type objectType, JObject obj)
{
var array = obj["player"][0];
var content = array.Children<JObject>();
var player = new Player();
foreach (var prop in player.GetType().GetProperties())
{
var attr = prop.GetCustomAttributes(typeof(JsonPropertyAttribute), false).FirstOrDefault();
var propName = ((JsonPropertyAttribute)attr).PropertyName;
var jsonElement = content.FirstOrDefault(c => c.Properties()
.Any(p => p.Name == propName));
var value = jsonElement.GetValue(propName);
var type = prop.PropertyType;
if (type == typeof(string))
{
prop.SetValue(player, (string)value, null);
}
else if (type == typeof(PlayerName))
{
var playerName = JsonConvert.DeserializeObject<PlayerName>(value.ToString());
prop.SetValue(player, (PlayerName)playerName, null);
}
else if (type == typeof(Headshot))
{
var headshot = JsonConvert.DeserializeObject<Headshot>(value.ToString());
prop.SetValue(player, headshot, null);
}
else if (type == typeof(ByeWeeks))
{
var byeWeeks = JsonConvert.DeserializeObject<ByeWeeks>(value.ToString());
prop.SetValue(player, byeWeeks, null);
}
else if (type == typeof(List<EligiblePosition>))
{
var eligiblePositions = JsonConvert.DeserializeObject<List<EligiblePosition>>(value.ToString());
prop.SetValue(player, eligiblePositions, null);
}
}
return player;
}
public override object ReadJson(Newtonsoft.Json.JsonReader reader, Type objectType, object existingValue, Newtonsoft.Json.JsonSerializer serializer)
{
var obj = JObject.Load(reader);
var target = Create(objectType, obj);
serializer.Populate(obj.CreateReader(), target);
return target;
}
}