36

I am trying to experiment around destructuring assignment. Now I have a case which I trying to cop up with destructuring itself.

For example, I have an input like this:

let input = {latitude: "17.0009", longitude: "82.2108"}

Where latitude and longitude key values are strings, but I want to parse them into a number while destructuring.

let input = {latitude: "17.0009", longitude: "82.2108"}
let {latitude,longitude} = input

console.log(typeof latitude,typeof longitude)

I can see in babel repl that this takes a reference of an object and then access each key. So the above code is the same as:

"use strict";

 var arr = {
   latitude: "17.0009",
   longitude: "82.2108"
  };
 var latitude = arr.latitude,
     longitude = arr.longitude;

I want do something like using the destructuring syntax itself.

"use strict";

var arr = {
  latitude: "17.0009",
  longitude: "82.2108"
};
var latitude = Number(arr.latitude),
    longitude = Number(arr.longitude);

I am open to see some hacks too.

Update

I am able to come with up one hack with the , operator:

let arr = {latitude: "17.0009", longitude: "82.2108"}

let {lat,lng} = ({latitude,longitude} = arr, ({lat:+latitude,lng:+longitude}))

console.log(typeof lat, typeof lng)

On side note:- you must read Moritz Roessler's answer this is hacky but contains good knowledge and information

