首页 > 解决方案 > 如何使用来自 Yahoo Api 的索引解析 json 数组

问题描述

鉴于来自 Yahoo API 的此响应,我正在尝试使用 Newtonsoft.Json 和 ASP.NET / C# 解析播放器。我还想检查最后的值count

{
  "fantasy_content": {
    "xml:lang": "en-US",
    "yahoo:uri": "/fantasy/v2/game/nfl/players",
    "game": [
      {
        "game_key": "390",
        "game_id": "390",
        "name": "Football",
        "code": "nfl",
        "type": "full",
        "url": "https://football.fantasysports.yahoo.com/f1",
        "season": "2019",
        "is_registration_over": 0,
        "is_game_over": 0,
        "is_offseason": 0
      },
      {
        "players": {
          "0": {
            "player": [
              [
                {
                  "player_key": "390.p.30972"
                },
                {
                  "player_id": "30972"
                },
                {
                  "name": {
                    "full": "Saquon Barkley",
                    "first": "Saquon",
                    "last": "Barkley",
                    "ascii_first": "Saquon",
                    "ascii_last": "Barkley"
                  }
                },
                {
                  "editorial_player_key": "nfl.p.30972"
                },
                {
                  "editorial_team_key": "nfl.t.19"
                },
                {
                  "editorial_team_full_name": "New York Giants"
                },
                {
                  "editorial_team_abbr": "NYG"
                },
                {
                  "bye_weeks": {
                    "week": "11"
                  }
                },
                {
                  "uniform_number": "26"
                },
                {
                  "display_position": "RB"
                },
                {
                  "headshot": {
                    "url": "https://s.yimg.com/iu/api/res/1.2/RtPm7fdFHthz1._DrpkAqA--~C/YXBwaWQ9eXNwb3J0cztjaD0yMzM2O2NyPTE7Y3c9MTc5MDtkeD04NTc7ZHk9MDtmaT11bGNyb3A7aD02MDtxPTEwMDt3PTQ2/https://s.yimg.com/xe/i/us/sp/v/nfl_cutout/players_l/09242018/30972.png",
                    "size": "small"
                  },
                  "image_url": "https://s.yimg.com/iu/api/res/1.2/RtPm7fdFHthz1._DrpkAqA--~C/YXBwaWQ9eXNwb3J0cztjaD0yMzM2O2NyPTE7Y3c9MTc5MDtkeD04NTc7ZHk9MDtmaT11bGNyb3A7aD02MDtxPTEwMDt3PTQ2/https://s.yimg.com/xe/i/us/sp/v/nfl_cutout/players_l/09242018/30972.png"
                },
                {
                  "is_undroppable": "0"
                },
                {
                  "position_type": "O"
                },
                [],
                {
                  "eligible_positions": [
                    {
                      "position": "RB"
                    }
                  ]
                },
                [],
                [],
                []
              ]
            ]
          },
          "1": {
            "player": [
              [
                {
                  "player_key": "390.p.29238"
                },
                {
                  "player_id": "29238"
                },
                {
                  "name": {
                    "full": "Ezekiel Elliott",
                    "first": "Ezekiel",
                    "last": "Elliott",
                    "ascii_first": "Ezekiel",
                    "ascii_last": "Elliott"
                  }
                },
                {
                  "editorial_player_key": "nfl.p.29238"
                },
                {
                  "editorial_team_key": "nfl.t.6"
                },
                {
                  "editorial_team_full_name": "Dallas Cowboys"
                },
                {
                  "editorial_team_abbr": "Dal"
                },
                {
                  "bye_weeks": {
                    "week": "8"
                  }
                },
                {
                  "uniform_number": "21"
                },
                {
                  "display_position": "RB"
                },
                {
                  "headshot": {
                    "url": "https://s.yimg.com/iu/api/res/1.2/.0ocryeNkGmnFWlYOhT4hw--~C/YXBwaWQ9eXNwb3J0cztjaD0yMzM2O2NyPTE7Y3c9MTc5MDtkeD04NTc7ZHk9MDtmaT11bGNyb3A7aD02MDtxPTEwMDt3PTQ2/https://s.yimg.com/xe/i/us/sp/v/nfl_cutout/players_l/09272018/29238.png",
                    "size": "small"
                  },
                  "image_url": "https://s.yimg.com/iu/api/res/1.2/.0ocryeNkGmnFWlYOhT4hw--~C/YXBwaWQ9eXNwb3J0cztjaD0yMzM2O2NyPTE7Y3c9MTc5MDtkeD04NTc7ZHk9MDtmaT11bGNyb3A7aD02MDtxPTEwMDt3PTQ2/https://s.yimg.com/xe/i/us/sp/v/nfl_cutout/players_l/09272018/29238.png"
                },
                {
                  "is_undroppable": "0"
                },
                {
                  "position_type": "O"
                },
                [],
                {
                  "eligible_positions": [
                    {
                      "position": "RB"
                    }
                  ]
                },
                [],
                [],
                []
              ]
            ]
          },
          "2": {
            "player": [
              [
                {
                  "player_key": "390.p.30180"
                },
                {
                  "player_id": "30180"
                },
                {
                  "name": {
                    "full": "Alvin Kamara",
                    "first": "Alvin",
                    "last": "Kamara",
                    "ascii_first": "Alvin",
                    "ascii_last": "Kamara"
                  }
                },
                {
                  "editorial_player_key": "nfl.p.30180"
                },
                {
                  "editorial_team_key": "nfl.t.18"
                },
                {
                  "editorial_team_full_name": "New Orleans Saints"
                },
                {
                  "editorial_team_abbr": "NO"
                },
                {
                  "bye_weeks": {
                    "week": "9"
                  }
                },
                {
                  "uniform_number": "41"
                },
                {
                  "display_position": "RB"
                },
                {
                  "headshot": {
                    "url": "https://s.yimg.com/iu/api/res/1.2/loANJKjPdmUu1gM1jyKK1A--~C/YXBwaWQ9eXNwb3J0cztjaD0yMzM2O2NyPTE7Y3c9MTc5MDtkeD04NTc7ZHk9MDtmaT11bGNyb3A7aD02MDtxPTEwMDt3PTQ2/https://s.yimg.com/xe/i/us/sp/v/nfl_cutout/players_l/08252018/30180.png",
                    "size": "small"
                  },
                  "image_url": "https://s.yimg.com/iu/api/res/1.2/loANJKjPdmUu1gM1jyKK1A--~C/YXBwaWQ9eXNwb3J0cztjaD0yMzM2O2NyPTE7Y3c9MTc5MDtkeD04NTc7ZHk9MDtmaT11bGNyb3A7aD02MDtxPTEwMDt3PTQ2/https://s.yimg.com/xe/i/us/sp/v/nfl_cutout/players_l/08252018/30180.png"
                },
                {
                  "is_undroppable": "0"
                },
                {
                  "position_type": "O"
                },
                [],
                {
                  "eligible_positions": [
                    {
                      "position": "RB"
                    }
                  ]
                },
                [],
                [],
                []
              ]
            ]
          },
          "count": 3
        }
      }
    ],
    "time": "181.84494972229ms",
    "copyright": "Data provided by Yahoo! and STATS, LLC",
    "refresh_rate": "60"
  }
}

