Let me begin by expressing my disdain for this architecture. If there is a way to revamp your code into a more coherent Team
structure, I strongly recommend doing so. Is it achievable? Absolutely. Team
must be categorized as a type
rather than an interface because interfaces are limited to known keys only.
Let's start by deconstructing it.
TeamBase lays out the fundamental properties that are always present:
interface TeamBase {
startDate: Timestamp
endDate: Timestamp
agenda: Array<{
date: Date | Timestamp
title: string
description: string
}>
}
UidProperties outlines the contents of the unspecified key:
interface UidProperties {
mail: string
name: string
photoURL: string
}
Here's a utility type that enables declaring a property based on the key's type via mapped types:
type HasProperty<Key extends keyof any, Value> = {
[K in Key]: Value;
}
If the generic Key
is a specific string then K in Key
can only be that exact string, resulting in
HasProperty<"myKey", string>
resolving to
{ myKey: string; }
Now we need to define the type Team
. It must encompass all TeamBase
properties and involve a string key with a value of UidProperties
. The crucial detail causing previous issues is that this key cannot exist in TeamBase's keys. Therefore, instead of utilizing string
, we employ
Exclude<string, keyof TeamBase>
.
You could utilize HasProperty<...>
to imply that every non-TeamBase key should have a value of
UidProperties</code, but it might be better to opt for <code>Partial<HasProperty<...>>
, indicating that these unknown string keys could be
UidProperties
or
undefined
. It's wise to double check the value when accessing an unknown key regardless.
Therefore, our finalized Team type is:
export type Team = TeamBase & Partial<HasProperty<Exclude<string, keyof TeamBase>, UidProperties>>
This functions as intended. Since we've allowed for the potential absence of uid
, you cannot access its properties without confirming if it's defined using optional chaining with ?
or an if
statement.
function f1(team: Team) {
const startDate: Timestamp = team.startDate;
const uid: UidProperties | undefined = team['randomKey']
const name = uid?.name;
if ( uid !== undefined ) {
const mail = uid.mail;
}
}