Welcome to OStack Knowledge Sharing Community for programmer and developer-Open, Learning and Share
Welcome To Ask or Share your Answers For Others

Categories

0 votes
286 views
in Technique[技术] by (71.8m points)

typescript - Omit first parameter from functions in a JSON object

I am looking for a universal way to achieve something like this:

type InputType<T> = {
  increment: (state: T) => T,
  add: (state: T, count: number) => T
}

type ExpectedOutputType<T> ={
  increment: () => T,
  add: (count: number) => T
}

Or in other words generic type that mirrors a JSON object, but omits the first argument from functions, while preserving the types of other arguments. (all values are functions in this object).

So far I came up with this not working solution:

type OmitFirstArg<F> = F extends (x: any, ...args: infer P) => infer R
  ? (...args: P) => R
  : never;

type ReducerHandler<TState> = (state: TState, payload: unknown) => TState;

type ReducerHandlers<TState> = Record<string, ReducerHandler<TState>>;

type ReducerActions<TState, TReducers extends ReducerHandlers<TState>> = Record<
  keyof TReducers,
  OmitFirstArg<ReducerHandlers<keyof TReducers>>
>;

This is what I test it with:

function get<T>(input: ReducerHandlers<T>): ReducerActions<T, ReducerHandlers<T>> {
  throw new Error("Not implemented");
}

type CounterType = {
  counter: number;
};

const counter: ReducerHandlers<CounterType> = {
  increment: (state) => {
    state.counter++;
    return state;
  },
  add: (state, count: number) => {
    state.counter += count;
    return state;
  }
};

const input: ReducerHandlers<CounterType> = {
  increment: (state) => state,
  add: (state, count: number) => state
};

const output = get<CounterType>(input); // never
question from:https://stackoverflow.com/questions/65889917/omit-first-parameter-from-functions-in-a-json-object

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome To Ask or Share your Answers For Others

1 Answer

0 votes
by (71.8m points)

Your OmitFirstArg seems to be fine, but you need to map it over the properties of your object:

type OmitFirstArgFromMethods<T> = { [K in keyof T]:
  T[K] extends (x: any, ...args: infer P) => infer R ? (...args: P) => R : T[K]
}

type ExpectedOutputType<T> = OmitFirstArgFromMethods<InputType<T>>
/* type ExpectedOutputType<T> = {
    increment: () => T;
    add: (count: number) => T;
} */

By using Record instead of a mapped type where you act on each property in turn, you're basically throwing away the relationship between the keys and the values.


I'm not sure how to use your test code, though. If get() is supposed to take an object full of methods-of-at-least-one-argument-of-the-same-type, then you can use a recursive generic constraint to guarantee that input's properties are all methods of at least one argument, and where the first argument to each is the same type:

function get<T extends { [K in keyof T]:
  (state: Parameters<T[keyof T]>[0], ...rest: any) => any
}>(input: T): OmitFirstArgFromMethods<T> {
  throw new Error("Not implemented");
}

Then your counter should have its type inferred, not annotated, since you don't want it to forget that the keys are named increment and add (I assume):

const counter = {
  increment: (state: CounterType) => {
    state.counter++;
    return state;
  },
  add: (state: CounterType, count: number) => {
    state.counter += count;
    return state;
  }
};

And then finally the typings work as I think you want:

const output = get(counter);
output.add(123).counter; // okay
output.increment().counter; // okay

While preventing "wrong" inputs:

get({
  foo: "oops" // error, string is not assignable to function
})

get({
  bar: (x: string, y: number) => 1, // error, string | number is not string
  baz: (x: number) => 2 // error, string | number is not string
})

I'm not 100% sure how you plan to implement get(), since it needs to pass to the methods of input a value it doesn't necessarily have. Perhaps get() should also accept a value of type Parameters<T[keyof T]>[0]?

Playground link to code


与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome to OStack Knowledge Sharing Community for programmer and developer-Open, Learning and Share
Click Here to Ask a Question

...