Skip to content

Optional object literal typing #1912

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
NN--- opened this issue Feb 3, 2015 · 18 comments
Closed

Optional object literal typing #1912

NN--- opened this issue Feb 3, 2015 · 18 comments
Labels
By Design Deprecated - use "Working as Intended" or "Design Limitation" instead

Comments

@NN---
Copy link

NN--- commented Feb 3, 2015

Currently it is not possible to set a type for a key in the object literal.

interface A {
    x: number;
    y: string;
}

var a = {
    // I would assume the following syntax
    // <A>q: { ... }
    q: {
        x: 1,
        y: "a"
    }
}

However it is possible to cast a literal value but it is not the same.

interface A {
    x: number;
    y: string;
}

var a = {
    // q has type 'A', and we can write anything that compatible to that literal.
    q: <A>{
        x: 1,
        y: "a"
    }
}
@JsonFreeman
Copy link
Contributor

Right, casting is not the same because it would allow an object that is not necessarily an A. But you can do this:

var a: { q: A } = {
    q: {
        x: 1,
        y: "a"
    }
}

@danquirk
Copy link
Member

danquirk commented Feb 3, 2015

Jason's suggestion is the solution here. There's no good syntax for specifying the type on a per property basis, similar to trying to do the same inside a destructuring pattern.

@danquirk danquirk closed this as completed Feb 3, 2015
@danquirk danquirk added the By Design Deprecated - use "Working as Intended" or "Design Limitation" instead label Feb 3, 2015
@NN---
Copy link
Author

NN--- commented Feb 4, 2015

@danquirk it solves it but it requires to describe the whole structure of the object.
i would like to see a syntax for typing the key in-place in the future .

@danquirk
Copy link
Member

danquirk commented Feb 4, 2015

If you use normal type annotation syntax then it's extremely awkward, if not actually ambiguous in some cases. If you don't, then you're inventing a new type annotation syntax for this one corner case.

var a = {
    q: A: { x: 1, y: 'a' }
}

The vast majority of the time a cast at the property level suffices, or else you can use an annotation of the full object.

@mpawelski
Copy link

I have a question related to this one.
I just noticed that in 1.4 version this compiles fine:

interface ITest {
    foo: string;
    bar: string;
}

var test2 = <ITest>{
    foo: "foo"
}

I thought, when casting the object must this be compatible too. I remember that I used to do things like this to workaround this

var test2 = <ITest><any>{
    foo: "foo"
}

Am I wrong or it just got changed some time ago?

About the original question. I think the ability to specify type for literal and have compiler check it would be very helpful in certain situations. I have some places in code where the type is not strongly typed but for certain object literals I have information what should be their type so I would like to annotate them with certain type so compiler would produce error in this place when the type changes.

It is also strange I get Intellisense when I cast and rename refactoring works but compiler doesn't enforce that this literal match the type:
typescript_cast

@RyanCavanaugh
Copy link
Member

An expression of the form <T>U is valid if U is assignable to T or T is assignable to U (otherwise the type assertion operator would be useless). You're much better off with var test: ITest to put a contextual type on the initializer than by using <T>{ ... }.

@JsonFreeman
Copy link
Contributor

Yes, to paraphrase @RyanCavanaugh's point, a type annotation

var test: ITest = { foo: "foo" };

means that the object has to be an ITest, which is why it will give an error if the object is missing bar. However, the cast

var test = <ITest>{foo: "foo" };

means that the object might be an ITest, and that the compiler should consider it an ITest. In this case, the compiler checks that ITest can be assigned to your object, because that is proof that the object might be an ITest. This feature should be used when you somehow know that the object will be an ITest at runtime, even though it does not look statically compatible with ITest.

@NN---
Copy link
Author

NN--- commented Feb 26, 2015

Here the case where I need it:.

This is declaration code

interface B  {
}
declare var B: {
    prototype: B;
    f(): B;
}

And I want to define B.
I have to cast the right value instead of defining type in the left side.
'prototype:< B >null' instead of smth like '< B >prototype: null'

interface B  {
}
declare var B: {
    prototype: B;
}

var B = {
    prototype:<B>null
}

@JsonFreeman
Copy link
Contributor

Ah, you are essentially splitting up the typing of B and the initialization of B into two steps. But you can also combine them:

var B: {
    prototype: B;
} = {
    prototype: null
};

@JsonFreeman
Copy link
Contributor

And if you want to do it in two steps, you can also do:

var B: {
    prototype: B;
};
B = {
    prototype: null
};

@NN---
Copy link
Author

NN--- commented Feb 26, 2015

I can but I already have a definition file that I cannot change since it comes from NuGet package.

@JsonFreeman
Copy link
Contributor

Then perhaps you just want

B = {
    prototype: null
}

Note the absence of the word var. The word var was causing you to need the cast before.

This does assume that a var B is already declared in the relevant scope. But if your definition file declares var B, it is implying that the definition is indeed in scope.

@NN---
Copy link
Author

NN--- commented Feb 26, 2015

The definition declares it but it is not defined in all environments .
Specifically I have a unit test and it runs without the variable defined, so I must define it with some stub of my own.

@JsonFreeman
Copy link
Contributor

Ok, then I would go back to what I had said before:

var B: {
    prototype: B;
} = {
    prototype: null
};

I agree this seems cumbersome, but I think the problem is that your definition file does not really reflect the environment of your unit test.

Maybe the feature you want is something that merges declarations for vars, the same way declarations are merged for interfaces. If we had a feature like that, you could do

var B = {
    prototype: null
};

@JsonFreeman
Copy link
Contributor

And to finish the thought, each initializer of the variable would have to have a type assignable to the aggregate type of all the type annotations of the declarations.

@JsonFreeman
Copy link
Contributor

And zero type annotations implicitly gives the variable type 'any'.

@JsonFreeman
Copy link
Contributor

Hmm, this looks like something entirely different. There is no property you could write there that has the semantics you want, you would have to assign it a function instead of an object.

QQ = function() { };

Though in this case TypeScript would give you an error.

When dealing with construct signatures, you're better off declaring a class, rather than an interface and a var. So instead, you might have:

declare class QQ { }

And now QQ will have the correct type. But you should only do this if you don't have to redeclare it later. So if your unittest has

class QQ { }

then don't include the declare class.

@NN---
Copy link
Author

NN--- commented Feb 27, 2015

Managed it. Thanks.
I need to specify the type and the initialization.
The only problem I had is that the declared variable type had 'new' and for this I must define a class.

@microsoft microsoft locked and limited conversation to collaborators Jun 18, 2018
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
By Design Deprecated - use "Working as Intended" or "Design Limitation" instead
Projects
None yet
Development

No branches or pull requests

5 participants