Code Maniac
  • 37,143
  • 5
  • 39
  • 60
  • `let input = {latitude: "17.0009"- 0, longitude: "82.2108"- 0}` While actually destructuring expressions aren't allowed only assignments. Before or after is allowed... – zer00ne Mar 16 '19 at 07:11
  • @zer00ne but i am not able to change input, for example take it as if it is coming from a third party. – Code Maniac Mar 16 '19 at 07:13
  • How about input as a return of a function? – zer00ne Mar 16 '19 at 07:17
  • [take this as reference](https://stackoverflow.com/a/55193878/9624435) what i am looking to do is `{lat:+lat,lng:+lng}` to just `{lat,lng}` if i am able to destructure as well as change parse it to number – Code Maniac Mar 16 '19 at 07:20
  • It looks like there's a type conversion on return – zer00ne Mar 16 '19 at 07:27
  • if the object is from a JSON string, the numbers can be converted in the [`JSON.parse` reviver](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/JSON/parse#Using_the_reviver_parameter) – Slai Mar 16 '19 at 08:09
  • The update does not work if you specify `use strict` because `latitude` and `longitude` on the left side of the comma operator are implicit global variables. – Chris Idzerda Mar 16 '19 at 18:25

8 Answers8

29

Destructuring is just a nice way to unpack properties from objects and arrays and assign them to variables. As the trasnpiled code in the question suggests, any kind of operation is not possible.

One hack would be to create 2 more variables (which don't exist in input) and set the default value to the number equivalent of the previously destrucutred properties:

let input = { latitude: "17.0009", longitude: "82.2108" }
let { latitude, longitude, lat = +latitude, long = +longitude } = input

console.log(typeof latitude, typeof longitude, typeof lat, typeof long)

The code approximately trasnpliles to this (Babel):

var latitude = input.latitude,
    longitude = input.longitude,
    lat = input.lat === undefined ? +latitude : input.lat,
    long = input.long === undefined ? +longitude : input.long;

It's just exploiting the order in which the variables are created and assigned property values. Again, this works only if there are no lat or long properties in input. Otherwise, it will fail the ternary condition and lat will be set to input.lat.


Something like this would be much easier to read though:

let { latitude, longitude } = input;
let lat = +latitude, 
    long = +longitude;

OR

let [ lat, long ] = [ +latitude, +longitude ]
adiga
  • 34,372
  • 9
  • 61
  • 83
  • 2
    Thanks for idea mate :) yeah that's what i did but with `,` operator which is less readable – Code Maniac Mar 16 '19 at 08:20
  • 3
    It'd be nice if we could just `let { +latitude } = …` –  Mar 16 '19 at 16:14
  • @Alhadis yeah it would be nice. but with current implementation it is not possible. cause when you do nested destructuring `lat obj = {a:{b:{c:'some'}}}; let {a:{b:c}} = obj` it is same as `let c = obj.a.b.c` so if you use `+` some where in between you wont be able to access propeties – Code Maniac Mar 18 '19 at 17:07
  • I love this one, it allows use of `const` as well. – Bibberty Mar 12 '20 at 20:45
12

You could destructure the values, take an array of the values and map the a new data type of the value and assign this values back to the variables.

let input = { latitude: "17.0009", longitude: "82.2108" },
    { latitude, longitude} = input;

[latitude, longitude] = [latitude, longitude].map(Number);

console.log(typeof latitude, latitude);
console.log(typeof longitude, longitude);
Nisarg Shah
  • 14,151
  • 6
  • 34
  • 55
Nina Scholz
  • 376,160
  • 25
  • 347
  • 392
6

You could have a reusable function, like this below:

const numberInputs = input =>
    Object.keys(input).reduce((acc, val) => {
        acc[val] = +input[val];
        return acc;
    }, {});

and then reuse it across...

Then do:

let {latitude,longitude} = numberInputs(input);

console.log(typeof latitude,typeof longitude) //number //number

and get 17.0009 and 82.2108 as numbers...

This way you keep your original object also and make a copy... so you have both original and copy of the object which has numbers as values...

Alireza
  • 100,211
  • 27
  • 269
  • 172
  • Thanks for inputs. i am able to do it with out function using `,` operator and destructuring as you can see in the update. but i am having hard time to incorporate the in map `arr.map((/Here i am trying to do the same what i am able to do with comma operatore/) => {})` can you help in that ? – Code Maniac Mar 16 '19 at 08:02
4

It's not possible - no operations can be performed on a property during destructuring. If you use destructuring to extract a property into a variable, that variable will be === to the original property value.

(of course, you could transform the original object's values to Number prior to destructuring, but that's not the same thing)

Snow
  • 3,820
  • 3
  • 13
  • 39
4

Whilst you cannot perform type conversion within the destructuring expression itself, a possible alternative/workaround could be to destructure the properties within the arguments of a function, and then return an array with the new types within it.

For example, something like the following:

const input = {latitude: "17.0009", longitude: "82.2108"}
const [lat, lng] = (({latitude:a, longitude:b}) => [+a, +b])(input);

console.log(typeof lat, typeof lng); // number number

However, for something like this, I wouldn't use destructuring and probably would resort to regular dot notation:

const input = {latitude: "17.0009", longitude: "82.2108"}
const lat = +input.latitude;
const lng = +input.longitude;

console.log(typeof lat, typeof lng); // number number
Nick Parsons
  • 45,728
  • 6
  • 46
  • 64
  • Thanks for inputs. i am able to do it with out function using `,` operator and destructuring as you can see in the update. but i am having hard time to incorporate the in map arr.map((/Here i am trying to do the same what i am able to do with comma operatore/) => {}) can you help in that ? – Code Maniac Mar 16 '19 at 08:02
  • 1
    Favor readability over hacky shortcuts - other users of your code and future you will thank you. Modern transpilers will do all the minification for you. So why not write in human readable form? – jayarjo Mar 16 '19 at 08:06
  • 1
    @CodeManiac hm, I couldn't come up with anything, but it seems like adiga has, I think their solution is what you're after – Nick Parsons Mar 16 '19 at 08:16
  • 1
    @NickParsons yeah mate his answer is cleaner than the `,` operator hack which i was doing – Code Maniac Mar 16 '19 at 08:21
2

There is a super hacky way of doing this using a getter defined on String.prototype as a helper function.

(You probably don't want to do that)

Object.defineProperty (String.prototype, "asNumber",{
   get: function () { return +this}
});
let input = {latitude: "17.0009", longitude: "82.2108"}
let {latitude:{asNumber:latitude},
     longitude: {asNumber:longitude}} = input

console.log (latitude, longitude)

Let's break that down into simpler steps.

//Extending the `String` prototype means every string 
//will have access to the defined property  via 
//its prototype, so
String.prototype.foo = function () {return `${this}.foo\`} 
//means you can now call foo() like any other string method
"bar".foo() //"bar.foo"`

//A getter allows you to define a function that acts 
//as a property which will be executed upon access. 
let obj = {get getter () {console.log ('Hi');}}
obj.getter // Hi

//Combine those two and you can call functions by 
//accessing properties of strings. 
Object.defineProperty (String.prototype, "asNumber",{
   get: function () { return +this}
});

//Now that you have a property that is available at 
//every string - and make it execute a function; you 
//can convert a string to a number, simply by
//accessing a property
"42".asNumber //42

//To make that work with destructuring assignment, 
//you need to know about another handy feature. You 
//can assign destructured properties to a new 
//variable name.
let {a:b, b:a} = {a:'a', b:'b'};
a; //'b'
b; //'a'

//Since you can nest destructuring assignments, and 
//every string implicitly has a 'asNumber' property, 
//you can destructure that property as well. 

let {lat: {asNumber}} = {lat: "42"};
asNumber //42

//The last thing to know is, there's apparently 
//nothing wrong with using an existing variable as 
//new name for a destructured property. So you can 
//just use the `asNumber` property from the 
//prototype and assign it to the same variable  
//you destructured from the object.
let {lat: {asNumber: lat}} = {lat: "42"};
lat; //42

There is nothing wrong with using the same name because only the last variable name will be introduced in the let block's scope

Moritz Roessler
  • 8,542
  • 26
  • 51
  • Yes i don't want, but still love see some more explanation about it – Code Maniac Mar 16 '19 at 15:50
  • 1
    @CodeManiac Sure (: glad to see people interested in the language. I added an explanation – Moritz Roessler Mar 16 '19 at 19:49
  • Can't ask for any better explanation :) this explanation contains so much of knowledge – Code Maniac Mar 16 '19 at 20:06
  • But one thing i want to know `let {a : {b}} = {'a' : {'b': 'some value }}` here the nested destructuring captures `b` as `some value`. than how `let {lat: {asNumber}} = {lat: "42"};` is capturing ? or is it same as `let {lat:asNumber} = {lat} = {lat: "42"};` ? – Code Maniac Mar 16 '19 at 20:11
  • @CodeManiac Yes, exactly, they are the same! (: – Moritz Roessler Mar 16 '19 at 20:15
  • I have experimented enough [here](https://stackoverflow.com/questions/54605286/what-is-destructuring-assignment-and-its-uses) but didn't knew this that i can use this nested one in such war. thanks mate :) – Code Maniac Mar 16 '19 at 20:19
2

If you don't mind using lodash, you can try this:

import { mapValues } from 'lodash';

const input = {latitude: "17.0009", longitude: "82.2108"}
const {latitude, longitude} = mapValues(input, Number);
calvin
  • 31
  • 1
0

I would probably set things up so that each "object type" I cared about had a corresponding "parser type": an object with the same keys, but whose values are the appropriate parsing functions for each member.

Like so:

"use strict";

var arr = {
    latitude: "17.0009",
    longitude: "82.2108"
};

function Parser(propParsers)
{
    this.propParsers = propParsers;
    this.parse = function (obj) {
        var result = {};
        var propParsers = this.propParsers;
        Object.keys(obj).forEach(function (k) {
            result[k] = propParsers[k](obj[k]);
        });
        return result;
    };
}

var parser = new Parser({
    latitude: Number,
    longitude: Number
});

let {latitude,longitude} = parser.parse(arr);
console.log(latitude);
console.log(longitude);
Derrick Turk
  • 4,246
  • 1
  • 27
  • 27