Home > Enterprise >  typescript error when trying to implement union types for function (generics)
typescript error when trying to implement union types for function (generics)

Time:01-18

typescript playground

So I'm trying to create a more general function which can help me transform data from an object instead something else.

However, with my current implementation, I'm getting the typescript error

Element implicitly has an 'any' type because expression of type '"channel" | "id" | "name"' can't be used to index type '{ id: string; name: string; source: string; }'.
  Property 'channel' does not exist on type '{ id: string; name: string; source: string; }'.

Since the keys may not be matching up, but I am unsure on how to make it into a proper generic function.

Code:

type channelCreateChanges = {
  change_from : {}
  change_to : {
    id : string,
    name : string,
    source : string,
  },
  datetime : string,
  operation : "create",
  entity : "channel",
}
type channelUpdateChanges = {
  change_from : {
    id : string,
    name : string,
    source : string,
  }
  change_to : {
    id : string,
    name : string,
    source : string,
  },
  datetime : string,
  operation : "update",
  entity : "channel",
}
type programCreateChanges = {
  change_from : {
  }
  change_to : {
    channel : string,
    id : string,
    name : string,
    origin : string,
  },
  datetime : string,
  operation : "create",
  entity : "program",
}
type programDeleteChanges = {
  change_from : {
    channel : string,
    id : string,
    name : string,
    origin : string,
  }
  change_to : {
  },
  datetime : string,
  operation : "delete",
  entity : "program",
}

type dataChangeRow = {
  from : string,
  to : string,
  datetime : string,
  operation : string,
  entity : string
}

const programKeys = [
  "id",
  "channel"
] as const;
const channelKeys = [
  "id",
  "name",
] as const;

type allChanges = channelCreateChanges | channelUpdateChanges | programCreateChanges | programDeleteChanges
type allKeys = typeof programKeys | typeof channelKeys

function ANY_CHANGE_LOG_TO_ROWS (data : allChanges, keys : allKeys) : dataChangeRow[] {
    const rows = [] as dataChangeRow[];
    for (const key of keys) {
      const row = {
        datetime : data.datetime,
        entity : data.entity,
        operation : data.operation
      } as dataChangeRow;

      if (data.operation === "create") {
        row.from = `-`;
        row.to = `${key}: ${data.change_to[key]}`;
            }
      else if (data.operation === "update") {
        row.from = `${key}: ${data.change_from[key]}`;
        row.to = `${key}: ${data.change_to[key]}`;
      }
      else if (data.operation === "delete") {
        row.from = `${key}: ${data.change_from[key]}`;
        row.to = `-`;
      }
            rows.push(row);
    }
    return rows;
}

CodePudding user response:

As Robby notice to get this working, you need that Channel have 'channel' property.

I propose to forget about specify keys for specific logging each object type. (probably not what you want)

Here is a real generic implementation :

type Channel = { id : string; name : string; source : string }
type Program = { id : string; name : string; origin : string; channel : string }

type OperationType = "create" | "update" | "delete";

type Operation<ENTITY, OP extends OperationType> = {
  change_from : OP extends "update" | "delete" ?  ENTITY : undefined;
  change_to : OP extends "update" | "create" ? ENTITY : undefined;
  datetime : string,
  operation : OP,
  entity : ENTITY extends Channel ? 'Channel' : ENTITY extends  Program ? 'Program' : 'unknown';
 } 

type channelCreateChanges = Operation<Channel,"create">
type channelUpdateChanges = Operation<Channel,"update">
type programCreateChanges = Operation<Program,"create">
type programDeleteChanges = Operation<Program,"delete">


type dataChangeRow = {
  from : string,
  to : string,
  datetime : string,
  operation : string,
  entity : string
}

type allChanges = channelCreateChanges | channelUpdateChanges | programCreateChanges | programDeleteChanges

function ANY_CHANGE_LOG_TO_ROWS (allOperations : Array<allChanges>) : dataChangeRow[] {
    const rows = allOperations.map((ope) => {
        const row : dataChangeRow = {
            from : ope.change_from ? JSON.stringify(ope.change_from) : '-',
            to : ope.change_to ? JSON.stringify(ope.change_to) : '-',
            datetime : ope.datetime,
            entity : ope.entity,
            operation : ope.operation
         } ;
         return row;
     })
  
    return rows;
}

If you really needs to log speciifc key, please notice that you can get these :

type AllKeys = keyof Channel | keyof Program; // "id" | "name" | "source" | "origin" | "channel"
type CommonKeys = keyof Channel & keyof Program; // "id" | "name" 

You can se that you use Allkeys, but what you really want is CommonKey, to address any of Channel or Program by indexing.

If you need 'channel' property you have to manage so that CommonKeys equals to "id" | "name" | "channel"

For example you can add {channel:undefined} property to Channel type.

CodePudding user response:

OK !

Now i have understood what you want, sorry for the mistake.

I let you see details and test in link below, but in summary here is your function implementation :

const ANY_CHANGE_LOG_TO_ROWS = <ENTITY, OP extends OperationType, K extends keyof ENTITY>(ope : Operation<ENTITY,OP>, keyToSerialise : Array<AllKeys>) : dataChangeRow[] => {
{
    const rows = Array<dataChangeRow>();
    
    const entityKeys = (ope.change_to === undefined ? Object.keys(ope.change_from!) : Object.keys(ope.change_to!) )  as Array<K>;

    for(const key of entityKeys)
        if(keyToSerialise.includes(key as AllKeys)) {
          const row : dataChangeRow = {
            from : ope.change_from ? (key as string   ":"   (ope.change_from as ENTITY)[key]): '-',
            to : ope.change_to ?  (key as string   ":"   (ope.change_to as ENTITY)[key]): '-',
            datetime : ope.datetime,
            operation : ope.operation,
            entity : ope.entity,
          } ;
          rows.push(row)
        }
    return rows;
  }
}

There is many cast, that i guess would be valid.

please see this playground to more detailed code, with comments on tricky parts.

  • Related