samchon / typia

Super-fast/easy runtime validators and serializers via transformation
https://typia.io/
MIT License
4.45k stars 153 forks source link

Implement pick & omit functionality #943

Open aspirisen opened 7 months ago

aspirisen commented 7 months ago

Feature Request

A description of the problem you're trying to solve.

TypeScript allows you to combine several types into one, however, sometimes you need to extract or omit special subset of fields from that "combined" object. I face the necessity of pick/omit when I work with React because React is unhappy when you pass non-dom properties to jsx, but I think that functions will be useful in other cases too.

interface LayoutStyles {
    display?: 'block' | 'flex'
    width?: number
    height?: number
    // many other fields
}

interface SpacingStyles {
    padding?: string
    margin?: string
    // many other fields
}

interface Props extends LayoutStyles, SpacingStyles, ButtonHTMLAttributes<HTMLButtonElement> {}

function Component(props: PropsWithChildren<Props>) {
    return (
        // Along with button html props we pass some attributes that doesn't exist in html lang
        // Moreover, we accidentally pass width and height attributes which exist in html 
        // and hence they will be added in the final markup, but it is not what we want
        <button
            {...props}
            style={{ display: props.display, width: props.width, height: props.height, padding: props.padding, margin: props.margin }}
        >
            {props.children}
        </button>
    )
}

An overview of the suggested solution.

Typia could have the following functions: pick - takes generic of what fields should be picked and the source object. It returns new object with picked fields from the generic. omit - takes generic of what fields should be omitted and the source object. It returns new object without fields from the generic. extract - takes generic of what fields should be extracted and the source object. It returns a tuple with two objects - the extracted fields and the rest fields. As you can see the pick and omit functionality can be achieved via the extract function, but I think to worth to leave all of them just for the purpose of simplicity and performance.

Along with other functions from Typia there can be functions like createPick, createOmit and createExtract.

Examples of how the suggestion would work in various places.

function Component(props: PropsWithChildren<Props>) {
    return (
        <button
            {...typia.misc.omit<LayoutStyles & SpacingStyles>(props)}
            style={typia.misc.pick<LayoutStyles & SpacingStyles>(props)}
        >
            {props.children}
        </button>
    )
}

or

function Component(props: PropsWithChildren<Props>) {
    const [style, rest] = typia.misc.extract<LayoutStyles & SpacingStyles>(props)

    return (
        <button {...rest} style={style}>
            {props.children}
        </button>
    )
}

Note about deep functionality - probably there will be cases when you want to deeply pick or omit object, but that feels much more complicated and I think it is worth to leave it for further improvements.

samchon commented 7 months ago

Is this feature what you want?

https://typia.io/docs/misc/#clone-functions

aspirisen commented 7 months ago

@samchon as I understand clone makes the same as pick. However, I am not sure that it can do omit.

samchon commented 7 months ago

Clone with Omit<T, K> type like below may what you want.

typia.misc.assertClone<Omit<T, "a"|"b">>(input);
aspirisen commented 5 months ago

@samchon it is not exactly as omit, it doesn't return rest properties.

For example

interface Test {
  a?: string;
  b?: string
}

const data: Record<string, any> = { c: 'a' }
const other = typia.misc.omit<Test>(data)

Using typia.misc.assertClone<Omit<Test, keyof Test>>(data); looks like does nothing

ps. looks like clone skips function properties, what also is not the same as pick

const $co0 = (input)=>({
          onPress: "function" === typeof input.onPress ? undefined : input.onPress
      });
  return "object" === typeof input && null !== input ? $co0(input) : input;
samchon commented 2 months ago

If still want this feature, can you write the function interface?

AlexRMU commented 1 month ago

I think he's talking about something similar: https://github.com/samchon/typia/issues/202 https://github.com/lodash/lodash/issues/3172

type T1 = {
    a: string;
};
type T2 = {
    b: string;
};
const obj: T1 & T2 = {
    a: "",
    b: "",
};
const { target, rest } = fn<T1>(obj);
// target is T1 - { a: "" }
// rest is T2 - { b: "" }

This cannot be done now due to TS limitations: https://stackoverflow.com/questions/76490186/use-type-parameters-with-exact-type-instead-of-extending

Therefore, the prune function (and similar functions) can change the shape of an object without changing its type: playground (https://github.com/samchon/typia/issues/1205)