Is it possible to do conditional destructuring or have a fallback? - javascript

I have an object that has lots of deeply nested properties. I want to be able to access properties on "MY_KEY" (below), but if that doesn't exist then get "MY_OTHER_KEY". How can I accomplish that?
const {
X: {
Y: {
MY_KEY: {
Values: segments = []
} = {}
} = {}
} = {}
} = segment;

You could achieve this using a temporal variable inside your destructuring assignment, something like this:
function destructure(segments) {
const {
X: {
Y: {
MY_OTHER_KEY: _fallback_value = {},
MY_KEY: {
Values: segment = []
} = _fallback_value,
} = {},
} = {},
} = segments;
return segment;
}
console.log(destructure({})); // []
console.log(destructure({X:{}})); // []
console.log(destructure({X:{Y:{MY_KEY:{Values:"A"}}}})); // A
console.log(destructure({X:{Y:{MY_OTHER_KEY:{Values:"B"}}}})); // B
console.log(destructure({X:{Y:{MY_OTHER_KEY:{Values:"C"}, MY_KEY:{Values:"D"}}}})); // D
First of all, this kind of destructuring will attempt to extract the second key all the time, which might have some unintended implications, like a property getter for MY_OTHER_KEY will always run.
However I fail to see the beauty in it. Hiding some control flow inside destructuring is just confusing. I would rather suggest extracting the parent object and use regular property access on it:
function destructure(segments) {
const {
X: {
Y: nested = {},
} = {},
} = segments;
const selected = nested.MY_KEY || nested.MY_OTHER_KEY || {};
const {
Values: segment = []
} = selected;
return segment;
}
console.log(destructure({})); // []
console.log(destructure({X:{}})); // []
console.log(destructure({X:{Y:{MY_KEY:{Values:"A"}}}})); // A
console.log(destructure({X:{Y:{MY_OTHER_KEY:{Values:"B"}}}})); // B
console.log(destructure({X:{Y:{MY_OTHER_KEY:{Values:"C"}, MY_KEY:{Values:"D"}}}})); // D

While object destructuring is cool and new, it is not the best in all cases. May do:
try {
var segments = segment.X.Y.MY_KEY.Values;
} catch(e){
//whatever
}

Check if the target is defined
let {/* do destructuring stuff */}
if (MY_KEY === undefined) {
// define `MY_OTHER_KEY` variable here
}
If you are trying to assign target to a different property if the previous target definition is undefined you can assign the variable at subsequent target definition
const segment = {Y:123};
let or = "Y";
let X;
({X, X = X || segment[or]} = segment);
console.log(X);

Related

Is there a shorter way to write this code, assign a value to a sub object?

