I get from the server a list of objects
[{name:'test01', age:10},{name:'test02', age:20},{name:'test03', age:30}]
I load them into html controls for the user to edit.
Then there is a button to bulk save the entire list back to the database.
Instead of sending the whole list I only want to send the subset of objects that were changed.
It can be any number of items in the array. I want to do something similar to frameworks like Angular that mark an object property like "pristine" when no change has been done to it. Then use that flag to only post to the server the items that are not "pristine", the ones that were modified.
Here is a function down below that will return an array/object of changed objects when supplied with an old array/object of objects and a new array of objects:
// intended to compare objects of identical shape; ideally static.
//
// any top-level key with a primitive value which exists in `previous` but not
// in `current` returns `undefined` while vice versa yields a diff.
//
// in general, the input type determines the output type. that is if `previous`
// and `current` are objects then an object is returned. if arrays then an array
// is returned, etc.
const getChanges = (previous, current) => {
if (isPrimitive(previous) && isPrimitive(current)) {
if (previous === current) {
return "";
}
return current;
}
if (isObject(previous) && isObject(current)) {
const diff = getChanges(Object.entries(previous), Object.entries(current));
return diff.reduce((merged, [key, value]) => {
return {
...merged,
[key]: value
}
}, {});
}
const changes = [];
if (JSON.stringify(previous) === JSON.stringify(current)) {
return changes;
}
for (let i = 0; i < current.length; i++) {
const item = current[i];
if (JSON.stringify(item) !== JSON.stringify(previous[i])) {
changes.push(item);
}
}
return changes;
};
For Example:
const arr1 = [1, 2, 3, 4]
const arr2 = [4, 4, 2, 4]
console.log(getChanges(arr1, arr2)) // [4,4,2]
const obj1 = {
foo: "bar",
baz: [
1, 2, 3
],
qux: {
hello: "world"
},
bingo: "name-o",
}
const obj2 = {
foo: "barx",
baz: [
1, 2, 3, 4
],
qux: {
hello: null
},
bingo: "name-o",
}
console.log(getChanges(obj1.foo, obj2.foo)) // barx
console.log(getChanges(obj1.bingo, obj2.bingo)) // ""
console.log(getChanges(obj1.baz, obj2.baz)) // [4]
console.log(getChanges(obj1, obj2)) // {foo:'barx',baz:[1,2,3,4],qux:{hello:null}}
const obj3 = [{ name: 'test01', age: 10 }, { name: 'test02', age: 20 }, { name: 'test03', age: 30 }]
const obj4 = [{ name: 'test01', age: 10 }, { name: 'test02', age: 20 }, { name: 'test03', age: 20 }]
console.log(getChanges(obj3, obj4)) // [{name:'test03', age:20}]
Utility functions used:
// not required for this example but aid readability of the main function
const typeOf = o => Object.prototype.toString.call(o);
const isObject = o => o !== null && !Array.isArray(o) && typeOf(o).split(" ")[1].slice(0, -1) === "Object";
const isPrimitive = o => {
switch (typeof o) {
case "object": {
return false;
}
case "function": {
return false;
}
default: {
return true;
}
}
};
You would simply have to export the full list of edited values client side, compare it with the old list, and then send the list of changes off to the server.
Hope this helps!
Here are a few ideas.
Use a framework. You spoke of Angular.
Use Proxies, though Internet Explorer has no support for it.
Instead of using classic properties, maybe use Object.defineProperty's set/get to achieve some kind of change tracking.
Use getter/setting functions to store data instead of properties: getName() and setName() for example. Though this the older way of doing what defineProperty now does.
Whenever you bind your data to your form elements, set a special property that indicates if the property has changed. Something like __hasChanged. Set to true if any property on the object changes.
The old school bruteforce way: keep your original list of data that came from the server, deep copy it into another list, bind your form controls to the new list, then when the user clicks submit, compare the objects in the original list to the objects in the new list, plucking out the changed ones as you go. Probably the easiest, but not necessarily the cleanest.
A different take on #6: Attach a special property to each object that always returns the original version of the object:
var myData = [{name: "Larry", age: 47}];
var dataWithCopyOfSelf = myData.map(function(data) {
Object.assign({}, data, { original: data });
});
// now bind your form to dataWithCopyOfSelf.
Of course, this solution assumes a few things: (1) that your objects are flat and simple since Object.assign() doesn't deep copy, (2) that your original data set will never be changed, and (3) that nothing ever touches the contents of original.
There are a multitude of solutions out there.
With ES6 we can use Proxy
to accomplish this task: intercept an Object write, and mark it as dirty.
Proxy allows to create a handler Object that can trap, manipulate, and than forward changes to the original target Object, basically allowing to reconfigure its behavior.
The trap we're going to adopt to intercept Object writes is the handler set().
At this point we can add a non-enumerable property flag like i.e: _isDirty using Object.defineProperty() to mark our Object as modified, dirty.
When using traps (in our case the handler's set()) no changes are applied nor reflected to the Objects, therefore we need to forward the argument values to the target Object using Reflect.set().
Finally, to retrieve the modified objects, filter() the Array with our proxy Objects in search of those having its own Property "_isDirty".
// From server:
const dataOrg = [
{id:1, name:'a', age:10},
{id:2, name:'b', age:20},
{id:3, name:'c', age:30}
];
// Mirror data from server to observable Proxies:
const data = dataOrg.map(ob => new Proxy(ob, {
set() {
Object.defineProperty(ob, "_isDirty", {value: true}); // Flag
return Reflect.set(...arguments); // Forward trapped args to ob
}
}));
// From now on, use proxied data. Let's change some values:
data[0].name = "Lorem";
data[0].age = 42;
data[2].age = 31;
// Collect modified data
const dataMod = data.filter(ob => ob.hasOwnProperty("_isDirty"));
// Test what we're about to send back to server:
console.log(JSON.stringify(dataMod, null, 2));
Without using .defineProperty()
If for some reason you don't feel comfortable into tapping into the original object adding extra properties as flags, you could instead populate immediately
the dataMod (array with modified Objects) with references:
const dataOrg = [
{id:1, name:'a', age:10},
{id:2, name:'b', age:20},
{id:3, name:'c', age:30}
];
// Prepare array to hold references to the modified Objects
const dataMod = [];
const data = dataOrg.map(ob => new Proxy(ob, {
set() {
if (dataMod.indexOf(ob) < 0) dataMod.push(ob); // Push reference
return Reflect.set(...arguments);
}
}));
data[0].name = "Lorem";
data[0].age = 42;
data[2].age = 31;
console.log(JSON.stringify(dataMod, null, 2));
Can I Use - Proxy (IE)
Proxy - handler.set()
Global Objects - Reflect
Reflect.set()
Object.defineProperty()
Object.hasOwnProperty()
Without having to get fancy with prototype properties you could simply store them in another array whenever your form control element detects a change
Something along the lines of:
var modified = [];
data.forEach(function(item){
var domNode = // whatever you use to match data to form control element
domNode.addEventListener('input',function(){
if(modified.indexOf(item) === -1){
modified.push(item);
}
});
});
Then send the modified array to server when it's time to save
Why not use Ember.js observable properties ? You can use the Ember.observer function to get and set changes in your data.
Ember.Object.extend({
valueObserver: Ember.observer('value', function(sender, key, value, rev) {
// Executes whenever the "value" property changes
// See the addObserver method for more information about the callback arguments
})
});
The Ember.object actually does a lot of heavy lifting for you.
Once you define your object, add an observer like so:
object.addObserver('propertyKey', targetObject, targetAction)
My idea is to sort object keys and convert object to be string to compare:
// use this function to sort keys, and save key=>value in an array
function objectSerilize(obj) {
let keys = Object.keys(obj)
let results = []
keys.sort((a, b) => a > b ? -1 : a < b ? 1 : 0)
keys.forEach(key => {
let value = obj[key]
if (typeof value === 'object') {
value = objectSerilize(value)
}
results.push({
key,
value,
})
})
return results
}
// use this function to compare
function compareObject(a, b) {
let aStr = JSON.stringify(objectSerilize(a))
let bStr = JSON.stringify(objectSerilize(b))
return aStr === bStr
}
This is what I think up.
It would be cleanest, I’d think to have the object emit an event when a property is added or removed or modified.
A simplistic implementation could involve an array with the object keys; whenever a setter or heck the constructor returns this, it first calls a static function returning a promise; resolving: map with changed values in the array: things added, things removed, or neither. So one could get(‘changed’) or so forth; returning an array.
Similarly every setter can emit an event with arguments for initial value and new value.
Assuming classes are used, you could easily have a static method in a parent generic class that can be called through its constructor and so really you could simplify most of this by passing the object either to itself, or to the parent through super(checkMeProperty).
Related
I have a deeply nested object, and throughout my code I often need to manipulate data like this:
var current_index = test_dict['current']['index']
var current_section = test_dict['current']['section']
test_dict[current_section]['data']['words'][current_index]
So you can see when accessing the object, I'm using variables that reference other parts of the object. This has been working great for me, but throughout my code I also need to periodically update current_index and current_section.
Since those variables are only shallow copies/references and not direct shortcuts to those actual values, doing current_index++ increases current_index but not test_dict['current']['index']
In my code test_dict needs to store all of the current information, so I'm trying to figure out how I can update that dictionary directly without typing out a deeply nested path.
I know I can use dot notation, but that wouldn't save me any time since I would have to do something like:
test_dict[test_dict.current.section]['data']['words'][test_dict.current.index]
I know I can also create a reference to let current_index and let current_section at the beginning of the function, but since I have to manipulate that test_dict object in almost every function it would be impractical to define it hundreds of times.
Is there a better way? Should I just create a getCurrentIndex() function and then do this?
test_dict[getCurrentSection()]['data']['words'][getCurrentIndex()]
Since those variables are only shallow copies/references and not direct shortcuts to those actual values, doing current_index++ increases current_index but not test_dict['current']['index']
OK.. objects(and arrays) are pointers/references.. numbers, strings, booleans, etc are copies.. If you want shortcuts to things, here is an example
var current=test_dict['current'];
var current_index = current['index'];
var current_section = current['section'];
test_dict[current_section]['data']['words'][current_index];
current['index']++ //index inside test_dict would get added to
Perhaps you could try Proxy object:
const dict = {
current: {
index: 1,
section: "test1"
},
test1: {
data: {
words: ["test1 index0", "test1 index1", "test1 index2"]
}
},
test2: {
data: {
words: ["test2 index0", "test2 index1", "test2 index2"],
other: {
sub1: {
sub1_1: ["some", "text", "blah"],
sub1_2: "single string",
sub1_3: {}
}
}
},
blah: {
sub1: {
sub1_4: 123456
}
}
}
}
const test_dict = new Proxy(dict, {
get: function (target, key, receiver)
{
const data = target[dict.current.section] && target[dict.current.section][key] || target[key];
if (typeof data === 'object' && data !== null && !(data instanceof Array))
return new Proxy(data, this)
return data instanceof Array && data[dict.current.index] || data || target;
}
});
console.log("[data][words]", test_dict['data']['words']);
test_dict.current.index = 2
console.log("[data][words]", test_dict['data']['words']);
test_dict.current.section = "test2"
test_dict.current.index = 0
console.log("[data][words]", test_dict['data']['words']);
console.log("data.words", test_dict.data.words);
console.log("data", test_dict.data);
console.log("sub1", test_dict['data']['other']['sub1']);
console.log("sub1_1", test_dict['data']['other']['sub1']['sub1_1']);
console.log("sub1_2", test_dict['data']['other']['sub1']['sub1_2']);
console.log("sub1_3", test_dict['data']['other']['sub1']['sub1_3']);
console.log("blah.sub1_4", test_dict['blah']['sub1']['sub1_4']);
I would like to extract values from an object (done), which get put into an array (done), then those values are changed (by tweening library) and returned to me as an array of values.
I would like to insert those values back into an object of the same shape from whence they came.
But I'm stuck on something fairly basic, and certainly do-able.
I have an example state, like so (it could be a totally different shape, this is just an example):
var state = {
ignore: "me",
count: 0,
foo: 10,
bar: {
baz: 99
}
}
I have a new state, that I want to pass into my tweening method:
var newState = {
count: 100,
foo: 50,
bar: {
baz: 999
}
}
There's a tween library in the tweenState method, that produces an array of these values at each "frame" of the animation:
// NOTE: I only pass in the properties I wanna tween!!
myApp.tweenState({ count: 100, foo: 50, bar: { baz: 999 } })
// internally, the tween lib produces something like this on each frame:
tweenedValues = [ 50.1231, 34.43543, 456.4543 ]
I would like to insert these tweened values into an object of the same shape as the state object I'm using, to create this on each frame (as per example state above):
tweenedState = {
ignore: "me",
count: 50.1231,
foo: 34.43543,
bar: {
baz: 456.4543
}
}
...so far, I only have this terrible code (called on each frame):
// make sure tweened values are in an array
var valuesArr = Array.isArray(tweenedValues) ? tweenedValues : [tweenedValues]
// this object will hold tween values of current frame/progress,
// set to original state for now... we'll update it's values from
// valuesArr later
var tweenedState = self.state
// set the newState properties with new values from valuesArr
// (newState is the state passed into the tweenState() method)
Object.keys(newState).forEach((key, index) => {
if (typeof newState[key] === "object") {
Object.keys(newState[key]).forEach(key2 => {
tweenedState[key][key2] = valuesArr[index]
})
} else {
tweenedState[key] = valuesArr[index]
}
})
console.log("tweenedState: ", tweenedState)
Which has these obvious problems:
not recursive, can't go more than 2 levels deep into an object
in any case, the line tweenedState[key][key2] = valuesArr[index] doesn't set the value as expected
when I made it into a recursive function, it looped forever and crashed
So, I need a function that takes the array, and inserts the values into the correct place - namely the properties passed in to begin with (which creates the tweenedState object).
EDIT: Obviously, the state object may look totally different - the one given is just an example - It may be many levels deep, and may contain other stuff NOT passed in.. The key point is that I end up with a state object exactly like the original, but with the tweened values inserted instead.
And I repeat - the Tween library creates the array of values, not me, not my choice. And yes I know object properties are not always in the same order.
EDIT 2: this code has to be isomorphic - working in recent browsers (don't care about IE) and Node 10 or later, ideally without polyfills...
EDIT 3: NOW SOLVED:
Both NinaScholz and mashi have provided answers that worked for me (after I fixed a bug elsewhere).
Ninas answer has stricter JS version requirements, and is a little less portable than mashis answer. On that basis I have accepted mashis answer, though Ninas answer is very nice, too.
Thanks.
function setTweenedValues(state, values) {
const reducer = (newState, [key, val]) => {
newState[key] = val;
if (typeof val === "number") {
newState[key] = values.shift();
}
if (typeof val === "object") {
newState[key] = Object.entries(val).reduce(reducer, {});
}
return newState;
};
const newState = Object.entries(state).reduce(reducer, {});
return newState;
}
var state = { count: 200, foo: 10, bar: { zzz: 999 }, ignore: "me" },
tweenedValues = [50.1231, 34.43543, 456.4543],
tweenedState = setTweenedValues(state, [...tweenedValues]);
console.log(tweenedState);
You could iterate the entries and call the function again for nested entries.
function setValues(pattern, values) {
return Object.fromEntries(Object
.entries(pattern)
.map(([k, v]) => [
k,
v && typeof v === 'object'
? setValues(v, values)
: values.length ? values.shift() : v
])
);
}
var state = { count: 200, foo: 10, bar: { zzz: 999 }, ignore: "me" },
tweenedValues = [50.1231, 34.43543, 456.4543],
tweenedState = setValues(state, [...tweenedValues]);
console.log(tweenedState);
as i see it you have values in an array that are always in a guaranteed order, and you want them as prop values in an object... you could use object destructuring
const [count, foo, bar] = tweenedValues;
then make object like this
let obj = {count, foo, bar}
i am not getting proper the return after insertgraph in objection.js
i am getting the result like :
[
User {
name: 'Santosh Devi',
city: 'Suratgarh',
number: '9898987458',
userroles: UserRoles { role_id: 2, user_id: 37 },
id: 37
}
]
where i want the result like :
[
{
name: 'Santosh Devi',
city: 'Suratgarh',
number: '9898987458',
userroles: { role_id: 2, user_id: 37 },
id: 37
}
]
There are few ways to get rid of the specific class references:
1. JSON.parse(JSON.stringify(result))
This will rebuild the object by first converting the whole object to a string (in JSON format), and then by doing the reverse -- creating a new object from a string. As this string format (JSON) does not store custom class information, it achieves your purpose. However, if your object has functions, symbols, then these will be omitted. Also Map and Set will become empty objects. For a more complete list of restrictions. See JSON.stringify
2. Deep Clone
There are several deep-clone functions out there, that may or may not do what you expect. Some will still try to maintain the original prototype references, so that it would not benefit you. You can find some here: How to Deep clone in javascript. For your case, this one would do the job:
function deepClone(obj, hash = new WeakMap()) {
if (Object(obj) !== obj) return obj; // primitives
if (hash.has(obj)) return hash.get(obj); // cyclic reference
const result = Array.isArray(obj) ? [] : {};
hash.set(obj, result);
return Object.assign(result, ...Object.keys(obj).map(
key => ({ [key]: deepClone(obj[key], hash) }) ));
}
You call it as newResult = deepClone(result).
The advantage here, is that it supports cyclic references, which JSON.stringify cannot handle. Also, there is no string conversion happening, which really is not necessary. You can extend this function to keep deal with some class instances that you like to stay that way. See how you can support Date, RegExp, Map, Set, ... in this answer. But don't do the "catch-all" line.
3. Change the prototype
With this strategy you mutate the result in-place.
function removeClasses(obj, hash = new WeakSet()) {
if (Object(obj) !== obj) return; // primitives
if (hash.has(obj)) return; // cyclic reference
hash.add(obj);
if (Array.isArray(obj)) Object.setPrototypeOf(obj, Array.prototype);
else Object.setPrototypeOf(obj, Object.prototype);
for (let value of Object.values(obj)) {
removeClasses(value, hash);
}
}
Call it as removeClasses(result), and afterwards result will have been "fixed". Again, this method does not use a conversion to string. As it does not create a new object either, it consumes less memory. But on the other hand you mutate an object, and some would advise against that.
I am learning functional programming in Javascript and using Ramda. I have this object
var fieldvalues = { name: "hello there", mobile: "1234",
meta: {status: "new"},
comments: [ {user: "john", comment: "hi"},
{user:"ram", comment: "hello"}]
};
to be converted like this:
{
comments.0.comment: "hi",
comments.0.user: "john",
comments.1.comment: "hello",
comments.1.user: "ram",
meta.status: "new",
mobile: "1234",
name: "hello there"
}
I have tried this Ramda source, which works.
var _toDotted = function(acc, obj) {
var key = obj[0], val = obj[1];
if(typeof(val) != "object") { // Matching name, mobile etc
acc[key] = val;
return acc;
}
if(!Array.isArray(val)) { // Matching meta
for(var k in val)
acc[key + "." + k] = val[k];
return acc;
}
// Matching comments
for(var idx in val) {
for(var k2 in val[idx]) {
acc[key + "." + idx + "." + k2] = val[idx][k2];
}
}
return acc;
};
// var toDotted = R.pipe(R.toPairs, R.reduce(_toDotted, {}));
var toDotted = R.pipe(R.toPairs, R.curry( function(obj) {
return R.reduce(_toDotted, {}, obj);
}));
console.log(toDotted(fieldvalues));
However, I am not sure if this is close to Functional programming methods. It just seems to be wrapped around some functional code.
Any ideas or pointers, where I can make this more functional way of writing this code.
The code snippet available here.
UPDATE 1
Updated the code to solve a problem, where the old data was getting tagged along.
Thanks
A functional approach would
use recursion to deal with arbitrarily shaped data
use multiple tiny functions as building blocks
use pattern matching on the data to choose the computation on a case-by-case basis
Whether you pass through a mutable object as an accumulator (for performance) or copy properties around (for purity) doesn't really matter, as long as the end result (on your public API) is immutable. Actually there's a nice third way that you already used: association lists (key-value pairs), which will simplify dealing with the object structure in Ramda.
const primitive = (keys, val) => [R.pair(keys.join("."), val)];
const array = (keys, arr) => R.addIndex(R.chain)((v, i) => dot(R.append(keys, i), v), arr);
const object = (keys, obj) => R.chain(([v, k]) => dot(R.append(keys, k), v), R.toPairs(obj));
const dot = (keys, val) =>
(Object(val) !== val
? primitive
: Array.isArray(val)
? array
: object
)(keys, val);
const toDotted = x => R.fromPairs(dot([], x))
Alternatively to concatenating the keys and passing them as arguments, you can also map R.prepend(key) over the result of each dot call.
Your solution is hard-coded to have inherent knowledge of the data structure (the nested for loops). A better solution would know nothing about the input data and still give you the expected result.
Either way, this is a pretty weird problem, but I was particularly bored so I figured I'd give it a shot. I mostly find this a completely pointless exercise because I cannot picture a scenario where the expected output could ever be better than the input.
This isn't a Rambda solution because there's no reason for it to be. You should understand the solution as a simple recursive procedure. If you can understand it, converting it to a sugary Rambda solution is trivial.
// determine if input is object
const isObject = x=> Object(x) === x
// flatten object
const oflatten = (data) => {
let loop = (namespace, acc, data) => {
if (Array.isArray(data))
data.forEach((v,k)=>
loop(namespace.concat([k]), acc, v))
else if (isObject(data))
Object.keys(data).forEach(k=>
loop(namespace.concat([k]), acc, data[k]))
else
Object.assign(acc, {[namespace.join('.')]: data})
return acc
}
return loop([], {}, data)
}
// example data
var fieldvalues = {
name: "hello there",
mobile: "1234",
meta: {status: "new"},
comments: [
{user: "john", comment: "hi"},
{user: "ram", comment: "hello"}
]
}
// show me the money ...
console.log(oflatten(fieldvalues))
Total function
oflatten is reasonably robust and will work on any input. Even when the input is an array, a primitive value, or undefined. You can be certain you will always get an object as output.
// array input example
console.log(oflatten(['a', 'b', 'c']))
// {
// "0": "a",
// "1": "b",
// "2": "c"
// }
// primitive value example
console.log(oflatten(5))
// {
// "": 5
// }
// undefined example
console.log(oflatten())
// {
// "": undefined
// }
How it works …
It takes an input of any kind, then …
It starts the loop with two state variables: namespace and acc . acc is your return value and is always initialized with an empty object {}. And namespace keeps track of the nesting keys and is always initialized with an empty array, []
notice I don't use a String to namespace the key because a root namespace of '' prepended to any key will always be .somekey. That is not the case when you use a root namespace of [].
Using the same example, [].concat(['somekey']).join('.') will give you the proper key, 'somekey'.
Similarly, ['meta'].concat(['status']).join('.') will give you 'meta.status'. See? Using an array for the key computation will make this a lot easier.
The loop has a third parameter, data, the current value we are processing. The first loop iteration will always be the original input
We do a simple case analysis on data's type. This is necessary because JavaScript doesn't have pattern matching. Just because were using a if/else doesn't mean it's not functional paradigm.
If data is an Array, we want to iterate through the array, and recursively call loop on each of the child values. We pass along the value's key as namespace.concat([k]) which will become the new namespace for the nested call. Notice, that nothing gets assigned to acc at this point. We only want to assign to acc when we have reached a value and until then, we're just building up the namespace.
If the data is an Object, we iterate through it just like we did with an Array. There's a separate case analysis for this because the looping syntax for objects is slightly different than arrays. Otherwise, it's doing the exact same thing.
If the data is neither an Array or an Object, we've reached a value. At this point we can assign the data value to the acc using the built up namespace as the key. Because we're done building the namespace for this key, all we have to do compute the final key is namespace.join('.') and everything works out.
The resulting object will always have as many pairs as values that were found in the original object.
I have this Javascript object.
req.session
In my code I add properties to this object. These properties can be other objects, arrays, or just plain strings.
req.session.savedDoc = someObject;
req.session.errors = ['someThing', 'anotherThing', 'thirdThing'];
req.session.message = 'someString'
If I later would like to erase all added properties of this object, what is the easiest/best way?
There must be a better way than this?
// Delete all session values
delete req.session.savedDoc;
delete req.session.errors;
delete req.session.message;
#VisioN's answer works if you want to clear that specific reference, but if you actually want to clear an object I found that this works:
for (var variableKey in vartoClear){
if (vartoClear.hasOwnProperty(variableKey)){
delete vartoClear[variableKey];
}
}
There are two possible solutions to the problem:
Assign an empty object
req.session = {};
The garbage collector will clean the memory automatically. This variant is super fast and will work in most cases, however, you need to use it with caution, as it may keep the references to the objects in memory. This caveat is described in the TLDR section below.
Delete properties one-by-one
Object.keys(object).forEach(key => delete object[key]);
This will clean the object by going through every non-prototype property and deleting it. It's safer but slower. You need to decide if it makes sense for you to use it in a particular case.
TLDR
Any solution given above will do the job for the author in the current situation, as well as any other valid solution provided in this question. It mainly depends on the way how the developer wants to manipulate the deprecated data.
Session object may contain data that is linked by different variable, and setting a new empty object to req.session will not break the reference to the old data, so the old data will be available where it is still required. Although the correct way to keep old data is to clone the initial object, real-life scenarios can be different. Let's look at the following example:
req.session.user = { name: "Alexander" }; // we store an object in the session
var session = req.session; // save reference to the session in a variable
console.log( req.session, session ); // {user: Object}, {user: Object}
req.session = {}; // let's replace session with a new object
console.log( req.session, session ); // {}, {user: Object}
We still can fetch old data from session variable but req.session is empty: here setting a new object works as a sort of alternative to deep cloning. The garbage collector will not remove data from the old req.session object as it is still referenced by the session variable.
Deep cleaning of the object with:
Object.keys(object).forEach(key => delete object[key]);
... will explicitly remove all values from the req.session object and, since session variable is linked to the same object, session will become empty as well. Let's see how it works:
req.session.user = { name: "Alexander" }; // we store an object in the session
var session = req.session; // save reference to the session in a variable
console.log( req.session, session ); // {user: Object}, {user: Object}
Object.keys(req.session).forEach(key => delete req.session[key]);
console.log( req.session, session ); // {}, {}
As you can see now, in both cases we get empty objects.
From speed and memory perspectives setting a new empty object will be much faster than cleaning the old object property by property, however memory-wise if the old data is still referenced somewhere, the new object approach won't free up memory that old data is consuming.
It's quite obvious that choosing the approach to take is mostly up to your coding scenario but in most cases req.session = {}; will do the job: it is fast and short. However, if you keep references to the original object in other variables, you may consider using deep implicit object properties deletion.
I can see only one correct solution for removing own properties from object:
for (var x in objectToClean) if (objectToClean.hasOwnProperty(x)) delete objectToClean[x];
If you want to use it more than once, you should create a cleaning function:
function deleteProperties(objectToClean) {
for (var x in objectToClean) if (objectToClean.hasOwnProperty(x)) delete objectToClean[x];
}
For your case the usage would be:
deleteProperties(req.session);
This solution removes properties from the object wherever it's referenced and keeping the old reference.
Example:
Using empty object assignment:
var x = {a: 5};
var y = x;
x = {}; // x will be empty but y is still {a: 5}, also now reference is gone: x !== y
Using cleaning method:
var x = {a: 5};
var y = x;
deleteProperties(x); // x and y are both empty and x === y
If you want to delete all properties without touching methods you can use :
for(var k in req.session) if(!req.session[k].constructor.toString().match(/^function Function\(/)) delete req.session[k];
You can use a map instead if you care about performance like so
const map = new Map()
map.set("first", 1)
map.set("second", 1)
map.clear()
This is a O(1) operation, so even if you have a huge object you do not need to iterate x times to delete the entries.
I've done it like this
var
i,
keys = Object.keys(obj);
for(i = 0; i < keys.length; i++){
delete obj[keys[i]];
}
You could add it to Object (prototype's not ideal here) - will be static.
Object.defineproperties(Object, {
'clear': function(target){
var
i,
keys = Object.keys(target);
for(i = 0; i < keys.length; i++){
delete target[keys[i]];
}
}
});
Then you can clear random objects with
Object.clear(yourObj);
yourObj = {} replaces the reference to a new object, the above removes it's properties - reference is the same.
The naive object = {} method is okay for vanilla Object, but it deletes prototypes of custom objects.
This method produces an empty object that preserves prototypes, using Object.getPrototypeOf() and Object.create():
emptyObj = Object.create(Object.getPrototypeOf(obj), {});
Example:
class Custom extends Object {
custom() {}
}
let custom = new Custom();
custom.foo = "bar";
console.log(custom.constructor.name, custom);
// Custom {"foo": "bar"}
// naive method:
let objVanilla = {}
console.log(objVanilla.constructor.name, objVanilla);
// Object {}
// safe method:
objSafe = Object.create(Object.getPrototypeOf(custom), {});
console.log(objSafe.constructor.name, objSafe);
// Custom {}
This script removes property recursively except for the data reported in vector.
You need the lodash library
-- Function:
function removeKeysExcept(object, keysExcept = [], isFirstLevel = true) {
let arrayKeysExcept = [],
arrayNextKeysExcept = {};
_.forEach(keysExcept, (value, i) => {
let j = value.split('.');
let keyExcept = j[0];
arrayKeysExcept.push(keyExcept);
j.shift();
if (j.length) {
j = j.join('.');
if (!arrayNextKeysExcept[keyExcept]) {
arrayNextKeysExcept[keyExcept] = [];
}
arrayNextKeysExcept[keyExcept].push(j);
}
})
_.forEach(arrayNextKeysExcept, (value, key) => {
removeKeysExcept(object[key], value, false);
});
if (isFirstLevel) {
return;
}
Object.keys(object).forEach(function (key) {
if (arrayKeysExcept.indexOf(key) == -1) {
delete object[key];
}
});
}
Run so:
-- Removes all properties except the first level and reported in the vector:
removeKeysExcept(obj, ['department.id','user.id']);
-- Removes all properties
removeKeysExcept(obj, ['department.id','user.id'], false);
-- Example:
let obj = {
a: {
aa: 1,
ab: {
aba: 21
}
},
b: 10,
c: {
ca: 100,
cb: 200
}
};
removeKeysExcept(obj, ['a.ab.aba','c.ca']);
/*OUTPUT: {
a: {
ab: {
aba: 21
}
},
b: 10,
c: {
ca: 100,
}
};*/
removeKeysExcept(obj, ['a.ab.aba','c.ca'], false); //Remove too firt level
/*OUTPUT: {
a: {
ab: {
aba: 21
}
},
c: {
ca: 100,
}
};*/
removeKeysExcept(obj);
/*OUTPUT: {b:10};*/
removeKeysExcept(obj, [], false); //Remove too firt level
/*OUTPUT: {};*/