0

Let say there is a type A and which will be a type of object property objA:

    type A = {
      a: number
    }

I would like to have an object to have objA and other properties will be expected to be type string.

However, the following using Index Signatures will make the value has more types.

    type B = {
      objA: A;
      [index: string]: string | A; // cannot be `string` here or it will error
    }
    
    const b: B = {
      objA: {
        a: 1
      },
      someText: `some string`,
    }
    
    b.someText // expect `someText` to be string, but got `string | A`
       // ^?

But the above can resolve objA correctly.

Please notice that const b have to be discourage excess properties during direct defining object, but may or may not for object references. Or in other words, it need to be Stricter object literal assignment checks:


const b: B = {
  objA: {
    a: 1,
    b: 2, // <-- does get error.
  },
  someText: `some string`,
}

Following is acceptable.

const objA = { a: 1, b: 2 }

const b: B = {
  objA, // <-- object reference which cannot forbid excess properties  maybe allowed. No error here.
  someText: `some string`,
}

Below were other attempts.


    type C = {
      [a in Exclude<string, "objA">]: string;
    }
    const c: C = {
      objA: { // error: Type '{ a: number; }' is not assignable to type 'string'.(2322)
        a: 1
      },
      someText: `some string`,
    }
 
    type E = {
      [a in Exclude<string, "objA">]: string
    } & {
      objA: A,
      default: string,
    }
    
    // Error:
    // Type '{ objA: { a: number; }; someText: string; }' is not assignable to type 'E'.
    //   Type '{ objA: { a: number; }; someText: string; }' is not assignable to type '{ [x: string]: string; }'.
    //     Property 'objA' is incompatible with index signature.
    //       Type '{ a: number; }' is not assignable to type 'string'.(2322)
    const e: E = {
      objA: {
        a: 1
      },
      someText: `some string`,
    }

Playground Link

Any ideas?

Edwin
  • 395
  • 2
  • 12
  • There is no specific type corresponding to your requirement, so the best we can do is a generic helper function that validates inputs. For example, does [this approach](https://tsplay.dev/w2PPzm) meet your needs? If so I can write up an answer; if not, what am I missing? – jcalz Jun 13 '22 at 03:54
  • @jcalz Thanks for your suggestion. Your approach with generic solved part of the problem, but now TS cannot check inner type A. [Example: Playground](https://tsplay.dev/w1PPGW) – Edwin Jun 13 '22 at 10:07
  • 1
    That's a completely separate issue with TS; excess properties are not considered type errors; `{a: 1, b: 2}` is assignable to the type `{a: number}` (see [this example](//tsplay.dev/WKyyMN)). There are [excess property checks](//www.typescriptlang.org/docs/handbook/release-notes/typescript-1-6.html#stricter-object-literal-assignment-checks) which act more like a linter rule than a type safety rule, but they only trigger in places where the compiler would forget the extra properties. The generic function preserves these properties in the type of the output, so they are not considered errors. – jcalz Jun 13 '22 at 13:13
  • 1
    There's a *lot* to be said about excess property warnings in TypeScript and it seems to me to be out of scope for the question as asked. See [this Q/A](https://stackoverflow.com/a/70701968/2887218). Still, if this is important to you, you should consider [edit]ing the question to clarify that you want your type to forbid excess properties on the `A` member, and then you'll get a huge answer with all kinds of tricks to try to keep excess property checking, but with a bunch of caveats about how the compiler can't *really* prevent them anyway, so you might as well embrace them. – jcalz Jun 13 '22 at 13:16
  • 1
    So, what do you want to see here? There's [this approach](https://tsplay.dev/weppEW) but it's not foolproof. What kind of answer should I write up? – jcalz Jun 13 '22 at 13:23
  • I can understand there are many limitations in typescript and there are trade offs. Both of your approaches were great. Maybe I need to make the question a bit more clear on keeping the usage like `const b: B`. To be honest, the question type B originally **cannot** excess properties as initialisation. Your first approach changed the usage, as you said it was support since TS 1.6. Your second approaches does fit the original case. – Edwin Jun 14 '22 at 05:09
  • To make the question a bit more clear, I will add `forbid excess properties` in the question. Please feel free to write it up as an answer and I will accept it. Thanks for your detailed expiation and the example scripts about the trade offs between your approaches! – Edwin Jun 14 '22 at 05:09
  • I'm saying that it is impossible to actually *forbid* excess properties. All you can do is discourage them. Even your original version has this problem if you use an intermediate variable, like [this](https://tsplay.dev/w6P1Gm). I am happy to write up the answer that *discourages* excess properties, but that's all it is. ... – jcalz Jun 14 '22 at 15:35
  • ... If you have code which will do something terrible at runtime if there are excess properties, you should still consider protecting against that instead of relying on TypeScript to forbid them. It surely seems like this is not the original scope or intent of your question, though. So if I do write up that version, I'll probably just point to existing q/a about excess properties instead of explaining what's going on here. – jcalz Jun 14 '22 at 15:35
  • @jcalz Sorry for my poor understanding and language skills. I have updated the question. Thanks for your kindness, patient, and being professional. I hope this time I have represent the question correctly. – Edwin Jun 18 '22 at 08:53

0 Answers0