在Ruby中建模复杂的JSON

在Ruby中建模复杂的JSON,ruby,json,rubygems,Ruby,Json,Rubygems,我有一段JSON,它是NBA比赛的boxscore,如下例所示。是否有任何策略可以将这些数据转换成一个健全的Ruby模型(可能使用序列化器)?当我从远程API中提取这些数据时,这个JSON中有很多嵌套的数据,例如个人玩家统计、团队统计、团队数据等。最好使用sane Ruby建模工具来处理这个JSON。没关系,但我将尝试将此建模应用到ruby gem中,而不是rails应用程序中 下面是我正在处理的一个JSON blob示例: [ { "game_length": "2:38",

我有一段JSON,它是NBA比赛的boxscore,如下例所示。是否有任何策略可以将这些数据转换成一个健全的Ruby模型(可能使用序列化器)?当我从远程API中提取这些数据时,这个JSON中有很多嵌套的数据,例如个人玩家统计、团队统计、团队数据等。最好使用sane Ruby建模工具来处理这个JSON。没关系,但我将尝试将此建模应用到ruby gem中,而不是rails应用程序中

下面是我正在处理的一个JSON blob示例:

[
  {
    "game_length": "2:38",
    "location": "Sleep Train Arena",
    "game_time": "10:00 PM",
    "season_type": "Regular",
    "game_id": 19123,
    "scores_by_quarter": [
      {
        "scores": {
          "quarter": "1",
          "away_team_score": 23,
          "home_team_score": 24
        }
      },
      {
        "scores": {
          "quarter": "2",
          "away_team_score": 27,
          "home_team_score": 21
        }
      },
      {
        "scores": {
          "quarter": "3",
          "away_team_score": 23,
          "home_team_score": 27
        }
      },
      {
        "scores": {
          "quarter": "4",
          "away_team_score": 21,
          "home_team_score": 22
        }
      },
      {
        "scores": {
          "quarter": "OT",
          "away_team_score": 10,
          "home_team_score": 4
        }
      }
    ],
    "team_stats": [
      {
        "team": {
          "team_id": 101,
          "team_name": "LA Clippers",
          "listings": [
            {
              "listing": {
                "freethrows_attempted": 2,
                "freethrows_made": 0,
                "field_goals_attempted": 5,
                "turnovers": 1,
                "total_rebounds": 3,
                "blocks": 0,
                "steals": 1,
                "assists": 1,
                "field_goals_made": 2,
                "offensive_rebounds": 1,
                "player_first_name": "Jared",
                "three_points_made": 1,
                "player_last_name": "Dudley",
                "points": 5,
                "personal_fouls": 4,
                "three_points_attempted": 3,
                "minutes_played": 37,
                "technical_fouls": 0,
                "player_position": "F",
                "player_id": 1292,
                "started": "Yes",
                "seconds_played": 16,
                "defensive_rebounds": 2
              }
            },
            {
              "listing": {
                "freethrows_attempted": 10,
                "freethrows_made": 7,
                "field_goals_attempted": 15,
                "turnovers": 4,
                "total_rebounds": 12,
                "blocks": 0,
                "steals": 0,
                "assists": 1,
                "field_goals_made": 7,
                "offensive_rebounds": 2,
                "player_first_name": "Blake",
                "three_points_made": 0,
                "player_last_name": "Griffin",
                "points": 21,
                "personal_fouls": 4,
                "three_points_attempted": 0,
                "minutes_played": 38,
                "technical_fouls": 1,
                "player_position": "F",
                "player_id": 1590,
                "started": "Yes",
                "seconds_played": 57,
                "defensive_rebounds": 10
              }
            },
            {
              "listing": {
                "freethrows_attempted": 5,
                "freethrows_made": 0,
                "field_goals_attempted": 9,
                "turnovers": 1,
                "total_rebounds": 15,
                "blocks": 9,
                "steals": 0,
                "assists": 2,
                "field_goals_made": 5,
                "offensive_rebounds": 6,
                "player_first_name": "DeAndre",
                "three_points_made": 0,
                "player_last_name": "Jordan",
                "points": 10,
                "personal_fouls": 1,
                "three_points_attempted": 0,
                "minutes_played": 40,
                "technical_fouls": 0,
                "player_position": "C",
                "player_id": 1487,
                "started": "Yes",
                "seconds_played": 14,
                "defensive_rebounds": 9
              }
            },
            {
              "listing": {
                "freethrows_attempted": 4,
                "freethrows_made": 4,
                "field_goals_attempted": 9,
                "turnovers": 1,
                "total_rebounds": 1,
                "blocks": 0,
                "steals": 1,
                "assists": 2,
                "field_goals_made": 4,
                "offensive_rebounds": 0,
                "player_first_name": "J.J.",
                "three_points_made": 1,
                "player_last_name": "Redick",
                "points": 13,
                "personal_fouls": 2,
                "three_points_attempted": 4,
                "minutes_played": 16,
                "technical_fouls": 0,
                "player_position": "G",
                "player_id": 1129,
                "started": "Yes",
                "seconds_played": 59,
                "defensive_rebounds": 1
              }
            },
            {
              "listing": {
                "freethrows_attempted": 4,
                "freethrows_made": 4,
                "field_goals_attempted": 12,
                "turnovers": 1,
                "total_rebounds": 1,
                "blocks": 0,
                "steals": 1,
                "assists": 2,
                "field_goals_made": 5,
                "offensive_rebounds": 0,
                "player_first_name": "Darren",
                "three_points_made": 1,
                "player_last_name": "Collison",
                "points": 15,
                "personal_fouls": 1,
                "three_points_attempted": 2,
                "minutes_played": 40,
                "technical_fouls": 0,
                "player_position": "G",
                "player_id": 1578,
                "started": "Yes",
                "seconds_played": 7,
                "defensive_rebounds": 1
              }
            },
            {
              "listing": {
                "freethrows_attempted": 4,
                "freethrows_made": 4,
                "field_goals_attempted": 22,
                "turnovers": 2,
                "total_rebounds": 7,
                "blocks": 0,
                "steals": 0,
                "assists": 11,
                "field_goals_made": 12,
                "offensive_rebounds": 2,
                "player_first_name": "Jamal",
                "three_points_made": 3,
                "player_last_name": "Crawford",
                "points": 31,
                "personal_fouls": 1,
                "three_points_attempted": 7,
                "minutes_played": 37,
                "technical_fouls": 0,
                "player_position": "G",
                "player_id": 299,
                "started": "No",
                "seconds_played": 29,
                "defensive_rebounds": 5
              }
            },
            {
              "listing": {
                "freethrows_attempted": 4,
                "freethrows_made": 3,
                "field_goals_attempted": 5,
                "turnovers": 2,
                "total_rebounds": 5,
                "blocks": 0,
                "steals": 1,
                "assists": 1,
                "field_goals_made": 1,
                "offensive_rebounds": 1,
                "player_first_name": "Antawn",
                "three_points_made": 0,
                "player_last_name": "Jamison",
                "points": 5,
                "personal_fouls": 1,
                "three_points_attempted": 0,
                "minutes_played": 16,
                "technical_fouls": 0,
                "player_position": "F",
                "player_id": 682,
                "started": "No",
                "seconds_played": 37,
                "defensive_rebounds": 4
              }
            },
            {
              "listing": {
                "freethrows_attempted": 2,
                "freethrows_made": 1,
                "field_goals_attempted": 1,
                "turnovers": 0,
                "total_rebounds": 0,
                "blocks": 0,
                "steals": 0,
                "assists": 0,
                "field_goals_made": 1,
                "offensive_rebounds": 0,
                "player_first_name": "Ryan",
                "three_points_made": 0,
                "player_last_name": "Hollins",
                "points": 3,
                "personal_fouls": 2,
                "three_points_attempted": 0,
                "minutes_played": 10,
                "technical_fouls": 0,
                "player_position": "C",
                "player_id": 1198,
                "started": "No",
                "seconds_played": 11,
                "defensive_rebounds": 0
              }
            },
            {
              "listing": {
                "freethrows_attempted": 2,
                "freethrows_made": 1,
                "field_goals_attempted": 4,
                "turnovers": 0,
                "total_rebounds": 1,
                "blocks": 0,
                "steals": 0,
                "assists": 0,
                "field_goals_made": 0,
                "offensive_rebounds": 1,
                "player_first_name": "Reggie",
                "three_points_made": 0,
                "player_last_name": "Bullock",
                "points": 1,
                "personal_fouls": 0,
                "three_points_attempted": 3,
                "minutes_played": 12,
                "technical_fouls": 0,
                "player_position": "F",
                "player_id": 2408,
                "started": "No",
                "seconds_played": 3,
                "defensive_rebounds": 0
              }
            },
            {
              "listing": {
                "freethrows_attempted": 0,
                "freethrows_made": 0,
                "field_goals_attempted": 1,
                "turnovers": 0,
                "total_rebounds": 1,
                "blocks": 0,
                "steals": 0,
                "assists": 0,
                "field_goals_made": 0,
                "offensive_rebounds": 0,
                "player_first_name": "Willie",
                "three_points_made": 0,
                "player_last_name": "Green",
                "points": 0,
                "personal_fouls": 0,
                "three_points_attempted": 1,
                "minutes_played": 15,
                "technical_fouls": 0,
                "player_position": "G",
                "player_id": 634,
                "started": "No",
                "seconds_played": 7,
                "defensive_rebounds": 1
              }
            },
            {
              "listing": {
                "player_first_name": "Chris",
                "reason": "Coach's Decision",
                "player_last_name": "Paul",
                "status": "Did Not Play",
                "player_id": 1011
              }
            },
            {
              "listing": {
                "player_first_name": "Matt",
                "reason": "Coach's Decision",
                "player_last_name": "Barnes",
                "status": "Did Not Play",
                "player_id": 827
              }
            },
            {
              "listing": {
                "player_first_name": "Byron",
                "reason": "Coach's Decision",
                "player_last_name": "Mullens",
                "status": "Did Not Play",
                "player_id": 1568
              }
            },
            {
              "listing": {
                "player_first_name": "Maalik",
                "reason": "Injured",
                "player_last_name": "Wayns",
                "status": "Did Not Play",
                "player_id": 2344
              }
            }
          ],
          "totals": {
            "freethrows_attempted": 37,
            "freethrows_made": 24,
            "field_goals_attempted": 83,
            "total_rebounds": 46,
            "points_off_turnovers": 18,
            "blocks": 9,
            "illegal_defense": 1,
            "steals": 4,
            "assists": 20,
            "field_goals_made": 37,
            "turnoveres": 12,
            "offensive_rebounds": 13,
            "team_turnovers": 12,
            "three_points_made": 6,
            "team_rebounds": 14,
            "shooting_percentages": {
              "field_goal_percentage": 0.446,
              "three_point_percentage": 0.3,
              "freethrow_percentage": 0.649
            },
            "points": 104,
            "personal_fouls": 16,
            "three_points_attempted": 20,
            "minutes_played": 265,
            "technical_fouls": 2,
            "seconds_played": 240,
            "non_player_technicals": 0,
            "defensive_rebounds": 33
          }
        }
      },
      {
        "team": {
          "team_id": 109,
          "team_name": "Sacramento",
          "listings": [
            {
              "listing": {
                "freethrows_attempted": 1,
                "freethrows_made": 0,
                "field_goals_attempted": 13,
                "turnovers": 1,
                "total_rebounds": 6,
                "blocks": 0,
                "steals": 0,
                "assists": 4,
                "field_goals_made": 6,
                "offensive_rebounds": 0,
                "player_first_name": "Derrick",
                "three_points_made": 0,
                "player_last_name": "Williams",
                "points": 12,
                "personal_fouls": 3,
                "three_points_attempted": 2,
                "minutes_played": 32,
                "technical_fouls": 0,
                "player_position": "F",
                "player_id": 2192,
                "started": "Yes",
                "seconds_played": 21,
                "defensive_rebounds": 6
              }
            },
            {
              "listing": {
                "freethrows_attempted": 0,
                "freethrows_made": 0,
                "field_goals_attempted": 7,
                "turnovers": 1,
                "total_rebounds": 4,
                "blocks": 1,
                "steals": 0,
                "assists": 0,
                "field_goals_made": 3,
                "offensive_rebounds": 2,
                "player_first_name": "Jason",
                "three_points_made": 0,
                "player_last_name": "Thompson",
                "points": 6,
                "personal_fouls": 4,
                "three_points_attempted": 0,
                "minutes_played": 23,
                "technical_fouls": 0,
                "player_position": "F",
                "player_id": 1496,
                "started": "Yes",
                "seconds_played": 11,
                "defensive_rebounds": 2
              }
            },
            {
              "listing": {
                "freethrows_attempted": 3,
                "freethrows_made": 3,
                "field_goals_attempted": 19,
                "turnovers": 3,
                "total_rebounds": 9,
                "blocks": 3,
                "steals": 0,
                "assists": 6,
                "field_goals_made": 11,
                "offensive_rebounds": 1,
                "player_first_name": "DeMarcus",
                "three_points_made": 0,
                "player_last_name": "Cousins",
                "points": 25,
                "personal_fouls": 5,
                "three_points_attempted": 0,
                "minutes_played": 41,
                "technical_fouls": 0,
                "player_position": "C",
                "player_id": 1900,
                "started": "Yes",
                "seconds_played": 21,
                "defensive_rebounds": 8
              }
            },
            {
              "listing": {
                "freethrows_attempted": 3,
                "freethrows_made": 3,
                "field_goals_attempted": 12,
                "turnovers": 2,
                "total_rebounds": 4,
                "blocks": 0,
                "steals": 2,
                "assists": 3,
                "field_goals_made": 5,
                "offensive_rebounds": 2,
                "player_first_name": "Ben",
                "three_points_made": 1,
                "player_last_name": "McLemore",
                "points": 14,
                "personal_fouls": 3,
                "three_points_attempted": 6,
                "minutes_played": 34,
                "technical_fouls": 0,
                "player_position": "G",
                "player_id": 2432,
                "started": "Yes",
                "seconds_played": 43,
                "defensive_rebounds": 2
              }
            },
            {
              "listing": {
                "freethrows_attempted": 1,
                "freethrows_made": 1,
                "field_goals_attempted": 12,
                "turnovers": 0,
                "total_rebounds": 3,
                "blocks": 0,
                "steals": 0,
                "assists": 7,
                "field_goals_made": 4,
                "offensive_rebounds": 1,
                "player_first_name": "Greivis",
                "three_points_made": 2,
                "player_last_name": "Vasquez",
                "points": 11,
                "personal_fouls": 2,
                "three_points_attempted": 6,
                "minutes_played": 35,
                "technical_fouls": 0,
                "player_position": "G",
                "player_id": 1970,
                "started": "Yes",
                "seconds_played": 58,
                "defensive_rebounds": 2
              }
            },
            {
              "listing": {
                "freethrows_attempted": 0,
                "freethrows_made": 0,
                "field_goals_attempted": 4,
                "turnovers": 1,
                "total_rebounds": 3,
                "blocks": 0,
                "steals": 0,
                "assists": 0,
                "field_goals_made": 2,
                "offensive_rebounds": 1,
                "player_first_name": "Marcus",
                "three_points_made": 0,
                "player_last_name": "Thornton",
                "points": 4,
                "personal_fouls": 5,
                "three_points_attempted": 2,
                "minutes_played": 17,
                "technical_fouls": 0,
                "player_position": "G",
                "player_id": 1646,
                "started": "No",
                "seconds_played": 26,
                "defensive_rebounds": 2
              }
            },
            {
              "listing": {
                "freethrows_attempted": 2,
                "freethrows_made": 1,
                "field_goals_attempted": 5,
                "turnovers": 1,
                "total_rebounds": 9,
                "blocks": 0,
                "steals": 0,
                "assists": 0,
                "field_goals_made": 3,
                "offensive_rebounds": 3,
                "player_first_name": "Patrick",
                "three_points_made": 0,
                "player_last_name": "Patterson",
                "points": 7,
                "personal_fouls": 4,
                "three_points_attempted": 0,
                "minutes_played": 26,
                "technical_fouls": 0,
                "player_position": "F",
                "player_id": 1950,
                "started": "No",
                "seconds_played": 59,
                "defensive_rebounds": 6
              }
            },
            {
              "listing": {
                "freethrows_attempted": 0,
                "freethrows_made": 0,
                "field_goals_attempted": 1,
                "turnovers": 2,
                "total_rebounds": 1,
                "blocks": 0,
                "steals": 0,
                "assists": 0,
                "field_goals_made": 0,
                "offensive_rebounds": 1,
                "player_first_name": "Hamady",
                "three_points_made": 0,
                "player_last_name": "N`diaye",
                "points": 0,
                "personal_fouls": 3,
                "three_points_attempted": 0,
                "minutes_played": 4,
                "technical_fouls": 0,
                "player_position": "C",
                "player_id": 1944,
                "started": "No",
                "seconds_played": 56,
                "defensive_rebounds": 0
              }
            },
            {
              "listing": {
                "freethrows_attempted": 0,
                "freethrows_made": 0,
                "field_goals_attempted": 2,
                "turnovers": 0,
                "total_rebounds": 0,
                "blocks": 0,
                "steals": 1,
                "assists": 1,
                "field_goals_made": 0,
                "offensive_rebounds": 0,
                "player_first_name": "Travis",
                "three_points_made": 0,
                "player_last_name": "Outlaw",
                "points": 0,
                "personal_fouls": 0,
                "three_points_attempted": 1,
                "minutes_played": 9,
                "technical_fouls": 0,
                "player_position": "F",
                "player_id": 759,
                "started": "No",
                "seconds_played": 30,
                "defensive_rebounds": 0
              }
            },
            {
              "listing": {
                "player_first_name": "Chuck",
                "reason": "Coach's Decision",
                "player_last_name": "Hayes",
                "status": "Did Not Play",
                "player_id": 1044
              }
            },
            {
              "listing": {
                "player_first_name": "Carl",
                "reason": "Injured",
                "player_last_name": "Landry",
                "status": "Did Not Play",
                "player_id": 1333
              }
            },
            {
              "listing": {
                "player_first_name": "Ray",
                "reason": "Injured",
                "player_last_name": "McCallum",
                "status": "Did Not Play",
                "player_id": 2430
              }
            },
            {
              "listing": {
                "player_first_name": "Jimmer",
                "reason": "Coach's Decision",
                "player_last_name": "Fredette",
                "status": "Did Not Play",
                "player_id": 2111
              }
            }
          ],
          "totals": {
            "freethrows_attempted": 10,
            "freethrows_made": 8,
            "field_goals_attempted": 91,
            "total_rebounds": 44,
            "points_off_turnovers": 15,
            "blocks": 5,
            "illegal_defense": 1,
            "steals": 4,
            "assists": 24,
            "field_goals_made": 42,
            "turnoveres": 15,
            "offensive_rebounds": 12,
            "team_turnovers": 16,
            "three_points_made": 6,
            "team_rebounds": 6,
            "shooting_percentages": {
              "field_goal_percentage": 0.462,
              "three_point_percentage": 0.261,
              "freethrow_percentage": 0.8
            },
            "points": 98,
            "personal_fouls": 33,
            "three_points_attempted": 23,
            "minutes_played": 265,
            "technical_fouls": 1,
            "seconds_played": 360,
            "non_player_technicals": 0,
            "defensive_rebounds": 32
          }
        }
      }
    ],
    "away_team_final_score": 104,
    "quarter": 5,
    "game_date": "November 29, 2013",
    "technicals": [
      {
        "listing": {
          "flagrant": "False",
          "team_id": 109,
          "time_left": "8:40",
          "player_first_name": "",
          "quarter": 3,
          "player_last_name": "",
          "player_id": 0,
          "def_3_sec": "True",
          "bench": "False"
        }
      },
      {
        "listing": {
          "flagrant": "False",
          "team_id": 101,
          "time_left": "6:11",
          "player_first_name": "Blake",
          "quarter": 3,
          "player_last_name": "Griffin",
          "player_id": 1590,
          "def_3_sec": "False",
          "bench": "False"
        }
      },
      {
        "listing": {
          "flagrant": "False",
          "team_id": 101,
          "time_left": "5:46",
          "player_first_name": "",
          "quarter": 3,
          "player_last_name": "",
          "player_id": 0,
          "def_3_sec": "True",
          "bench": "False"
        }
      }
    ],
    "sport": "NBA",
    "visiting_team": "LA Clippers",
    "status": "FINAL",
    "home_team_id": 109,
    "title": "LA Clippers at Sacramento",
    "attendance": "17317",
    "home_team_final_score": 98,
    "updated_at": "2013-11-30 06:08:40 UTC",
    "visiting_team_id": 101,
    "home_team": "Sacramento",
    "season_year": "2013",
    "officials": "Tony Brothers, David Guthrie, JT Orr"
  }
]

我必须将您的JSON复制到一个文件中才能正确读取它(注意,这不是必需的,您可以将JSON流直接提供给
JSON.parse
,因为它应该正确格式化),然后将其作为JSON解析为哈希数组,并使用OpenStruct将其转换为ruby对象,如果您有一个大数组,那么OpenStruct的性能不是很好,可以找到解释

由OpenStruct生成的setter和getter

a.each { |x| p x.methods(false) };
[:game_length, :game_length=, :location, :location=, :game_time, :game_time=, :season_type, :season_type=, :game_id, :game_id=, :scores_by_quarter, :scores_by_quarter=, :team_stats, :team_stats=, :away_team_final_score, :away_team_final_score=, :quarter, :quarter=, :game_date, :game_date=, :technicals, :technicals=, :sport, :sport=, :visiting_team, :visiting_team=, :status, :status=, :home_team_id, :home_team_id=, :title, :title=, :attendance, :attendance=, :home_team_final_score, :home_team_final_score=, :updated_at, :updated_at=, :visiting_team_id, :visiting_team_id=, :home_team, :home_team=, :season_year, :season_year=, :officials, :officials=]

您可以将JSON数据解析为两个类:数组和哈希:

  • 阵列:

    data = [1, 2]
    data.to_json # => "[1,2]"
    JSON.parse(data.to_json) # => [1, 2]
    
  • 散列:

    data = {'a' => 1}
    data.to_json # => "{\"a\":1}"
    JSON.parse(data.to_json) # => {"a"=>1}
    
尝试生成任何其他内容都会导致错误

一旦您将JSON字符串解析回Ruby对象,您就可以使用这两种类型中的一种作为所需数据的包装器或容器,因此,如果数据是数组,您可以使用普通Ruby循环遍历数据,如果数据是散列,则可以直接访问所需的值

虽然您的示例看起来很复杂,但可以很容易地分解为一些简单的代码来提取数据。如果我解析数据并将其存储在名为
data
的变量中:

  • data。首先
    返回包含数据的哈希,在您的示例中,该哈希就是所有数据。如果存在多个散列,则使用以下方法对其进行迭代:

    data.each { |hash| ... }
    
    或:

一旦有了子散列,您就可以通过迭代或使用散列访问以类似的方式访问内容:

data.first['scores_by_quarter'].last['scores']['home_team_score']
# => 4

减少数据大小。扔掉这么大的样品没什么好处,除非你想让人们的眼睛发晕。
data.map { |hash| ... }
data.first['scores_by_quarter'].last['scores']['home_team_score']
# => 4