Home > other >  Is it possible to have optionally nested key-value pairs in TypeScript?
Is it possible to have optionally nested key-value pairs in TypeScript?

Time:07-22

I would like to have an object like this:

const foo = { ... }; // how?

console.log(foo.a); // '1'
console.log(foo.a.bar); // { 'prop1': 1, 'prop2': 2 }
console.log(foo.b); // '2'
console.log(foo.b.bar); // { 'prop1': 1, 'prop2': 2 }

So, the default value of foo.a is described in the comment after the statement with the value '1'. But I would also like to go deeper into the object, to access a deeper property bar, of which a is the parent.

Something to demonstrate the desired structure:

const foo = {
  a: '1',
  [a.bar]: { prop1: 1, prop2: 2 },
  b: '2',
  [b.bar]: { prop1: 1, prop2: 2 }
}

Is this possible with TypeScript without using a function to call it? So something like this would not be a good solution for this case:

foo.a();
foo.a().bar();

CodePudding user response:

Don't use this pattern.

Another version of what you seem to be asking is "Can I extend primitive types with additional properties"?

Is this possible?

Yes (sort of), but it's a code smell and I've never seen a compelling case for it. Here are two reasons why you shouldn't do it:

  1. Anyone reviewing the code (even your future self) won't be expecting extra properties on these types, which will make it harder to read and understand.

  2. Especially if the property names are going to be dynamic, there's a higher likelihood of overwriting/shadowing existing, standard property names on the object form of the primitive, which will almost certainly cause bugs.

With that out of the way — and, again, don't use this (you've been warned) — here's how you can do it:

References:

TS Playground

const bar = { prop1: 1, prop2: 2 };

const foo = {
  a: Object.assign('one', { bar }),
  b: Object.assign('two', { bar }),
};

/* The type of "foo" is:
{
  a: "one" & {
    bar: {
      prop1: number;
      prop2: number;
    };
  };
  b: "two" & {
    bar: {
      prop1: number;
      prop2: number;
    };
  };
}
*/

console.clear();

// This is a `String` (object), not a `string` (primitive):
console.log(foo.a); // String "one" { bar: { prop1: 1, prop2: 2 } }

// You can stringify it using any of these methods:
console.log(String(foo.a)); // "one"
console.log(foo.a.toString()); // "one"
console.log(`${foo.a}`); // "one"

console.log(foo.a.toUpperCase()); // "ONE"

console.log(foo.a.bar); // { prop1: 1, prop2: 2 }

console.log(foo.b); // String "two" { bar: { prop1: 1, prop2: 2 } }
console.log(String(foo.b)); // "two"
console.log(foo.b.toUpperCase()); // "TWO"
console.log(foo.b.bar); // { prop1: 1, prop2: 2 }

It would be better to devise a system for yourself of storing the value on a property on an object, (for example, as David suggested, using the value property).

CodePudding user response:

Well, there's a possible solution but it's maybe not exactly what you want. See when creating an object (for ex. a class), you have to possibility to give it a toString method. This way when the object is concatenated into a string. It executes your method instead. Here's an example:

const foo = {
  a: {
    prop1: 123,
    prop2: 456,
    toString(){
      return "1" }
    },
  b: {
    prop1: 123,
    prop2: 456,
    toString(){
      return "1"
    }
  }
}

// The problem is that if you want to get the `"1"` of an object. You have to convert it into a string like for example :

console.log(`${foo.a}`)
console.log(foo.a.toString())
console.log(""   foo.a)

// And of course you can still access every possible property

console.log(foo.a.prop1)
console.log(foo.b.prop2)
console.log(foo.b.prop1   ' - '   foo.b)

  • Related