Selecting correct types from an union based on another property in mapping function

I am converting some old project to Typescript recently and got stuck with some nested object types… Basically I have two types created based on backend response object and they have some common properties eg. index but also some unique ones eg. sharkEnabled:

type TSharkItem = {
  index: string;
  sharkEnabled: boolean;
};

type TGrapeItem = {
  index: string;
  grapeEnabled: boolean;
};

// Union type of data for later use
type TData = TGrapeItem[] | TSharkItem[];

Now I have mapping function mapDataToState which will need to know which type of TData to use and create proper state out of it. To do that, there was created some kind of “connector/mapping” between fields:

// Connector/mapping
const columnMap = {
  fruit: {
    enabledColumn: "grapeEnabled",
  },
  fish: {
    enabledColumn: "sharkEnabled",
  },
} as const;
type ColumnMap = typeof columnMap;
type AvailableColumnName = ColumnMap["fish"] | ColumnMap["fruit"];


// Return type of mapDataToState fn
type State = { isEnabled: boolean }[];


const mapDataToState = (data: TData, columnNames: AvailableColumnName): State => {
  return data.reduce((memo, row) => {
    return [
      ...memo,
      {
        isEnabled: row[columnNames.enabledColumn], // ERROR: Property 'grapeEnabled' does not exist on type 'TSharkItem | TGrapeItem'.
      },
    ];
  }, [] as State);
};

I understood the error from TS, that for sure grapeEnabled only exist in TGrapeItem type, not TSharkItem – but unfortunately I’m quite fresh in TS world and I’m not sure if that’s even possible to achieve.

Any hints much appreciated 🙂

Link to TS playground:

https://www.typescriptlang.org/play/?#code/MYewdgzgLgBKA2BXAtmAsgQwA4wLwwG8AoGGAMwCdEBLKALkJNJgFMwMAjeFgEwGEQSVAwBEAcwrYWAUXZdeIgDRMAvstJlqEABYNizVnO79BKMKJ0YKAa1mdjS1cpUwMEOOGgBuIkSgBPLBYYASF0bDwYAKCQMg8wzCwfaOCAQQA3DGp4exZQswA5DGRg-HzURIBtEU0dEQBdGAAfENMK7GrKGigGnz9A4IAVAGVtK2sASSgWZEj9GGowHhYADwZoCkWxH1JLGzt5HgYOEEEWDDAfFT6UmEGAcUkgqZm5pkXltZgNrZ2YCSkB2Mx1O3AuVxuAzuABEMFAMJEHk8WC9kJVGi0RmMbKj0ZCgjBhvDpnMFhAgbwQWcLjAVHiiKBILBkNhYfDBiAiXDSjAABQ8OEYBiDNkYRTxQrFFgQBgZLI5eTlMBFEoASgYXJJuAAfIxSBQWFBEBQwDABfCAHQGniIYAsXm8krIEDiiggADuqrwuvm+sNxtNlSYBgtoadLuDzF9BjJFKOMDd7sqCElJQgFrYuRMYXq6hjakj9T+ahg6Nc7k1LFVEKIQA