有两件事让我失望。

  1. game是一个看起来很奇怪的数组,并且
  2. 每个玩家面前都有一个索引,比如

    0: { player: { //dah dah dah }

如果索引不存在,我可能会使用以下方法来计算它:

var jObject = JObject.Parse(await response.Content.ReadAsStringAsync());
if (jObject.ContainsKey("players"))
{
    var yPs = jObject["players"].ToObject<YahooPlayerListJson>();
    yPlayerList.AddRange(yPs.YPlayers);
    if(yPs.Count < 25) { f = 5000; }
}

但索引给我带来了困难。如何解析这个 JSON?

标签: c#jsonjson.net

解决方案


呃,这是一个非常糟糕的 JSON 格式。异构阵列使其特别难以使用。在我看来,这些数据是(严重)从 XML 转换而来的。如果是这样,直接使用 XML 可能更直接。无论如何,我们可以让它工作。这是我将采取的方法。

  1. 制作一些类来捕获您感兴趣的玩家数据:

    public class Player
    {
        public string PlayerKey { get; set; }
        public string PlayerId { get; set; }
        public Name Name { get; set; }
        public string EditorialPlayerKey { get; set; }
        public string EditorialTeamKey { get; set; }
        public string EditorialTeamFullName { get; set; }
        public string EditorialTeamAbbr { get; set; }
        public Week ByeWeeks { get; set; }
        public string UniformNumber { get; set; }
        public string DisplayPosition { get; set; }
        public Photo Headshot { get; set; }
        public string ImageUrl { get; set; }
        public string IsUndroppable { get; set; }
        public string PositionType { get; set; }
        public Position[] EligiblePositions { get; set; }
    }
    
    public class Name
    {
        public string Full { get; set; }
        public string First { get; set; }
        public string Last { get; set; }
    }
    
    public class Photo
    {
        public string Url { get; set; }
        public string Size { get; set; }
    }
    
    public class Week
    {
        [JsonProperty("week")]
        public int Number { get; set; }
    }
    
    public class Position
    {
        [JsonProperty("position")]
        public string Name { get; set; }
    }
    
  2. 设置一个配置了SnakeCaseNamingStrategy. 这将处理snake_caseJSON 中的ProperCase属性名称到 C# 类中的属性名称的转换。

    JsonSerializer serializer = new JsonSerializer
    {
        ContractResolver = new DefaultContractResolver
        {
            NamingStrategy = new SnakeCaseNamingStrategy()
        }
    };
    
  3. 将 JSON 解析为JObject.

    string json = await response.Content.ReadAsStringAsync();
    JObject root = JObject.Parse(json);
    
  4. 从那里使用LINQ-to-JSON查询来填充Players如下所示的列表。

    在这里,我使用SelectTokens()递归下降JsonPath表达式作为向下钻取player对象的快捷方式。JSON 中的每个播放器实际上是一个包含混合内容数组的数组,其中内容由一些对象组成,每个对象包含一个不同的属性,以及一些无用的空数组。因此,我将内部数组过滤为仅包含真实数据的对象,然后将SelectMany()它们的所有属性收集到一个 flatEnumerable<JProperty>中,然后将其放入一个临时JObject. 从那里我使用ToObject()之前配置的序列化程序Player从 中创建一个新实例JObject并返回,Player以便将其放入结果列表中。

    List<Player> players =
        root.SelectTokens("$..player")
            .Select(jt =>
            {
                JObject tempObj = new JObject(
                    jt.Children<JArray>()
                      .First()
                      .Children<JObject>()
                      .SelectMany(jo => jo.Properties())
                );
                return tempObj.ToObject<Player>(serializer);
            })
            .ToList();
    
  5. 如果您想count从 JSON 中获取 并将其与检索到的实际玩家数量进行比较,您可以这样做:

    int playerCount = (int)root.SelectToken("$..players.count");
    
    if (players.Count != playerCount)
        throw new Exception("The number of players in the list does not match the player count!");
    

这是一个工作演示:https ://dotnetfiddle.net/8KYNNn


推荐阅读