Dejan Toteff
Dejan Toteff

Reputation: 2207

Cannot use custom type inside an interface

I seem unable to understand why I cannot use custom type inside an interface.

I have the following definition:

type Operation = 'update'|'remove'|'add'
interface Rule {
  op: Operation
  path:string
  value?:any
}

function applyDiff<Output>(rules: Rule[], obj: object): Output;

but I receive Type 'string' is not assignable to type 'Operation' as an error at this failing test:

const obj = {a: {b:1, c:2}}
const rules = [
  {op: 'update', path: 'a.c', value:10},
] 
const result = applyDiff(rules, obj)

but this code works just fine:

function fn(x: number, y: 'add'|'remove'|'update'){
  return x > 1 && y === 'add'
  
}
const result = fn(1, 'remove')

What I am missing here? I would appreciate any helpful guidance on the matter. Thank you.

Upvotes: 1

Views: 67

Answers (1)

jonrsharpe
jonrsharpe

Reputation: 122116

First, the simple solution:

const rules: Rule[] = [
        // ^------^
  {op: 'update', path: 'a.c', value:10},
] 

But why doesn't that work to begin with? Because the inferred type of rules is {op: string; path: string; value: number}[], and there are lots of strings that aren't any of the values in the Operation union. Objects and arrays are mutable, so there's nothing to prevent the value of op changing (e.g. pushing a new object into the array, or mutating the existing object in the array) between defining rules and passing it to applyDiff.

This also suggests some other solutions:

  1. Pass the array directly to the function:

    const result = applyDiff([
      {op: 'update', path: 'a.c', value:10},
    ], obj);
    

    Here there's no way for the values to change between defining the array and passing it to applyDiff, so the narrower type {op: 'update'; path: 'a.c'; value: 10}[] is inferred.

  2. Use a const assertion to explicitly narrow the type of the rule:

    const rules = [
      {op: 'update', path: 'a.c', value:10} as const,
                                        // ^-------^
    ];
    

    This tells the compiler you consider that object to be immutable, which leads it to infer a narrower type for rules too. You could instead explicitly narrow the type of the op:

    const rules = [
      {op: 'update' as const, path: 'a.c', value:10},
                // ^-------^
    ];
    

    with similar effects.

Upvotes: 1

Related Questions