Home > Mobile >  How to create a type for an object whose elements have values that are related?
How to create a type for an object whose elements have values that are related?

Time:09-22

I am receiving some arbitrary table data, from which I will create a sortable table in React:

const columns = ['key1', 'key2'],
const rows = [
    { key1: 'value 1,1', key2: 'value 2,1' },
    { key1: 'value 1,2', key2: 'value 2,2' },
]

I do not know what the table will contain, so 'key1' | 'key2' ... are unknown to my code. However, I do know that the keys of rows[i] will all be elements of columns. Is there a way to enforce this in the types of the two objects? What if I have both as arguments to a function? I tried something like this:

type TableProps<Row> = {
    cols: (keyof Row)[];
    rows: Row[];
}
const Table: React.FC<TableProps<Record<string, any>> = ({ cols, rows }) => { ... }

However, this seems equivalent to defining TableProps simply as

type TableProps = {
    cols: string[];
    rows: Record<string, any>[];
}

which is not a strong enough statement. It would be nice if I could type the column-names as a string-literal without fixed values, and the rows as Records with those (unknown) string literals as keys. But I can't figure out how to do that.

Also, it would be nice to express somehow that cols is not just any old selection of the values of the string-literal in question, but all of them in some order, once each. But maybe that is too much to ask.

I'm not sure it will even add much benefit to make such firm restrictions through typing, but in my head it sounds so simple to express what I want, so it bothers me that I can't figure out how to do it.

Edit:

To rephrase my question:

How can I type the following function, so that I get an error from typescript in the appropriate place?

const example = (tableData: ???): void => {
    const {cols, rows} = tableData;
    
    if (cols.length === 0 || rows.length === 0) { return; }
    
    const ex1 = rows[0][cols[0]]; // OK
    const badKey = 'some random string'
    const ex2 = rows[0][badKey];  // ERROR
    // badKey here may in fact actually be one of the keys of rows[0]
    // at runtime. TypeScript cannot know that, but that is okay.
}

Edit II: Changed the title from

Can you create a string-literal type without knowing the string values beforehand?

to

How to create a type for an object whose elements have values that are related?

CodePudding user response:

I found an answer to the above, but in truth I am not satisfied yet.

const example = <T extends string>(
    tableData: { cols: T[], rows: Record<T, any>[] }
): void => {    
    const {cols, rows} = tableData;
    if (cols.length === 0 || rows.length === 0) { return; }
    
    const ex1 = rows[0][cols[0]]; // OK
    const ex2 = rows[0]['asdf'];  // ERROR: 
    // Property 'asdf' does not exist on type 'Record<T, any>'.
}

I worry that this approach would make the function tricky to use, since I would have to have this generic T everywhere as context whenever dealing with a tableData as function argument or value. It would be nice if I could just define some loose type somewhere, which encodes this flexibility that the generic T gives in this case, and use that in all places where a TableData pops up. I will keep trying to find a solution to the overall problem.

Apologies for any confusion, as I realise the title of the question is starting to lose relevance. I will continue to read up to see if I can get a more general solution to my problem.

CodePudding user response:

No, it's not possible, for the simple reason that the typing is not related to when your application runs. Also, the JS is not typed, only the TS is.

This means that you can't enforce typings for when you have data in the application, you can only enforce typings for when you code.

The simplest typing I would think of in this case is this one :

interface DatasetRow {
  [key: string]: string;
}
type Dataset = DatasetRow[];

It's very simple but that's what represents your data the best.

But to better answer you, maybe you should tell us (me?) what are you trying to achieve in the first place ?

  • Related