1

I want to elegantly copy a typescript struct interfaceName (which satisfies a certain interface). This TypeScript contains a map attribute3 of Type Map<IKey, number>, which perhaps has to be modified (depending on a condition): An additional key value pair has to be inserted.

IKey, again, is an interface.

interface InterfaceName{
   attribute1: string
   attribute2: string
   attribute3: Map<IKey, number>

}

interface IKey{
    a1: number
    a2: number
}

}

My attempt was the following: I just use the ... syntax to copy all the members of interfaceName and then try to assign attribute3 a modified copy. But somehow this does not work: In the end the same Map object is passed out of the function, regardless of the boolean.

const createModifiedCopyIfWished = (interfaceName: InterfaceName, wished: Boolean) => wished ? {
        ...interfaceName,
        attribute3: interfaceName.attribute3.set({a1:1,a2:2},5)
    }
    :
    interfaceName

let a: InterfaceName = {
    attribute1: "a",
    attribute2: "b",
    attribute3: new Map<IKey, number>()
}

let b = createModifiedCopyIfWished(a, true)

// {"attribute1":"a","attribute2":"b","attribute3":{}}
console.log(JSON.stringify(b))

What is the prober way to do this? Is it even possible within one statement?

Working example: LiveCode

JFFIGK
  • 632
  • 1
  • 7
  • 24
  • When you say "somehow this does not work", what do you mean? That both `InterfaceName` objects share a common `Map` instance and you want them to be different instances? There's a lot of stuff going on here that could cause you trouble (e.g., your `IKey` is not a great key type) and it's hard to pick apart with code that doesn't compile (what is `{1,2}`) and without a description of the problem other than "does not work". Please try to edit this into a [mcve] so someone can help you. Cheers! – jcalz Dec 31 '18 at 02:51
  • thanks for feedback. I corrected the code and specified the not working thing clearly. I see that IKey is not a great key type (because it could have more attributes which would make a different key), thus, I will consider making it a class. – JFFIGK Dec 31 '18 at 03:07

1 Answers1

1

Yes, it should be possible with one statement, like

const createModifiedCopyIfWished = (
  interfaceName: InterfaceName, 
  wished: boolean
) => wished ? {
  ...interfaceName,
  attribute3: new Map(
    [...Array.from(interfaceName.attribute3), [{ a1: 1, a2: 2 }, 123]]
  )
} : interfaceName

This is assuming you're trying to clone the map and not just modify the existing one.


Be warned, though, IKey is probably not a good key type for a Map. Map uses object equality based on identity (same exact object in memory) and not any kind of "same properties" equality. So, the following will not behave as one might expect:

const map: Map<IKey, number> = new Map();
map.set({ a1: 1, a2: 2 }, 123);
const val = map.get({ a1: 1, a2: 2});
console.log(val); // undefined !

That's because ({a1: 1, a2: 2} === {a1: 1, a2: 2}) is false. Unless you're very careful, using objects as map keys is a good way to lose them. The only way objects as map keys works is to store the actual object keys somewhere and use them later to look up entries in the map. And that's usually not what people want.

More reasonable behavior is probably just to use a string as your key and convert your intended key with something like JSON.stringify(objectKey) before putting it into the map or looking it up.


Anyway hope that helps. Good luck.

jcalz
  • 264,269
  • 27
  • 359
  • 360
  • thanks for the fast answer! Would it also be possible to instead use the plain constructor (like shown on the linked page) and call .set on the created object? Regarding the keys: I really did not expect that. So it is not possible at all without using the same object? A new string, again, is a different object in memory? the stringify thing would be quite ugly... – JFFIGK Dec 31 '18 at 03:19
  • You can use the regular constructor and call `set` if you want, yes. That looks nicer, too, I guess: `attribute3: new Map(interfaceName.attribute3).set({ a1: 1, a2: 2 }, 123)`. As for the keys: strings compare equal based on the value of the string, not the identity of the object. So `"foo" === "foo"` is `true`. It's definitely ugly, I agree. JavaScript doesn't have a native `Map` with user-defined equality. See [this question](https://stackoverflow.com/questions/29759480/how-to-customize-object-equality-for-javascript-set) for more info – jcalz Dec 31 '18 at 03:25
  • I don't know, I seem to miss something. If I take my code, your code or the fancy version from the comments here, neither one does work. You can find a link to typescriptlang.com/play at the end of my post. – JFFIGK Dec 31 '18 at 03:50
  • You can't `JSON.stringify()` a `Map`. Try inspecting the actual map entries, or do something like `JSON.stringify(Array.from(b.attribute3))` – jcalz Dec 31 '18 at 03:59
  • ... so it worked right away (besides that it was not a copy, but the modified source)... I was fooled by Redux Dev Tools. Thanks, you were a big help. – JFFIGK Dec 31 '18 at 04:26