Use destructuring to assign value to object property - javascript

I've used destructuring in various instances of my ES6 code but today I tried shortening a model instance declaration and it's not quite working in node. Basically, I'm pulling data from the YouTube Data API and storing it in my MongoDB instance. Accordingly, I created an object from the thumbnail portion of the response like so:
thumbnailData = {
smallWidth: element.snippet.thumbnails.default.width,
smallHeight: element.snippet.thumbnails.default.height,
smallURL: element.snippet.thumbnails.default.url,
medWidth: element.snippet.thumbnails.medium.width,
medHeight: element.snippet.thumbnails.medium.height,
medURL: element.snippet.thumbnails.medium.url,
highWidth: element.snippet.thumbnails.high.width,
highHight: element.snippet.thumbnails.high.height,
highURL: element.snippet.thumbnails.high.url
}
And I have a Mongoose schema like so:
const ThumbnailSchema = new Schema({
smallWidth: Number,
smallHeight: Number,
smallURL: String,
medWidth: Number,
medHeight: Number,
medURL: String,
highWidth: Number,
highHight: Number,
highURL: String
});
const Thumbnail = mongoose.model('thumbnail', ThumbnailSchema);
So I was trying to do something like this in my declaration:
let thumbs = new Thumbnail;
({ thumbs.smallWidth, thumbs.smallHeight, thumbs.smallURL } = thumbnailData);
But node.js throws a simple 'Unexpect token .' above my use of . in the left-hand side. Ideas if this is even possible? It's just killing me to have to write the object out in such long form but I can leave it if needs be because it does work. It seems much like the syntax below so not sure what the difference is, thanks.
var a, b;
({ a, b } = {a: 1, b: 2});

You can't destructure an object that way, Javascript recognizes the dots as property accesses, like #Bergi commented. One way you could minimize repetition however, is by destructuring default, medium, high out of the thumbnails property:
const { 'default': dflt, medium, high } = element.snippet.thumbnails;
let thumbs = new Thumbnail({
smallWidth: dflt.width,
smallHeight: dflt.height,
smallURL: dflt.url,
medWidth: medium.width,
medHeight: medium.height,
medURL: medium.url,
highWidth: high.width,
highHight: high.height,
highURL: high.url
});

Object.assign(thumbs, (({ smallWidth, smallHeight, smallURL}) => ({smallWidth, smallHeight, smallURL}))(thumbnailData));
A small IIFE that uses parameter destructuring would work. Or old stylish:
for(var key of ["smallWidth", "smallHeight", "smallURL"])
thumbs[key] = thumbnailData[key];

Your code
({ thumbs.smallWidth, thumbs.smallHeight, thumbs.smallURL } = thumbnailData);
will not work. Because the Js interpreter is not expecting the token . in the left hand that is considered as a property access. To make it work, you can use rather
({ a: thumbs.smallWidth, b:thumbs.smallHeight, c:thumbs.smallURL } = thumbnailData);
This will assign the value to your new object. Here is a working snippet:
var data = {a: "a", b: "b"};
( {a: data.a, b: data.b} = {a: 1, b : 2})
console.log(data.a)

Related

Typescript, turn Array of functions into merged type of all returned values