I'm working with nodeJs and I would like to assign a sub value to an object without to know if the «three» exist.
E.g : I would like to assign True to «global.proc.trackingMarketExport.started» without check if :
proc exist in global
trackingMarketExport exist in global.proc
....
Actually I'm writing this :
!global?.proc && ( global.proc = {})
!global.proc?.trackingMarketExport && ( global.proc.trackingMarketExport = { started: false })
For sample :
global?.proc?.trackingMarketExport?.started = true;
Thanks in advance for your help
Edit: Perhaps you're asking how to ensure that the target object exists and that its started property is set to true?
If that's the case you can use the logical nullish assignment operator (??=):
(((global ??= {}).proc ??= {}).trackingMarketExport ??= {}).started = true;
let global = undefined;
(((global ??= {}).proc ??= {}).trackingMarketExport ??= {}).started = true;
console.log(global);
You can use this syntax to assign it to the target object (if it exists) or to an anonymous, ephemeral object that will be garbage collected (if it doesn't):
(global?.proc?.trackingMarketExport ?? {}).started = true;
Here's a code snippet you can re-run repeatedly to see randomized results. Note that in both cases no error occurs:
let global = undefined;
if (Math.random() < 0.5) {
global = {
proc: {
trackingMarketExport: {},
},
};
}
(global?.proc?.trackingMarketExport ?? {}).started = true;
console.log(global);
Not sure if this is short enough, but...
var started = ((global.proc) && (global.proc.trackingMarketExport))? true : false;
if (started) { global.proc.trackingMarketExport = started; }
You could write something that walks the tree and sets an object if it does not exist. Many ways to write it.
const setObj = (obj, path, value) => {
const parts = path.split('.');
const lastKey = parts.pop();
const lastObj = parts.reduce((acc, key) => {
acc[key] = acc[key] || {};
return acc[key];
}, obj);
lastObj[lastKey] = value;
}
const path = 'proc.trackingMarketExport.started';
const value = false;
const x = {};
setObj(x, path, value);
console.log(x);
const y = {
"proc": {
"foo" : "baz",
"trackingMarketExport": {
"funky": {
"boo" : "bah"
}
}
}
};
setObj(y, path, value);
console.log(y);
while loop
const setObj = (obj, path, value) => {
const parts = path.split('.');
let key;
let walker = obj;
while (key = parts.shift()) {
walker[key] = parts.length ? (walker[key] || {}) : value;
walker = walker[key];
}
}
const path = 'proc.trackingMarketExport.started';
const value = false;
const x = {};
setObj(x, path, value);
console.log(x);
const y = {
"proc": {
"foo" : "baz",
"trackingMarketExport": {
"funky": {
"boo" : "bah"
}
}
}
};
setObj(y, path, value);
console.log(y);
Check out the Object.assign() method, which copies all properties to a target object and overrides it.
Object.assign(global, {
proc: {
trackingMarketExport: {
started: true
}
}
})
Then you can just put it in one line:
var globalDemo = {
otherProperties: {
a: 1,
b: 2
}
}
Object.assign(globalDemo, { proc: { trackingMarketExport: { started: true }}});
console.log(globalDemo);
console.log(globalDemo.proc.trackingMarketExport.started);
However, as mentioned in the comments, this technique will only work in your specific given scenario, because the other properties inside proc and trackingMarketExport will also be overridden and therefore deleted.

Javascript building an object directly

I have these values:
let buffer = {};
let value = 'value';
let x = 1;
let y = 2;
This is what I want to do:
buffer[x][y].value = value;
This is what I need to do, in order for it to work:
buffer[x] = {};
buffer[x][y] = {};
buffer[x][y].value = value;
My guess is that there is a better, maybe built in way to create an object like this in one step instead of three.
My guess is that there is a better, maybe built in way to create an object like this in one step instead of three.
What you have is fine, but you can also do it with computed properties (I'm assuming you need the values from variables) and shorthand property notation (for value):
let value = 'value';
let x = 1;
let y = 2;
let buffer = {
[x]: {
[y]: {
value
}
}
};
console.log(buffer);
If you want to create nested object based on a set of keys, you could use reduceRight. Use the rest parameters syntax to get the value and the array of paths as separate variables. Set the value as the initialValue parameter and add a level of nesting in each iteration
const create = (value, ...paths) => paths.reduceRight((r, k) => ({ [k]: r }), value)
console.log(create("final value", "1", "2", "value"))
console.log(create("final value", "any", "number", "of", "keys"))
Like this?
const buffer = {
1: {
2: {
value: 'value'
}
}
};
If x and y are already defined, then use computed properties:
let x = 1;
let y = 2;
const buffer = {
[x]: {
[y]: {
value: 'value'
}
}
};
You could reduce the given keys and assign the value at the last step.
const
setValue = (object, path, value) =>
path.reduce((o, k, i, { length }) => o[k] = i + 1 === length
? value
: o[k] || {}, object);
let buffer = {};
let value = 'value';
let x = 1;
let y = 2;
setValue(buffer, [x, y, value], value);
console.log(buffer);

How to find object value from property path in Javascript object

I explain the problem with sample.
For example i have javascript object. It looks like:
var trial= { points:[{x:1,y:2},{x:5,y:2},{x:3,y:4}] , obj:{id:5,name:"MyName"} }
I use deep diff module to find difference between two json array. Then it find the differences and find difference path. If value of x is changed, then it finds.
For example
path = ["points",0,"x"]
or
path= ["obj","name"]
So my question is that how to generate json object from these path.
For example i have to generate that
trial.points[0].x or trial.obj.name
How to do that ? thank you for your answer.
You can use the following logic:
for(var i = 0, result = trial; i < path.length; i++) {
result = result[path[i]];
}
You can use array#reduce and pass your object and path as variables. Array#reduce will return the value corresponding to a path.
var trial= { points:[{x:1,y:2},{x:5,y:2},{x:3,y:4}] , obj:{id:5,name:"MyName"} },
path1 = ["points",0,"x"],
path2= ["obj","name"],
valueAtPath = (object, path) => path.reduce((r,p) => r[p], object);
console.log(valueAtPath(trial, path1));
console.log(valueAtPath(trial, path2));
You can do like this:
var trial= { points:[{x:1,y:2}, {x:5,y:2},{x:3,y:4}] , obj:{id:5,name:"MyName"}};
var path = ["points", 0, "x"];
var object = trial;
path.map(field => object = object[field]);
console.log(object);
path = ["obj", "name"];
var object = trial;
path.map(field => object = object[field]);
console.log(object);
Disclosure: I am the author of both deep-diff and json-ptr.
To build an object out of an array of attribute names such as [ "points", 0, "x" ], use a good JSON Pointer implementation, since most of them have convenience methods for translating these paths, and applying values to object graphs.
For example (Node.js):
const diff = require("deep-diff");
const ptr = require("json-ptr");
let original = {
points: [
{ x: 1, y: 2 },
{ x: 5, y: 2 },
{ x: 3, y: 4 }
],
obj: {
id: 5,
name: "MyName"
}
};
let modified = JSON.parse(JSON.stringify(original));
modified.points[0].x = 7;
modified.obj.name = "Wilbur Finkle";
const differences = diff(original, modified);
// Produce an object that represents the delta between original and modified objects
const delta = differences.reduce((acc, record) => {
// Only process edits and newly added values
if (record.kind === "E" || record.kind === "N") {
ptr.set(
acc, // target
ptr.encodePointer(record.path), // pointer; from path
record.rhs, // modified value
true // force; creates object graph
);
}
return acc;
}, {});
console.log(JSON.stringify(delta, null, " "));
Produces:
{ points: [ { x: 7 } ], obj: { name: "Wilbur Finkle" } }

How do I convert an environment variable to an object in JS?

I'm trying to convert an environment variable into an object of values for configuration in JavaScript but I don't know the best way to achieve this.
The idea would be to output SAMPLE_ENV_VAR=value as:
{
sample: {
env: {
var: value
}
}
}
What I have so far:
const _ = require('lodash');
const process = require('process');
_.each(process.env, (value, key) => {
key = key.toLowerCase().split('_');
// Convert to object here
}
Here's a more complete solution based on yours:
const _ = require('lodash');
const result = {};
// We'll take the following as an example:
// process.env = { HELLO_WORLD_HI: 5 }
// We'll expect the following output:
// result = { hello: { world: { hi: 5 } } }
_.each(process.env, (value, key) => {
// We'll separate each key every underscore.
// In simple terms, this will turn:
// "HELLLO_WORLD_HI" -> ['HELLO', 'WORLD', 'HI']
const keys = key.toLowerCase().split('_');
// We'll start on the top-level object
let current = result;
// We'll assign here the current "key" we're iterating on
// It will have the values:
// 'hello' (1st loop), 'world' (2nd), and 'hi' (last)
let currentKey;
// We'll iterate on every key. Moreover, we'll
// remove every key (starting from the first one: 'HELLO')
// and assign the removed key as our "currentKey".
// currentKey = 'hello', keys = ['world', 'hi']
// currentKey = 'world', keys = ['hi'], and so on..
while ( (currentKey = keys.shift()) ) {
// If we still have any keys to nest,
if ( keys.length ) {
// We'll assign that object property with an object value
// result =// { HELLO: {} }
current[currentKey] = {};
// And then move inside that object so
// could nest for the next loop
// 1st loop: { HELLO: { /*We're here*/ } }
// 2nd loop: { HELLO: { WORLD: { /*We're here*/ } } }
// 3rd loop: { HELLO: { WORLD: { HI : { /*We're here*/ } } } }
current = current[currentKey];
} else {
// Lastly, when we no longer have any key to nest
// e.g., we're past the third loop in our example
current[currentKey] = process.env[key]
}
}
});
console.log(result);
To simply put:
We'll loop through every environment variable (from process.env)
Split the key name with an underscore, and, again, loop each key (['HELLO', 'WORLD', 'HI'])
Assign it to an object ({ hello: {} } -> { hello: { world: {} } } -> { hello: world: { hi: ? } } })
When we no longer have any keys left, assign it to the actual value ({ hello: { world: { hi: 5 } } })
Funnily enough, I just finished code for this last night for a personal project. What I ended up using is not ideal, but is working for me:
export function keyReducer(
src: any,
out: any,
key: string,
pre: string,
del: string
): ConfigScope {
const path = key.toLowerCase().split(del);
if (path[0] === pre.toLowerCase()) {
path.shift();
}
if (path.length === 1) { // single element path
const [head] = path;
out[head] = src[key];
} else {
const tail = path.pop();
const target = path.reduce((parent: any, next: string) => {
if (parent[next]) {
return parent[next];
} else {
return (parent[next] = <ConfigScope>{});
}
}, out);
target[tail] = src[key];
}
return out;
}
static fromEnv(env: Environment, {prefix = 'ABC', delimiter = '_'} = {}) {
const data: ConfigScope = Object.keys(env).filter(key => {
return StringUtils.startsWith(key, prefix);
}).reduce((out, key) => {
return keyReducer(env, out, key, prefix, '_');
}, <ConfigScope>{});
return new Config(data);
}
(with TypeScript type annotations)
The idea here is to split each key, create the target objects on the way down, then set the final value.
This is my quick take at it:
var object = {}; // the object to store the value in
var name = "SAMPLE_ENV_VAR"; // the environment variable key
var value = "value"; // the value of the environment variable
// helper function to automatically create an inner object if none exists
function getOrCreateInnerObj(obj, name) {
if (!obj.hasOwnProperty()) {
obj[name] = {};
}
return obj[name];
}
// an array of the individual parts (e.g. ["sample", "env", "var"])
var keyParts = name.toLowerCase().split("_");
// innerObj will contain the second to last element object in the tree based on the array of keys
var innerObj = getOrCreateInnerObj(object, keyParts[0]);
for (var i = 1; i < keyParts.length - 1; i++) {
innerObj = getOrCreateInnerObj(innerObj, keyParts[i]);
}
// set the value itself
innerObj[keyParts[keyParts.length - 1]] = value;
$("body").html(JSON.stringify(object));
The gist of it is, for all but the last element in the array of key parts, you get or create an object in the current parent object for that key, and once you've repeated this for all but the last key, you'll have the second-to-last inner object, which you can then set the value on.
Edit: Working example
Edit 2: Here is a much cleaner example that uses a little bit of recursion to accomplish the same thing
const basic = {};
let current;
`YOUR_VARIABLE_NAME`
.split(`_`)
.forEach((item, index, array) => {
if(index === 0) {
return current = basic[item] = {};
}
if(index === array.length - 1) {
return current[item] = process.env.HE_LO_NA;
}
current = current[item] = {};
});
console.log(require('util').inspect(basic, {depth: 10}));
const _ = require('lodash');
const process = require('process');
const result = Object.entries(process.env).reduce((acc, [key, value]) => {
_.set(acc, key.toLowerCase().replace('_', '.'), value);
return acc;
}, {})

Defaultdict equivalent in javascript

In python you can have a defaultdict(int) which stores int as values. And if you try to do a 'get' on a key which is not present in the dictionary you get zero as default value.
Can you do the same in javascript/jquery
You can build one using a JavaScript Proxy
var defaultDict = new Proxy({}, {
get: (target, name) => name in target ? target[name] : 0
})
This lets you use the same syntax as normal objects when accessing properties.
defaultDict.a = 1
console.log(defaultDict.a) // 1
console.log(defaultDict.b) // 0
To clean it up a bit, you can wrap this in a constructor function, or perhaps use the class syntax.
class DefaultDict {
constructor(defaultVal) {
return new Proxy({}, {
get: (target, name) => name in target ? target[name] : defaultVal
})
}
}
const counts = new DefaultDict(0)
console.log(counts.c) // 0
EDIT: The above implementation only works well with primitives. It should handle objects too by taking a constructor function for the default value. Here is an implementation that should work with primitives and constructor functions alike.
class DefaultDict {
constructor(defaultInit) {
return new Proxy({}, {
get: (target, name) => name in target ?
target[name] :
(target[name] = typeof defaultInit === 'function' ?
new defaultInit().valueOf() :
defaultInit)
})
}
}
const counts = new DefaultDict(Number)
counts.c++
console.log(counts.c) // 1
const lists = new DefaultDict(Array)
lists.men.push('bob')
lists.women.push('alice')
console.log(lists.men) // ['bob']
console.log(lists.women) // ['alice']
console.log(lists.nonbinary) // []
Check out pycollections.js:
var collections = require('pycollections');
var dd = new collections.DefaultDict(function(){return 0});
console.log(dd.get('missing')); // 0
dd.setOneNewValue(987, function(currentValue) {
return currentValue + 1;
});
console.log(dd.items()); // [[987, 1], ['missing', 0]]
I don't think there is the equivalent but you can always write your own. The equivalent of a dictionary in javascript would be an object so you can write it like so
function defaultDict() {
this.get = function (key) {
if (this.hasOwnProperty(key)) {
return key;
} else {
return 0;
}
}
}
Then call it like so
var myDict = new defaultDict();
myDict[1] = 2;
myDict.get(1);
A quick dirty hack can be constructed using Proxy
function dict(factory, origin) {
return new Proxy({ ...origin }, {
get(dict, key) {
// Ensure that "missed" keys are set into
// The dictionary with default values
if (!dict.hasOwnProperty(key)) {
dict[key] = factory()
}
return dict[key]
}
})
}
So the following code:
n = dict(Number, [[0, 1], [1, 2], [2, 4]])
// Zero is the default value mapped into 3
assert(n[3] == 0)
// The key must be present after calling factory
assert(Object.keys(n).length == 4)
Proxies definitely make the syntax most Python-like, and there's a library called defaultdict2 that offers what seems like a pretty crisp and thorough proxy-based implementation that supports nested/recursive defaultdicts, something I really value and am missing in the other answers so far in this thread.
That said, I tend to prefer keeping JS a bit more "vanilla"/"native" using a function-based approach like this proof-of-concept:
class DefaultMap {
constructor(defaultFn) {
this.defaultFn = defaultFn;
this.root = new Map();
}
put(...keys) {
let map = this.root;
for (const key of keys.slice(0, -1)) {
map.has(key) || map.set(key, new Map());
map = map.get(key);
}
const key = keys[keys.length-1];
map.has(key) || map.set(key, this.defaultFn());
return {
set: setterFn => map.set(key, setterFn(map.get(key))),
mutate: mutationFn => mutationFn(map.get(key)),
};
}
get(...keys) {
let map = this.root;
for (const key of keys) {
map = map?.get(key);
}
return map;
}
}
// Try it:
const dm = new DefaultMap(() => []);
dm.put("foo").mutate(v => v.push(1, 2, 3));
dm.put("foo").mutate(v => v.push(4, 5));
console.log(dm.get("foo")); // [1, 2, 3, 4, 5]
dm.put("bar", "baz").mutate(v => v.push("a", "b"));
console.log(dm.get("bar", "baz")); // ["a", "b"]
dm.put("bar", "baz").set(v => 42);
console.log(dm.get("bar", "baz")); // 42
dm.put("bar", "baz").set(v => v + 1);
console.log(dm.get("bar", "baz")); // 43
The constructor of DefaultMap accepts a function that returns a default value for leaf nodes. The basic operations for the structure are put and get, the latter of which is self-explanatory. put generates a chain of nested keys and returns a pair of functions that let you mutate or set the leaf node at the end of these keys. Accessing .root gives you the underlying Map structure.
Feel free to leave a comment if I've overlooked any bugs or miss useful features and I'll toss it in.
Inspired by #Andy Carlson's answer, here's an implementation that works in a slightly more Pythonic way:
class DefaultDict {
constructor(defaultVal) {
return new Proxy(
{},
{
get: (target, name) => {
if (name == '__dict__') {
return target;
} else if (name in target) {
return target[name];
} else {
target[name] = defaultVal;
return defaultVal;
}
},
}
);
}
}
Basically, it also lets you retrieve all the gotten and set values of the "target", similar to how collections.defaultdict works in Python. This allows us to do things like:
const myDict = new DefaultDict(0);
myDict['a'] += 1;
myDict['b'] += 2;
myDict['c'] += 3;
myDict['whatever'];
console.log(myDict.__dict__);
// {'a': 1, 'b': 2, 'c': 3, 'whatever': 0}
To add to Andy Carlson's answer
If you default dict an array, you'll get a toJSON field in the resulting object. You can get rid of it by deconstructing to a new object.
const dd = new DefaultDict(Array);
//...populate the dict
return {...dd};
The original answer does not seem to work on the nested cases. I made some modifications to make it work:
class DefaultDict {
constructor(defaultInit) {
this.original = defaultInit;
return new Proxy({}, {
get: function (target, name) {
if (name in target) {
return target[name];
} else {
if (typeof defaultInit === "function") {
target[name] = new defaultInit().valueOf();
} else if (typeof defaultInit === "object") {
if (typeof defaultInit.original !== "undefined") {
target[name] = new DefaultDict(defaultInit.original);
} else {
target[name] = JSON.parse(JSON.stringify(defaultInit));
}
} else {
target[name] = defaultInit;
}
return target[name];
}
}
});
}
}
var a = new DefaultDict(Array);
a["banana"].push("ya");
var b = new DefaultDict(new DefaultDict(Array));
b["orange"]["apple"].push("yo");
var c = new DefaultDict(Number);
c["banana"] = 1;
var d = new DefaultDict([2]);
d["banana"].push(1);
var e = new DefaultDict(new DefaultDict(2));
e["orange"]["apple"] = 3;
var f = new DefaultDict(1);
f["banana"] = 2;
The difference is that if defaultInit is an object, we need to return a deep copy of the object, instead of the original one.

Categories

Resources