gem
v0.13.0

JSON Output Shape

gem.to_json() and gem.parse_to_json() serialize the same ParsedMatch object returned by gem.parse(). JSON is gem's full-fidelity export: it keeps every nested record, and gem.load_json() reads it back into a ParsedMatch. Use it for saved artifacts, API responses, and downstream systems that do not use pandas.

import gem

json_str = gem.parse_to_json("my_replay.dem", indent=2)

match = gem.parse("my_replay.dem")
json_str = gem.to_json(match, indent=2)
data = gem.to_dict(match)  # the same match fields as a plain dict, without metadata

From the CLI:

python -m gem my_replay.dem --format json > match.json
python -m gem my_replay.dem --format json --analysis --output match.json

Saving and loading

Save a parsed match once and load it back later instead of re-parsing the replay. Loading a 40-minute replay's ~57 MB JSON takes a few seconds; parsing the .dem again takes about a minute.

from pathlib import Path

import gem

match = gem.parse("my_replay.dem")
Path("match.json").write_text(gem.to_json(match), encoding="utf-8")

loaded = gem.load_json("match.json")
assert loaded == match

hero = gem.find_player(loaded, "Axe")  # every analysis helper works on a loaded match

gem.load_json() restores the original Python types: enums such as log_type, tuples such as position_log, and integer keys such as final_items slots. gem.from_dict() does the same for an already-decoded JSON object.

Including analysis results

gem.analyze(match) runs every default post-parse analysis (smoke lifecycles, Roshan conversions, farming routes, and fight positioning) and returns a MatchAnalysis. Pass it to to_json() to embed the results under an analysis key:

analysis = gem.analyze(match)
json_str = gem.to_json(match, analysis=analysis)

# One call from a replay path:
json_str = gem.parse_to_json("my_replay.dem", analyze=True)

The analysis section is for JSON consumers such as web tools and other languages. It is not decoded by gem.load_json(); in Python, call gem.analyze(loaded) again, which gives the same results.

Versioning and compatibility

Every to_json() payload carries schema_version (currently 4) and gem_version beside the match fields:

  • Files from older gem versions, including ones written before schema_version existed, still load. Fields they do not contain fall back to their defaults.
  • Schema 3 files still load: schema 4 only changed the analysis section, which load_json() does not decode (gem 0.13 removed its interpretation fields).
  • Schema 2 and earlier named gem's fight list teamfights; it loads as fights.
  • Schema 1 (gem 0.10 and earlier) stored lane_pos on a different grid, which cannot be converted; it loads empty.
  • Keys the running gem does not know are ignored.
  • A file with a schema_version newer than the running gem supports raises ValueError; upgrade gem-dota to read it.
  • Output is strict JSON: NaN and infinite floats raise ValueError instead of being written as non-standard literals.

Top-level shape

The top-level object mirrors ParsedMatch, plus the version keys and the optional analysis section:

{
  "schema_version": 4,
  "gem_version": "0.13.0",
  "match_id": 8461735141,
  "game_mode": 2,
  "leagueid": 18324,
  "radiant_win": true,
  "duration": 3264,
  "radiant_score": 31,
  "dire_score": 18,
  "players": [],
  "draft": [],
  "combat_log": [],
  "wards": [],
  "objectives": [],
  "fights": [],
  "opendota_teamfights": [],
  "smoke_events": [],
  "neutral_item_finds": [],
  "radiant_gold_adv": [],
  "radiant_xp_adv": [],
  "analysis": {}
}

The actual output includes more fields than this abbreviated example. For the generated model documentation, see the Models API Reference.

Players

players contains one object per player slot, ordered 0-4 for Radiant and 5-9 for Dire.

{
  "player_id": 0,
  "hero_name": "npc_dota_hero_sven",
  "player_name": "Ame",
  "team": 2,
  "kills": 11,
  "deaths": 0,
  "assists": 3,
  "net_worth": 37348,
  "last_hits": 605,
  "denies": 32,
  "lane_role": 1,
  "damage_by_type": {
    "physical": 8920,
    "magical": 12340,
    "pure": 450
  },
  "purchase_log": [],
  "position_log": []
}

Use gem.constants.hero_display() or gem.catalog.hero_display() to turn internal hero NPC names into display names.

Event arrays

Most match events are arrays of records:

FieldContents
combat_logNormalized damage, death, heal, item, ability, modifier, gold, XP, rune, and buyback events
draftPicks and bans in replay order
wardsObserver and sentry placements with coordinates
objectivesOpenDota-shaped objective timeline
towers, barracks, roshans, tormentors, shrinesTyped Gem objective lists
fightsGem fight windows with richer participant stats
opendota_teamfightsOpenDota-compatible teamfight windows
smoke_eventsSmoke activations with grouped heroes and exact per-member modifier lifecycles
neutral_item_findsNeutral item find user messages
vision_modifiersVision-relevant modifier applications with semantic, lifecycle, pairing, and provenance evidence
vision_modifier_pairing_issuesAmbiguous or orphan modifier removals that were not force-paired

Combat log entries

Combat log records share one schema. log_type tells you which fields are meaningful for that row.

{
  "tick": 25969,
  "log_type": "DAMAGE",
  "attacker_name": "npc_dota_hero_sven",
  "target_name": "npc_dota_hero_pangolier",
  "inflictor_name": "sven_storm_bolt",
  "value": 58,
  "attacker_is_hero": true,
  "target_is_hero": true,
  "damage_type": "magical"
}

Common log_type values include DAMAGE, DEATH, HEAL, ITEM, PURCHASE, ABILITY, MODIFIER_ADD, MODIFIER_REMOVE, GOLD, XP, PICKUP_RUNE, and BUYBACK.

JSON vs DataFrames

Use JSON when you need the complete nested match object, including per-player dicts, fight player breakdowns, and evidence lists. Use gem.parse_to_dataframe() when you want flat, analysis-ready tables with a fixed schema that concatenate across replays.

frames = gem.parse_to_dataframe("my_replay.dem", include=["opendota"])

summary = frames["player_summary"]
combat = frames["combat_log"]
teamfights = frames["opendota_teamfights"]

See Time-Series & DataFrames for the table list and export examples.