反序列化Yahoo! Fantasy API播放器JSON

时间:2014-07-21 18:35:44

标签: c# json restsharp yahoo-api

我正在尝试反序列化Yahoo!返回的JSON。 fantasy API,使用此资源:

  

http://fantasysports.yahooapis.com/fantasy/v2/game/nfl/players;start=0;count=1?format=json

在尝试反序列化Player对象(这是一个数组数组)时,RestSharp抛出以下异常:

  

无法投射类型' RestSharp.JsonArray'键入' System.Collections.Generic.IDictionary`2 [System.String,System.Object]'

如果我从Player类中排除PlayerRoot属性,则反序列化成功(但当然缺少大部分有用信息)。

我使用的是手工编码的对象模型; Visual Studio中的Paste Special-->Paste JSON as Classes操作不适用于下面显示的JSON,但它看起来似乎是有效的JSON。

我的对象模型是否存在错误的?我的假设是关于player元素(嵌套数组)的某些内容正在炸毁RestSharp JSON反序列化器。

客户代码

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;
}

模型

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"
    }
}

一个注意事项:上面JSON中的"0"对象将重复n次,game/{gameType}/players;start={start};count={count},其中n = {count}。生成的JSON看起来像:

{
    "fantasy_content": {
        ...
                "players": {
                    "0": { }
                    "1": { }
                    "2": { }
                    ...

出于这个问题的目的,我只带回一个对象({count} = 1)。

修改

进一步审核后,player对象应为List<List<object>>object[][],因为播放器的每个属性都是数组中自己的元素。

我仍然不确定如何使用RestSharp反序列化器反序列化该内容,但我可以使用Json.NET获得我需要的大部分内容。

1 个答案:

答案 0 :(得分:1)

我无法使用RestSharp内置的反序列化器反序列化此JSON。我认为雅虎在这里返回一些相当奇怪的JSON(players对象本质上是一个对象列表列表的字典),而这种奇怪之处在于使自动反序列化变得困难。

我使用Json.NETCustomCreationConverter解决了这个问题。我并不特别喜欢这个解决方案,我认为有更好的方法可以做到这一点,但是在紧迫的期限内,这就是我提出的(为简洁起见,错误处理/记录/删除等) :

新客户代码

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());

新玩家类

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;
    }
}