So I have a an array of functions (or actually an object of functions but it doesn't matter) which returns a different objects such as this:
const arr = [
() => ({ a: "a" }),
() => ({ b: "b" })
]
and now I want to get a type that contains all the merged values such as:
{
a: string;
b: string;
}
If tried some reduce solutions but all I've gotten to is a type that looks like:
{ a: string } | { b: string }
which isn't what I'm looking for.
Any ideas?
Update 1
The array in the example is a simplification and the actual return values of the functions are unique and is therefore needed to be kept as is => I cannot use a generalized interface such as
interface ReturnValues {
[key: string]: string;
}
Update 2
The problem is not of a JS kind but of TS and it's types. Ultimately I want to achieve this kind of functionality:
const result = arr.reduce((sum, fn) => Object.assign(sum, fn()), {})
and I want the type of result to be { a: string, b: string } so that I can call result.a and typescript will know that this is a string. If the result is { a: string } | { b: string }, calling result.a typescript says this is of the type any.
Also, for the ease of it, one can assume that there is no overlapping of the returning values of the functions.
you can use Array.reduce
const arr = [
() => ({ a: "a" }),
() => ({ b: "b" })
]
const obj = arr.reduce((acc, cur) => ({ ...acc, ...cur() }), {});
console.log(obj);
Since TypeScript doesn't have proper variadic type support yet (See this issue), the only real way to achieve what you're looking for is this:
const a = [{a:1},{b:2}] as const;
function merge<TA, TB>(a: TA, b: TB): TA & TB;
function merge<TA, TB, TC>(a: TA, b: TB, c: TC): TA & TB & TC;
function merge<TA, TB, TC, TD>(a: TA, b: TB, c: TC, d: TD): TA & TB & TC & TD;
function merge(...list: Array<any>): any {}
const b = merge(...a);
There are 3 primary methods of "mixing" javascript objects.
The process your looking to achieve is called a "mixin".
The older and more widely used method is to use whats called an extend function.
There are many ways to write an extend function, but they mostly look something like this:
const extend = (obj, mixin) => {
Object.keys(mixin).forEach(key => obj[key] = mixin[key]);
return obj;
};
here "obj" is your first object, and "mixin" is the object you want to mix into "obj", the function returns an object that is a mix of the two.
The concept here is quite simple. You loop over the keys of one object, and incrementally assign them to another, a little bit like copying a file on your hard drive.
There is a BIG DRAWBACK with this method though, and that is any properties on the destination object that have a matching name WILL get overwritten.
You can only mix two objects at a time, but you do get control over the loop at every step in case you need to do extra processing (See later on in my answer).
Newer browsers make it somewhat easier with the Object.Assign call:
Object.assign(obj1, mix1, mix2);
Here "obj1" is the final mixed object, and "mix1", "mix2" are your source objects, "obj1" will be a result of "mix1" & "mix2" being combined together.
The MDN article on "Object.Assign" can be found here:
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/assign
Like the extend function above "Object Assign" WILL overwrite properties in the destination object, but it does have the advantage of doing many at a time. My example above only shows 2 "mix" objects, but you can in theory have as many as you like, and that comes in really useful when you have them all in array as you have.
In an array you can either map the objects into one function, and then use the spread operator available in newer browsers, or you can use for..in to loop over the collection.
If your using JQuery, you can use it's foreach method, and underscore.js has dozens of ways of looping.
Since your using TypeScript you can also combine a lot of this with typescripts looping operators too.
There is a 3rd way of merging objects, it's not widely used but it is gaining traction, and that's the "Flight-Mixin" approach that uses the Array prototype, it looks something like this:
const EnumerableFirstLast = (function () { // function based module pattern.
const first = function () {
return this[0];
},
last = function () {
return this[this.length - 1];
};
return function () { // function based Flight-Mixin mechanics ...
this.first = first; // ... referring to ...
this.last = last; // ... shared code.
};
}());
EnumerableFirstLast.call(Array.prototype);
The idea here is that the two objects all ready have the functionality you require on them, so instead of "mixing" them, your just providing a single interface that delegates to them behind the scenes.
Beacuse your adding to the array prototype, you can now do things like the following:
const a = [1, 2, 3];
a.first(); // 1
a.last(); // 3
This might seem as if it's of no use, until you consider what you've in effect just done is added two new functions to a datatype you cannot normally control, this MIGHT if applied to your own objects allow you to add functions dynamically, that simply just grab the values you need to merge in a loop without too much trouble, it would however require a bit of extra planning which is why I'm adding this as more of an idea for further exploration rather than part of the solution.
This method is better suited for objects that are largely function based rather than data based as your objects seem to be.
Irrespective of which mixin method you use though, you will still need to iterate over your array collection with a loop, and you will still need to use spread to get all the keys and properties in one place.
If you consider something like
const myarr = [
{name: "peter", surname: "shaw"},
{name: "schagler", surname: "kahn"}
]
The way the spread operator works is to bust those array entries out into individual parts. So for example, IF we had the following function:
function showTwoNames(entryOne, entryTwo) {
console.log(entryOne.name + " " + entryOne.surname);
console.log(entryTwo.name + " " + entryTwo.surname);
}
You could call that function with the spread operator as follows:
showTwoNames(...myarr);
If your array had more than 2 entries in it, then the rest would be ignored in this case, the number of entries taken from the array is directly proportional to the number of arguments for the function.
You could if you wanted to do the following:
function showTwoNames(entryOne, entryTwo, ...theRest) {
console.log(entryOne.name + " " + entryOne.surname);
console.log(entryTwo.name + " " + entryTwo.surname);
console.log("There are " + theRest.length + " extra entries in the array");
}
Please NOTE that I'm not checking for nulls and undefined or anything here, it should go without saying that you should ALWAYS error check function parameters especially in JavaScript/TypeScript code.
The spread operator can in it's own right be used to combine objects, it can be simpler to understand than other methods like "ObjectAssign" beacuse quite simply you use it as follows:
var destination = { ...source1, ...source2, ...source3); // for as many sources as needed.
Like the other methods this will overwrite properties with the same name.
If you need to preserve all properties, even identically named ones, then you have no choice but to use something like an extend function, but instead of just merging directly using a for-each as my first example shows, you'll need to examine the contents of "key" while also looking in the destination to see if "key" exists and renaming as required.
Update RE: the OP's updates
So being the curious kind I am, I just tried your updated notes on one of my Linux servers, Typescript version is 3.8.3, Node is 12.14.1 and it all seems to work just as you expect it to:
I'm using all the latest versions, so it makes me wonder if your problem is maybe a bug in an old version of TS, or a feature that has only just been added in the newest build and is not present in the version your using.
Maybe try an update see what happens.
It seems that TypeScript doesn't have a native solution for this. But I found a workaround.
As mentioned in the question, using the reduce-method one gets a TS type of { a: string } | { b: string } (and to be clear, of course also a resulting object of { a: "a", b: "b" }.
However, to get from { a: string } | { b: string } to { a: string, b: string } I used the following snippet to merge the types:
type UnionToIntersection<U> = (U extends any
? (k: U) => void
: never) extends (k: infer I) => void
? I
: never;
So this would be my resulting code:
const arr = [
() => ({ a: "a" }),
() => ({ b: "b" })
]
const result = arr.reduce((sum, fn) => Object.assign(sum, fn()))
// Result is now { a: "a", b: "b" }
// but the TS type is '() => ({ a: string } | { b: string })'
type ResultUnion = ReturnType<typeof result>
// ResultUnion = { a: string } | { b: string }
type ResultIntersection = UnionToIntersection<ResultUnion>
// This is where the magic happens
// ResultIntersection = { a: string } & { b: string}
// It's not _exactly_ what I wanted, but it does the trick.
// Done

ImmutableJS Record merge

I have imported an immutable record and wanted to add a new property to that before using it as the default state for my application. However, I cannot add the property at all despite trying merge, mergeDeep, mergeWith and mergeDeepWith. It returns the calling Record in all instances. I decided to try merge after seeing this link.
The below code snippet simulates my problem
a = Immutable.Record({a:1, b:2})
b = a()
c = Immutable.Record({z:12})
d = c()
e = b.merge(d)
console.log(e.toJS())
e = b.mergeDeep(d)
console.log(e.toJS())
e = b.mergeWith(d)
console.log(e.toJS())
e = b.mergeDeepWith(d)
console.log(e.toJS())
<script src="https://cdnjs.cloudflare.com/ajax/libs/immutable/3.8.2/immutable.js"></script>
This does not work with v3.8.2 and even with v4.0.0-rc-9, in all cases, b is returned. I am looking for a solution using 3.8.2 itself not necessarily with merge
Not really experienced with immutable, so any help would be really appreciated.
Thanks in advance
Maps are an approximation of key-value objects so use those instead of Records which have a different paradigm of Factories "A record is similar to a JS object, but enforces a specific set of allowed string keys, and has default values.".
const a = Immutable.Map({ a: 1, b: 2 }); // or Immutable.fromJS({ ... })
const b = Immutable.Map({ z:12 });
const c = a.merge(b);
console.log(c);
<script src="https://cdnjs.cloudflare.com/ajax/libs/immutable/3.8.2/immutable.js"></script>

Merging types in typescript (adding keys to an existing keyof type)

If I have a typescript type consisting of keys:
const anObject = {value1: '1', value2: '2', value3: '3'}
type objectKeys = keyof typeof anObject
and then I wish to add keys to that type, while retaining the current keys, how do I go about doing that?
for example, if I wanted to add the keys 'get_value1', 'get_value2', 'get_value3' to the type 'objectKeys'
In the end, I want a type that looks like so:
type objectKeys = keyof anObject + 'get_value1', 'get_value2', 'get_value3'
without having to manually define the keys prefixed with 'get_', I understand that I can type out the keys to create this object - however that is not feasible for my use case. I simply want to add some keys that may or may not exist to the type 'objectKeys'
I am also aware that I can create a generic or any type that allows for any key value, however I must know the actual key names. It does not help me to allow for ANY key to be requested of the object, I need the existing keys + the ones I'd like to add.
Thanks for any help.
added for clarity:
const anObject = {val1: '1', val2: '2'}
type objectKeys = keyof typeof anObject
Object.keys(anObject).forEach(key => {
const getAddition = `get_${key}`
anObject[getAddition] = getAddition
})
// now I don't know whats next, how do I update objectKeys to include the
// additions added in the forEach loop.
// What I really want is to not have to add the 'get' values to the object
// at all, JUST to the type. I want typechecking for the get values that
// may or may not actually exist on the object.
hope thats clearerer and such.
It sounds like you're asking for concatenation of string literal types: that is, you want to be able to take the string literal "get_" and another string literal like "value1", and have TypeScript understand that if you concatenate strings of those types you get a string of the type "get_value1". Unfortunately, this feature does not exist as of TypeScript 2.4 (and probably won't exist in 2.5 or 2.6 either) 🙁.
So there's no way to do what you're asking for and maintain strict type safety. You can, of course, relax the type safety and allow access for any unknown key:
const anObject = {val1: '1', val2: '2'};
const openObject: { [k: string]: any } & typeof anObject = anObject;
// replace "any" above with whatever type the get_XXX values are
Object.keys(openObject).forEach(key => {
const getAddition = `get_${key}`
openObject[getAddition] = getAddition
})
openObject.val1 = 1; // error, val1 is known to be a string
openObject.get_val1 = 1; // no error, get_val1 is any
openObject.gut_val4 = 1; // no error, oops, sorry
but you said you don't want to do that.
In that case, the suggestion I'd make is to give up on adding arbitrary keys to the object, and instead make the getters (or whatever they are) hang off a single get property, like so:
const anObject = { val1: '1', val2: '2' }
type AnObject = typeof anObject;
type ObjectKeys = keyof AnObject;
type GetAugmentedObject = AnObject & { get: Record<ObjectKeys, any> };
// replace "any" above with whatever type the get.XXX values are
const get = {} as GetAugmentedObject['get'];
Object.keys(anObject).forEach((key: ObjectKeys) => get[key] = key);
const augmentedObject: GetAugmentedObject = { ...anObject, get }
augmentedObject.val1; // ok
augmentedObject.val2; // ok
augmentedObject.get.val1; // ok
augmentedObject.get.val2; // ok
augmentedObject.get.val3; // error, no val3
augmentedObject.git.val1; // error, no git
This is not very different for the developer (obj.get.val1 vs. obj.get_val1) but makes a big difference to TypeScript's ability to follow along. If you're in any control of the code that's adding the keys I strongly advise doing something TypeScript-friendly like this, since you don't want to spend your time fighting with TypeScript if you don't have to.
Otherwise, if only string concatenation at the type level will work for you, and you feel your use case is compelling enough, maybe you should go to the relevant GitHub issue and give it a 👍 and describe why it's a must-have for you.
Hope that helps. Good luck!
2020 Update
It's there in typescript v4.1.0
You probably need this - https://github.com/microsoft/TypeScript/pull/40336
You can use template literals.
Here's an example where the property id must be # + key:
interface MyInterface<K extends string> {
something: {
[key in K]: { id: `#${key}` }
}
}
So the following is correct:
let x: MyInterface<'foo'> = {
something: {
foo: { id: '#foo' }
}
}
But this is incorrect:
let y: MyInterface<'foo'> = {
something: {
foo: { id: 'foo' }
}
}

Difference between Object.assign and just assign

I would like to know the difference between this:
Object.assign(otherObject, {
someNewProperty: ''
});
and
otherObject.someNewProperty = '';
And.. which one is faster?
Thanks.
The Object.assign() method is used to copy the values of all
enumerable own properties from one or more source objects to a target
object. It will return the target object.
Whereas otherObject.someNewProperty = ''; is a method to directly assign a value to some property of an object.
Obviously the Object.assign pattern is much slower : jsperf.com/assign-vs-equals
For single property, direct assignment (otherObject.someNewProperty = '') is twice faster, but for multiple values - time will grow. Each property + 5% to 10%. Also, code-wise Object.assign is nicer for multiple options.
Object.assign(otherObject, {
prop1: '',
prop2: '',
prop3: '',
...
});
VS
otherObject.prop1 = '';
otherObject.prop2 = '';
otherObject.prop3 = '';
...
You simply can run Profiles tab in Chrome Development tool and run few tests.
Object.assign() is a pretty versatile function that is designed to do complex object composition.
The property dot notation is a straight forward way to assign a single value to a single property.
Regarding which is faster, that's irrelevant considering these are not equivalent, and as one of my all time favorite posts noted "asking which one runs faster is maybe a non-starter".
There is another important thing to show here about the differences between assign directly and using Object.assign(actually, not exactly a difference, but a important thing to be aware).
If you have a Object that's assigned to another variable in JS, like this:
const a = { a: 1 }
const b = a
then, you decided to use Object.assign to change the value in a and assign to another variable(d), you will change the value in b as well(even you not assigning the Object.assign return to a, in this example let's assign to a new variable d).
const d = Object.assign(a, { a: 2 })
console.log(a) // { a: 2 }
console.log(b) // { a: 2 }
console.log(d) // { a: 2 }
Basically, it's important to know that Object.assign will mutate the target object as well all variables pointing to it.
Now, if you use directly the assignment to d, it'll not change the value in a(and in b as well will not change).
const d = { ...a, ...{ a: 2 }}
console.log(a) // { a: 1 }
console.log(b) // { a: 1 }
console.log(d) // { a: 2 }
This is actually a good question:
We just found a bug, where we would assign properties to a file using Object.assign.
const file = new File(["foo"], "foo.txt", {
type: "text/plain",
});
file.name='test'; // does not update the readonly value but doesn't return an error
Object.assign(file,{name:'test'}); // error: 'Cannot set property name of #<File> which has only a getter'

Define a custom hash() method for use with ES6 maps

To illustrate the problem, consider the following simple object
function Key( val ) {
this._val = val;
}
Now I create a ES6 Map instance and feed one entry into it like this
var map = new Map(),
key1 = new Key( 'key' );
map.set( key1, 'some value' );
console.log( 'key1: ', map.has( key1 ) );
// key1: true
So far everything is fine. The challenge, however, comes up, if I create a nearly identical object key2 like this
var key2 = new Key( 'key' );
So basically both keys are identical, but obviously key2 is not part of the map
console.log( 'key2: ', map.has( key2 ) );
// key2: false
JavaScript uses the object references as a key here, so the two separate objects will not point towards the same value.
What I would like to do now is, to add something like a hash() method to key's prototype, so that both object would point to the same key. Is something like this possible?
I know, that there would be a way to circumvent the problem using a factory pattern for the Key generation together with some caching. However, this results in a lot of problem regarding immutability of the objects and the cache preventing old objects from being garbage collected. So I think that is not really an option.
Is something like this possible?
No, this is a known flaw of ES6 Collections. All they do is check for reference identity, and there is no way to change that.
The best thing you can do (if hash consing the instances is not an option as you say) is not to use objects for the keys. Instead, use strings that encode the Key values, and convert back and forth between the two representations. Given that you consider your keys to be immutable, this should not pose a problem.
I've created a class called CanonMap in my library big-m to encapsulate mapping by hash instead of reference.
By default, it works with tuples, Dates, and simple objects:
const { CanonMap } = "big-m";
const myMap = new CanonMap();
myMap.set(
["Farooq", "867-5309"],
36.59
);
myMap.get(
["Farooq", "867-5309"]
) === 36.59;
myMap.set(
{name: "Farooq", number: "867-5309"},
36.59
);
myMap.get(
{number: "867-5309", name: "Farooq"} // Insensitive to key ordering
) === 36.59;
myMap.set(new Date(2012, 6, 5), "Tuesday");
myMap.get(new Date(2012, 6, 5)) === "Tuesday";
It can also be extended with a custom "canonizer" function that determines how to hash values:
import {naiveCanonize, jsonCanonize, JsonCanonMap, CanonMap} from "big-m";
// Same as default canonizer, but with greater recursion depth (default is 2)
new CanonMap([], 6);
// Canonize by ID with fallback to naive
const customCanonMap = new CanonMap([
[{id: "TEST1", x: 7}, 77],
[{ x: 7 }, 88]
], lookup => lookup.id || naiveCanonize(lookup));
customCanonMap.get({id: "TEST1", x: 8}) === 77; // Ignores other values, uses ID
customCanonMap.get({x: 8}) === undefined; // Uses all fields, so lookup fails
// Default canonizer with JSON.stringify
new CanonMap([], jsonCanonize);
// equivalent to
new CanonMap([], lookup => JSON.stringify(lookup));
// also equivalent to
new JsonCanonMap();
Finally, to implement a CanonMap that makes use of a prototype hash function on the object itself, as you described, you could do something like this:
const selfHashingCanonMap = new CanonMap([], lookup => {
if ("hash" in lookup) {
return lookup.hash();
} else {
return naiveCanonize(lookup);
}
});

Categories

Resources