简体   繁体   中英

Typescript: Create an interface or the narrowest type from an Object

Is there a way to create an interface or a narrow type from an object?

For example I would like something like:

const obj : someType = {
  a: "a",
  b: "b",
  c: {
    c: "c"
  }
};

type myType = narrowTypeOf obj

Whereas myType should then result into:

{
  a: string,
  b: string,
  c: {
    c: string
  }
};

Sadly (or better fortunately) the build in type mytype = typeof obj would result into someType , since I did already declare it to be of that type.

If there is a way to use an object as a parameter to a type definition - that would solve my problem aswell.

Unless SomeType is specifically a union in which one of the members is the narrow type you want, this isn't going to work. Let's first look at the happy case where SomeType is such a union, even though apparently that's not your use case:

type SomeType = {
  a: string,
  b: string,
  c: {
    c: string
  }
} | { foo: string } | { bar: string };

Here SomeType is explicitly the union of the type you want and two unrelated types. Then when you assign a value to obj annotated as SomeType ,

const obj: SomeType = {
  a: "a",
  b: "b",
  c: {
    c: "c"
  }
};

the type typeof obj captured at this point is the type you want:

type MyType = typeof obj;
/* type MyType = {
    a: string;
    b: string;
    c: {
        c: string;
    };
} */

The type MyType does not contain {foo: string} or {bar: string} . That's because the compiler uses control flow based type analysis to narrow union types upon assignment (see microsoft/TypeScript#8010 for the PR notes implementing this).

But there is no control flow based narrowing of non-union typed variables upon assignment. At one point the TS team seemed to be considering it , and there's an open suggestion (see microsoft/TypeScript#16976 ) for control flow narrowing of non-union types, but for now it's just not part of the language.

That means if your SomeType is just wider than you'd like but not specifically a union containing the element, you're stuck. Once you annotate a variable to be that wide type, all assignments to that variable will forget anything more specific about the current value of that type.


The right thing to do here is not to annotate the variable's type. The only reason why you should annotate a variable as a wider type than that of the value you assign is if you want to mutate the variable's contents later to make it some other inhabitant of that wider type. For example, if SomeType is given as this:

interface SomeType {
  a: string | number;
  b: unknown;
  c: object;
}

Then the only good reason why you would annotate the following declaration as SomeType :

const obj: SomeType = {
  a: "a",
  b: "b",
  c: {
    c: "c"
  }
};

Would be if you planned to do this later:

obj.a = 123;
obj.b = () => 456;
obj.c = [789];

Assuming you are not planning to need that wider type, then you should let the compiler infer the type of obj . If you are concerned that this will let you assign something incompatible with SomeType , then you can use a helper function which acts sort of like a narrowing annotation:

const annotateAs = <T>() => <U extends T>(u: U) => u;

This helper function can be used like this:

const asSomeType = annotateAs<SomeType>();

Now asSomeType() is a function which will return its input without widening, but will give an error if that input cannot be assigned to SomeType :

const oops = asSomeType({
  a: "a",
  b: "b",
  c: "c" // error! string is not an object
})

Now you can write the obj assignment like this:

const obj = asSomeType({
  a: "a",
  b: "b",
  c: {
    c: "c"
  }
});

That succeeded, so obj is definitely a SomeType , but now the type of obj is:

type MyType = typeof obj;
/* type MyType = {
    a: string;
    b: string;
    c: {
        c: string;
    };
} */

as you wanted.


Okay, hope that helps; good luck!

Playground link to code

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM