A name is a label, not a join key
Two players can share a name, and one player's spelling can change across displays. Shirt numbers are also unsuitable identifiers because they can be reused or change with a transfer. Use the player ID returned in a squad entry to join to a player record, and retain the squad's season context separately.
The team players operation returns squad members with id, name, nationality, photo_url, shirt_number, position and season. A player detail record provides a different resource shape. Do not assume that every field in one response belongs in the other, or copy team-specific shirt numbers into a global player identity.
Model membership separately from identity
A local data model can have one player table and a membership table keyed by team, season and player. The membership carries the context-specific role and shirt number. This prevents a new squad observation from rewriting a player's historical membership and makes a season selector meaningful.
The fixture uses two players with the same display name and different IDs. A name-based dictionary would collapse one of them. The tested join keeps both records and leaves an unresolved third player explicit rather than borrowing details from the first matching name.
Run the reproducible example
This Python 3 example uses deliberately synthetic inputs, not a downloaded match or a forecast for a real fixture. Save it as example.py and run python3 example.py. It uses only the standard library and its assertions check the behavior discussed here. Replace the fixture inputs with validated API records when connecting your application; demo identifiers are not production lookup values.
def join_members(members, players):
return [{"id":m["id"],"squad_name":m["name"],"season_id":m["season"]["id"],
"shirt_number":m["shirt_number"],"profile":players.get(m["id"])} for m in members]
members = [
{"id":"player_a","name":"Alex Example","shirt_number":9,"season":{"id":"season_demo"}},
{"id":"player_b","name":"Alex Example","shirt_number":14,"season":{"id":"season_demo"}},
{"id":"player_c","name":"Other Example","shirt_number":None,"season":{"id":"season_demo"}}
]
players = {"player_a":{"id":"player_a","nationality":"Example A"},
"player_b":{"id":"player_b","nationality":"Example B"}}
result = join_members(members,players)
assert len(result) == 3
assert result[0]["profile"]["nationality"] != result[1]["profile"]["nationality"]
assert result[2]["profile"] is None
assert result[2]["shirt_number"] is None
print([(r["id"],r["profile"] is not None) for r in result])
Read the result
Two identically named synthetic players retain distinct profile joins. The third member remains in the roster with an unresolved profile and a null shirt number. No record disappears because a secondary lookup was unavailable.
Handle missing joins as data work
A missing detail response can mean a wrong ID, a changed observation or a request failure. Record the exact state and retry only under your request policy. Do not fabricate a birth date, nationality or photograph to complete a profile card. The roster remains useful with its own returned name while the additional detail is unresolved.
Keep joins scoped to the intended season and team. The presence of a player in a returned squad does not establish that the player started a particular match or is currently available to play. Match lineups and injury observations provide different evidence and should remain separate in your product model.
Test transfers and presentation changes
Test identical names, a changed name under the same ID, different shirt numbers across seasons and missing detail records. Ensure that updating a current roster does not erase historical memberships. When a user opens an older season, show the membership associated with that season rather than the player's latest team context.
For a public profile, use stable resource links and treat nullable fields as optional display elements. A missing image can use a neutral visual fallback; it should not trigger a lookup of a similarly named person from another source. Identity discipline prevents subtle errors that polished profile cards can otherwise hide.
Connect this to your project
Build the membership join before adding profile cards or fantasy selection controls. Use the team guide for discovery and inspect the current player and squad schemas in OpenAPI. Stable IDs make the integration robust when display names and team contexts change.