I basicly need to do some replace logic to change the '.' to a '>' at here everything fine, i can just use the replace method from javascript, but i am searching the best way to do it.
Basicly i will use this function just for 1 specific task nothing more, i want to do this replace logic to my Name propertiy and description inside the object, so instead of doing a simple method that does the replace i need to pass it two times down.
At the moment i have this repeated: element.Name.replace('.', ' > ')
i created a method, but i thaught as the best possible way to maybe pass it to the function like: replaceMethod(firstProp,secondProp) where each prop gets replaced, so how can i inside the replace method just apply the same logic to all my arguments without using a useless for loop?
something like this:
replaceMethod(firstProp,secondProp) {
allArgs.replace('.', ' > ')
}
i did this:
callerFunc() {
// service get the object material, it has a name and description with '.'
replaceMethod(material,material.Name,material.Description)
// do some logic after the method with the material
}
replaceMethod(material,...keys) {
keys.forEach(k => material[k] = material[k].replace(/\./g, ' > '));
}
In ES6, you could use rest parameters ... for collecting all arguments.
function replaceMethod(...keys) {
keys.forEach(k => object[k] = object[k].replace(/\./g, ' > '));
}
var object = { name: 'foo.bar.baz', town: 'st.peter' };
replaceMethod('name', 'town');
console.log(object);
ES5 with use of arguments object.
function replaceMethod() {
Array.prototype.forEach.call(arguments, function (k) {
object[k] = object[k].replace(/\./g, ' > ');
});
}
var object = { name: 'foo.bar.baz', town: 'st.peter' };
replaceMethod('name', 'town');
console.log(object);
I would recommend passing the object as a parameter with the keys of the properties you want to change. Then return a new object with the changes instead of changing the object in place. This is a more functional approach without side effects. You can use the array reduce method. It is most convenient in ES6 using the spread operator ...
function replaceForKeys(obj, ...keys) {
return keys.reduce(
function (result, k) {
return { ...result, [k]: obj[k].replace(/\./g, ' > ') };
},
Object.assign({}, obj)
);
}
var obj = { 'foo': 'foo.bar', 'bar': 'bar.foo' };
var replaced = replaceForKeys(obj, 'foo', 'bar');
So the function takes every argument after the object as an array of keys and reduces over them returning the original object with the property replaced each time. The reduce method takes an initial value as the second parameter and in this case we use Object.assign to use a copy of the original object as the initial value. the [k]: syntax in the object is new in ES6 I believe and is a computed key. It just lets you assign keys in the object without knowing their value beforehand.
Related
I want it to be the case that when I instantiate an instance of the class TableCategory, an object is created using the data passed in at instantiation. I want this to happen automatically at instantiation. I don't want have to instantiate the class and then call a method that creates the object. This seems unnecessary cumbersome.
I am using a class because I then want to manipulate the resultant object using getters and setters for multiple instantiations. (In case you wonder why I'm not just using an object in the first place.)
I'm not 100% clear on classes in JS so I'm not sure where I'm going wrong. Please note the object creation is a the product of a function it takes an array passed in at instantiation and an array that is native to the class.
Here's my class:
export class TableCategory {
constructor(categoryValues = []) {
this.categoryValues = categoryValues;
this.categoryKeys = ['alpha','beta','gamma', 'delta'];
this.categoryData = this.categoryKeys.forEach(function(key, i) {
return this.categoryData[key] = this.categoryValues[i];
});
}
}
Then, for example:
const foo = new TableCategory(['a'. 'b', 'c', 'd']);
console.log(foo.categoryData.beta); // b
Perhaps I need to use static ? Not sure, grateful for any help
forEach() doesn't return anything. Create an empty categoryData object, and then fill it in in the forEach loop.
Also, you need to use an arrow function to be able to access this in the callback function.
class TableCategory {
constructor(categoryValues = []) {
this.categoryValues = categoryValues;
this.categoryKeys = ['alpha', 'beta', 'gamma', 'delta'];
this.categoryData = {};
this.categoryKeys.forEach((key, i) =>
this.categoryData[key] = this.categoryValues[i]
)
}
}
const foo = new TableCategory(['a', 'b', 'c', 'd']);
console.log(foo.categoryData.beta); // b
forEach does not return anything else than undefined. You can still get the desired object in a functional way, but with Object.fromEntries.
Also, as you say you use a class because you intend to mutate the instance(s) with getters and setters, I don't think it is a good idea to still store the values and keys separately from the third property, which has all key/value information.
You could for instance do this:
class TableCategory {
constructor(values = []) {
this._obj = Object.fromEntries(['alpha','beta','gamma', 'delta']
.map((key, i) => [key, values[i]]));
}
get values() {
return Object.values(this._obj);
}
get keys() {
return Object.keys(this._obj);
}
}
let obj = new TableCategory(["this", "is", "a", "test"]);
console.log(obj.values);
I've got a problem with a CodeCademy task. I am to re-create the findKey lodash library method. Here there are the steps of how to do it, but I got stuck, especially at point 5.
Add a method to our _ object called findKey.
Add two parameters to this method: object and predicate. We will
name our predicate function parameter predicate since this is the
name used in the Lodash documentation.
Within the method, use a for ... in loop to iterate through each key
in object.
Within the loop, create a variable called value and set it equal to
the value at the current key in object.
Still within the loop, create another variable called
predicateReturnValue and set it equal to the result of calling
predicate with value.
Finally, still within the loop, use an if statement to check
if predicateReturnValue is truthy. If it is, return the current key
from the method.
Outside of the loop, return undefined to address all cases where no
truthy values were returned from predicate.
This is my code that doesn't work:
findKey(object, predicate) {
for (let key in object) {
let value = object[key];
let predicateReturnValue = predicate(value);
if (predicateReturnValue === 'true') {
return value;
};
};
return undefined;
}
I appreciate your help!
You need to return the key after the truty check of the call of predicate.
function findKey(object, predicate) {
for (let key in object) {
let value = object[key];
let predicateReturnValue = predicate(value);
if (predicateReturnValue) { // just take the value
return key; // return key
}
}
}
const
isStrictEqual = a => b => a === b,
object = { a: 'foo', b: 'bar', c: 'baz' }
console.log(findKey(object, isStrictEqual('bar')));
console.log(findKey(object, isStrictEqual('cat')));
Say I want to assign a value like this:
x.label1.label2.label3 = someValue;
// or equivalently:
x['label1']['label2']['label3'] = someValue;
This works as long as x.label1.label2 is defined but runs into reference errors otherwise. Which makes sense of course. But is there an easy way to assign this anyway where it simply creates the necessary nested objects?
So for example, if x equals { label1: {}, otherLabel: 'otherValue' } I want to update x to become { label1: { label2: { label3: someValue } }, otherLabel: otherValue }
I think I might be able to write a function myself, but is there a language feature or standard library function that does this?
is there a language feature or standard library function that does this
No. You have to write your own function or use a library that provides such functionality.
Related: How to set object property (of object property of..) given its string name in JavaScript?
This is partially possible using the Proxy class. You can wrap your object in a Proxy and override the get trap to create another copy of the same proxy when you access a nonexistent property. This lets you recursively create "deep" properties. An example:
let traps = {
get: function (target, name) {
if (!(name in target))
target[name] = new Proxy({}, traps);
return target[name];
}
};
let x = new Proxy({}, traps);
Then you would use x like any object, except it has this special behavior:
x.label1.label2.label3 = 'foo';
which creates a nested hierarchy of objects. However, note that this will create an object even if you access a nonexistent property. Thus, you will have to use the in keyword to check if it really contains a given property.
I think you should indeed use a custom function such as:
function assignByPath(obj, path, value) {
var field = path.split('>'),
last = field.pop();
field.reduce(
function(node, f) {
return node[f] = node[f] instanceof Object ? node[f] : {};
}, obj
)[last] = value;
}
var myObj = {};
assignByPath(myObj, 'label1>label2>label3', 'someValue');
console.log(myObj);
Theoretically, you could also override Object.prototype, which would allow you to do:
myObj.assignByPath('label1>label2>label3', 'someValue');
But I would not recommend that.
You can use Array.prototype.shift(), Object.assign(), recursion
var x = {
label1: {},
otherLabel: "otherValue"
};
var nestprops = (props, value, obj, o, curr = props.shift()) => props.length
? nestprops(props, value, (Object.assign(obj, {[curr]: {}}) && obj[curr]), o)
: ((!value || value) && (obj[curr] = value) && o);
console.log(nestprops(["label1", "label2", "label3"], "someValue", x, x));
Check length of keys inside label1 object if its equal to 0 then modify it to your desired object.
Here is a snippet, hope it helps.
var obj = { label1: {}, otherLabel: 'otherValue' };
if(Object.keys(obj.label1).length == 0 ) {
obj.label1 = { label2: { label3: "value3" } };
}
console.log(obj);
var a = new Map([[ 'a', 1 ]]);
a.get('a') // 1
var forStorageSomewhere = JSON.stringify(a);
// Store, in my case, in localStorage.
// Later:
var a = JSON.parse(forStorageSomewhere);
a.get('a') // TypeError: undefined is not a function
Unfortunatly JSON.stringify(a); simply returns '{}', which means a becomes an empty object when restored.
I found es6-mapify that allows up/down-casting between a Map and a plain object, so that might be one solution, but I was hoping I would need to resort to an external dependency simply to persist my map.
Assuming that both your keys and your values are serialisable,
localStorage.myMap = JSON.stringify(Array.from(map.entries()));
should work. For the reverse, use
map = new Map(JSON.parse(localStorage.myMap));
Clean as a whistle:
JSON.stringify([...myMap])
Usually, serialization is only useful if this property holds
deserialize(serialize(data)).get(key) ≈ data.get(key)
where a ≈ b could be defined as serialize(a) === serialize(b).
This is satisfied when serializing an object to JSON:
var obj1 = {foo: [1,2]},
obj2 = JSON.parse(JSON.stringify(obj1));
obj1.foo; // [1,2]
obj2.foo; // [1,2] :)
JSON.stringify(obj1.foo) === JSON.stringify(obj2.foo); // true :)
And this works because properties can only be strings, which can be losslessly serialized into strings.
However, ES6 maps allow arbitrary values as keys. This is problematic because, objects are uniquely identified by their reference, not their data. And when serializing objects, you lose the references.
var key = {},
map1 = new Map([ [1,2], [key,3] ]),
map2 = new Map(JSON.parse(JSON.stringify([...map1.entries()])));
map1.get(1); // 2
map2.get(1); // 2 :)
map1.get(key); // 3
map2.get(key); // undefined :(
So I would say in general it's not possible to do it in an useful way.
And for those cases where it would work, most probably you can use a plain object instead of a map. This will also have these advantages:
It will be able to be stringified to JSON without losing key information.
It will work on older browsers.
It might be faster.
Building off of Oriol's answer, we can do a little better. We can still use object references for keys as long as the there is primitive root or entrance into the map, and each object key can be transitively found from that root key.
Modifying Oriol's example to use Douglas Crockford's JSON.decycle and JSON.retrocycle we can create a map that handles this case:
var key = {},
map1 = new Map([ [1, key], [key, 3] ]),
map2 = new Map(JSON.parse(JSON.stringify([...map1.entries()]))),
map3 = new Map(JSON.retrocycle(JSON.parse(JSON.stringify(JSON.decycle([...map1.entries()])))));
map1.get(1); // key
map2.get(1); // key
map3.get(1); // key
map1.get(map1.get(1)); // 3 :)
map2.get(map2.get(1)); // undefined :(
map3.get(map3.get(1)); // 3 :)
Decycle and retrocycle make it possible to encode cyclical structures and dags in JSON. This is useful if we want to build relations between objects without creating additional properties on those objects themselves, or want to interchangeably relate primitives to objects and visa-versa, by using an ES6 Map.
The one pitfall is that we cannot use the original key object for the new map (map3.get(key); would return undefined). However, holding the original key reference, but a newly parsed JSON map seems like a very unlikely case to ever have.
If you implement your own toJSON() function for any class objects you have then just regular old JSON.stringify() will just work!
Maps with Arrays for keys? Maps with other Map as values? A Map inside a regular Object? Maybe even your own custom class; easy.
Map.prototype.toJSON = function() {
return Array.from(this.entries());
};
That's it!
prototype manipulation is required here. You could go around adding toJSON() manually to all your non-standard stuff, but really you're just avoiding the power of JS
DEMO
test = {
regular : 'object',
map : new Map([
[['array', 'key'], 7],
['stringKey' , new Map([
['innerMap' , 'supported'],
['anotherValue', 8]
])]
])
};
console.log(JSON.stringify(test));
outputs:
{"regular":"object","map":[[["array","key"],7],["stringKey",[["innerMap","supported"],["anotherValue",8]]]]}
Deserialising all the way back to real Maps isn't as automatic, though. Using the above resultant string, I'll remake the maps to pull out a value:
test2 = JSON.parse(JSON.stringify(test));
console.log((new Map((new Map(test2.map)).get('stringKey'))).get('innerMap'));
outputs
"supported"
That's a bit messy, but with a little magic sauce you can make deserialisation automagic too.
Map.prototype.toJSON = function() {
return ['window.Map', Array.from(this.entries())];
};
Map.fromJSON = function(key, value) {
return (value instanceof Array && value[0] == 'window.Map') ?
new Map(value[1]) :
value
;
};
Now the JSON is
{"regular":"object","test":["window.Map",[[["array","key"],7],["stringKey",["window.Map",[["innerMap","supported"],["anotherValue",8]]]]]]}
And deserialising and use is dead simple with our Map.fromJSON
test2 = JSON.parse(JSON.stringify(test), Map.fromJSON);
console.log(test2.map.get('stringKey').get('innerMap'));
outputs (and no new Map()s used)
"supported"
DEMO
The accepted answer will fail when you have multi dimentional Maps. One should always keep in mind that, a Map object can take another Map object as a key or value.
So a better and safer way of handling this job could be as follows;
function arrayifyMap(m){
return m.constructor === Map ? [...m].map(([v,k]) => [arrayifyMap(v),arrayifyMap(k)])
: m;
}
Once you have this tool then you can always do like;
localStorage.myMap = JSON.stringify(arrayifyMap(myMap))
// store
const mapObj = new Map([['a', 1]]);
localStorage.a = JSON.stringify(mapObj, replacer);
// retrieve
const newMapObj = JSON.parse(localStorage.a, reviver);
// required replacer and reviver functions
function replacer(key, value) {
const originalObject = this[key];
if(originalObject instanceof Map) {
return {
dataType: 'Map',
value: Array.from(originalObject.entries()), // or with spread: value: [...originalObject]
};
} else {
return value;
}
}
function reviver(key, value) {
if(typeof value === 'object' && value !== null) {
if (value.dataType === 'Map') {
return new Map(value.value);
}
}
return value;
}
I wrote here the explanation about replacer and reviver functions here https://stackoverflow.com/a/56150320/696535
This code will work for any other value like regular JSON.stringify so there's no assumption that the serialised object must be a Map. It can also be a Map deeply nested in an array or an object.
One thing that is being left outis that Map is an ORDERED structure - i.e. when iterating the first item entered would be the first listed.
This is NOT like a Javascript Object. I required this type of structure (so i used Map) and then to find out that JSON.stringify doesn't work is painful (but understandable).
I ended up making a 'value_to_json' function, which means parsing EVERYTHING -
using JSON.stringify only for the most basic 'types'.
Unfortunately subclassing MAP with a .toJSON() doesn't work as it excepts a value not a JSON_string. Also it is considered legacy.
My use case would be exceptional though.
related:
https://github.com/DavidBruant/Map-Set.prototype.toJSON/issues/16
JSON left out Infinity and NaN; JSON status in ECMAScript?
How to stringify objects containing ES5 Sets and Maps?
JSON stringify a Set
function value_to_json(value) {
if (value === null) {
return 'null';
}
if (value === undefined) {
return 'null';
}
//DEAL WITH +/- INF at your leisure - null instead..
const type = typeof value;
//handle as much as possible taht have no side effects. function could
//return some MAP / SET -> TODO, but not likely
if (['string', 'boolean', 'number', 'function'].includes(type)) {
return JSON.stringify(value)
} else if (Object.prototype.toString.call(value) === '[object Object]') {
let parts = [];
for (let key in value) {
if (Object.prototype.hasOwnProperty.call(value, key)) {
parts.push(JSON.stringify(key) + ': ' + value_to_json(value[key]));
}
}
return '{' + parts.join(',') + '}';
}
else if (value instanceof Map) {
let parts_in_order = [];
value.forEach((entry, key) => {
if (typeof key === 'string') {
parts_in_order.push(JSON.stringify(key) + ':' + value_to_json(entry));
} else {
console.log('Non String KEYS in MAP not directly supported');
}
//FOR OTHER KEY TYPES ADD CUSTOM... 'Key' encoding...
});
return '{' + parts_in_order.join(',') + '}';
} else if (typeof value[Symbol.iterator] !== "undefined") {
//Other iterables like SET (also in ORDER)
let parts = [];
for (let entry of value) {
parts.push(value_to_json(entry))
}
return '[' + parts.join(',') + ']';
} else {
return JSON.stringify(value)
}
}
let m = new Map();
m.set('first', 'first_value');
m.set('second', 'second_value');
let m2 = new Map();
m2.set('nested', 'nested_value');
m.set('sub_map', m2);
let map_in_array = new Map();
map_in_array.set('key', 'value');
let set1 = new Set(["1", 2, 3.0, 4]);
m2.set('array_here', [map_in_array, "Hello", true, 0.1, null, undefined, Number.POSITIVE_INFINITY, {
"a": 4
}]);
m2.set('a set: ', set1);
const test = {
"hello": "ok",
"map": m
};
console.log(value_to_json(test));
js use the localStorage API to store the ES6 Map
bug
"[object Map]" ❌
(() => {
const map = new Map();
map.set(1, {id: 1, name: 'eric'});
// Map(1) {1 => {…}}
// ❌
localStorage.setItem('app', map);
localStorage.getItem('app');
// "[object Map]"
})();
solution
use JSON.stringify to serialize the Map object before storing it and then use JSON.parse to deserialize it before access the the Map object ✅
(() => {
const map = new Map();
map.set(1, {id: 1, name: 'eric'});
// Map(1) {1 => {…}}
// ✅
localStorage.setItem('app', JSON.stringify([...map]));
const newMap = new Map(JSON.parse(localStorage.getItem('app')));
// Map(1) {1 => {…}}
})();
screenshots
refs
https://www.cnblogs.com/xgqfrms/p/14431425.html
It's important to remember that if you try to setItem on a huge map collection, it will throw Quota Exceeded Error. I tried persisting to local storage a map with 168590 entries and got this error. :(
Here is what I need to do. I have an object that goes
{"MainASubB":"AB","MainBSubC":"BC"...}
Every once in a while I need to take out all attributes that start MainA prior to putting in a new attribute starting MainA. In the example above the object transformations would be
{"MainASubB":"AB","MainBSubC":"BC"...} =>
{"MainBSubC":"BC"...} => //MainASubB has now been taken out
{"MainASubD":"AB","MainBSubC":"BC"...}; A new MainA group attribute, MainASubD has now been added.
I am aware of what Javascript delete can do but on its own I dont think it quite takes me all the way there. I should mention that
At times there may be no MainA group attribute present in the first place.
Provided the code works as intended there can never be more than one MainA group attribute.
Stringifying the object, cleaning out the string as required, then de-stringifying it and then finally putting in the new MainA group attribute is certainly possible but I am wondering if there is another techique, perhaps one reliant on jQuery?, that will get me there faster.
You have to iterate over the properties, compare each name and then delete the property:
for (var prop in obj) {
if (prop.indexOf('MainA') === 0) { // property name starts with 'MainA'
delete obj[prop];
break; // since there can be only one
}
}
I wouldn't use such "hierarchical" property names though. Why not use nested objects and just overwrite the value as you see fit?
For example:
var obj = {
MainA: {
SubA: '...'
},
MainB: {
SubA: '...'
}
};
and then it's just:
obj.MainA = {SubD: '...'};
or just add the "sub" value:
obj.MainA.SubD = '...';
This would be more flexible in the long run.
Here is a generic function :
function removeProperties (obj, prop) {
Object.keys (obj).forEach (
function (p) {
if (typeof prop === 'string' ? p.indexOf (prop) == 0 : prop.test (p))
delete obj[p];
});
return obj;
}
The parameter obj is the object wheich you want to remove properties from. Parameter prop can be a string, 'MainA' for example, in which case any properties with names starting with that string will be removed. If 'prop is a regular expression then any properties whose names match it will be removed.
The removal is done in-place, i.e obj itself is modified, it is also returned as the result of the function.
See it in action here : http://jsfiddle.net/jstoolsmith/EpSxC/