How would I go on to get the information in the same format as it was saved?
Saved as, example:
document.querySelector('.btn').addEventListener('click', function(event){
const store = {
ci: document.querySelector('.a').innerHTML,
};
localStorage.setItem(store.city, JSON.stringify(store))
This is my code, the storage got different keys:
function myFunction(){
document.querySelector('.a').innerHTML = JSON.stringify(localStorage.getItem(i))}
Although it was not clear to me what is your expectation or expected behavior. But I will try to give an answer with the limited data.
You can get all the keys in localStorage by Object.keys method. And then iterate over the keys and JSON.parse all the localStorage[key].
Object.keys(localStorage).map(key => console.log(localStorage[key]))
That should solve your problem of getting the objects from localStorage.
According to the docs, Storage items can only store data of type DOMString. Converting other data types into a string using the JSON.stringify function is a valid idea!
You only need to reverse this change when reading the data:
function setObjectItem(key, item){
return localStorage.setItem(key, JSON.stringify(item));
}
function getObjectItem(key){
return JSON.parse(localStorage.getItem(key));
}
However, some data types can not be converted into a JSON string. For example, you cannot use this method to store DOM elements.
After setting up eslint-plugin-security, I went on to attempt to address nearly 400 uses of square brackets in our JavaScript codebase (flagged by the rule security/detect-object-injection). Although this plugin could be a lot more intelligent, any uses of square brackets could possibly be an opportunity for a malicious agent to inject their own code.
To understand how, and to understand the whole context of my question, you need to read this documentation: https://github.com/nodesecurity/eslint-plugin-security/blob/master/docs/the-dangers-of-square-bracket-notation.md
I generally tried to use Object.prototype.hasOwnProperty.call(someObject, someProperty) where I could to mitigate the chance that someProperty is maliciously set to constructor. Lot of situations were simply dereferencing an array index in for loops (for (let i=0;i<arr.length;i++) { arr[i] }) If i is always a number, this is obviously always safe.
One situation I don't think I have handled perfectly, are square bracket assignments like this:
someObject[somePropertyPotentiallyDefinedFromBackend] = someStringPotentiallyMaliciouslyDefinedString
I think the easiest way to solve this issue is with a simple util, safeKey defined as such:
// use window.safeKey = for easy tinkering in the console.
const safeKey = (() => {
// Safely allocate plainObject's inside iife
// Since this function may get called very frequently -
// I think it's important to have plainObject's
// statically defined
const obj = {};
const arr = [];
// ...if for some reason you ever use square brackets on these types...
// const fun = function() {}
// const bol = true;
// const num = 0;
// const str = '';
return key => {
// eslint-disable-next-line security/detect-object-injection
if (obj[key] !== undefined || arr[key] !== undefined
// ||
// fun[key] !== undefined ||
// bol[key] !== undefined ||
// num[key] !== undefined ||
// str[key] !== undefined
) {
return 'SAFE_'+key;
} else {
return key;
}
};
})();
You'd then use it like so:
someObject[safeKey(somePropertyPotentiallyDefinedFromBackend)] = someStringPotentiallyMaliciouslyDefinedString
This means if the backend incidentally sends JSON with a key somewhere of constructor we don't choke on it, and instead just use the key SAFE_constructor (lol). Also applies for any other pre-defined method/property, so now the backend doesn't have to worry about JSON keys colliding with natively defined JS properties/methods.
This utility function is nothing without a series of passing unit tests. As I've commented not all the tests are passing. I'm not sure which object(s) natively define toJSON - and this means it may need to be part of a hardcoded list of method/property names that have to be blacklisted. But I'm not sure how to find out every one of these property methods that needs to be blacklisted. So we need to know the best way anyone can generate this list, and keep it updated.
I did find that using Object.freeze(Object.prototype) helps, but I don't think methods like toJSON exist on the prototype.
How can we make sure the property being set is essentially not already defined on vanilla objects? (i.e. constructor)
It is more important to prevent a key from being accessed on the wrong object than to validate/protect object keys themselves. Designating certain object keys as ‘unsafe’ and avoiding accessing just these regardless of the circumstances is just another form of the ‘sanitizing’ anti-pattern. If the object doesn’t contain sensitive data in the first place, there is no risk of it being exfiltrated or modified by untrusted inputs. You don’t need to worry about accessing src or innerHTML if you don’t access it on a DOM node; you don’t need to worry about exposing eval if you don’t perform lookups on the global object. As such:
Only use bracket notation with objects that either are arrays or specifically contain nothing other than a mapping from arbitrary strings to other values (those usually constructed by object literal notation); this kind of object I’m going to call map-like below. When using map-like objects, also ensure the following:
that you never store functions (or, in later versions of ECMAScript, classes or proxies to either) directly in a map-like object. This is to avoid problems when keys like 'toJSON' or 'then' map to functions that the language may then interpret as methods that modify the behaviour of the object. If, for some reason, you need to store functions in a map-like object, put the function in a wrapper like { _: function () { /* ... */ } }.
that you never coerce map-like objects to strings using built-in language mechanisms: the toString method, the String constructor (with or without new), the + operator or Array.prototype.join. This is to avoid triggering problems when the 'toString' key is set on a map-like object, as even a non-function value will prevent the default coercion behaviour from occurring and will instead throw a TypeError.
When accessing arrays, make sure the index is indeed an integer. Consider also using built-in methods like push, forEach, map or filter that avoid explicit indexing altogether; this will reduce the number of places you will need to audit.
If you ever need to associate arbitrary data with an object with a relatively fixed set of keys, e.g. a DOM node, window or an object you defined with class (all of which I’ll call class-like below), and for some reason WeakMap is not available, put the data it on a hardcoded key; if you have more than one such data item, put it in a map-like object a stored on a hardcoded key.
Even when following the above though, you may still fall victim to an injection or exfiltration attack by inadvertently accessing a property of Object.prototype. Particularly worrying are constructor and various built-in methods (which can be leveraged to access the Function object, and ultimately perform arbitrary code execution), and __proto__ (which can be used to modify the prototype chain of an object). To protect against those threats, you may try some of the following strategies. They are not mutually exclusive, but for the sake of consistency it may be preferable to stick with just one.
Mangle all keys: this is probably the (conceptually) simplest option, portable even to engines from the days of ECMAScript 3, and robust even against future additions to Object.prototype (as unlikely as they are). Simply prepend a single non-identifier character to all keys in map-like objects; this will safely namespace away untrusted keys from all reasonably conceivable JavaScript built-ins (which presumably should have names that are valid identifiers). When accessing map-like objects, check for this character and strip it as appropriate. Following this strategy will even make concerns about methods like toJSON or toString mostly irrelevant.
// replacement for (key in maplike)
function maplikeContains(maplike, key) {
return ('.' + key) in maplike;
}
// replacement for (maplike[key])
function maplikeGet(maplike, key) {
return maplike['.' + key];
}
// replacement for (maplike[key] = value)
function maplikeSet(maplike, key, value) {
return maplike['.' + key] = value;
}
// replacement for the for-in loop
function maplikeEach(maplike, visit) {
for (var key in maplike) {
if (key.charAt(0) !== '.')
continue;
if (visit(key.substr(1), maplike[key]))
break;
}
}
Mangling all keys indiscriminately ensures that you will not end up confusing unmangled keys for mangled keys or vice versa. For example if, like in the question, you mangle 'constructor' into 'SAFE_constructor', but leave 'SAFE_constructor' itself as-is, then after mangling both keys will end up referring to the same data, which may be a security vulnerability in itself.
A drawback of this approach is that the prefix character is going to end up in JSON, if you ever serialise such a map-like object.
Enforce direct property access. Read accesses can be guarded with Object.prototype.hasOwnProperty, which will stop exfiltration vulnerabilities, but will not protect you from inadvertently writing to __proto__. If you never mutate such a map-like object, this should not be a problem. You can even enforce immutability using Object.seal. If don’t want that though, you may perform property writes via Object.defineProperty, available since ECMAScript 5, which can create properties directly on the object, bypassing getters and setters.
// replacement for (key in maplike)
function maplikeContains(maplike, key) {
return Object.prototype.hasOwnProperty.call(maplike, key);
}
// replacement for (maplike[key])
function maplikeGet(maplike, key) {
if (Object.prototype.hasOwnProperty.call(maplike, key))
return maplike[key];
}
// replacement for (maplike[key] = value)
function maplikeSet(maplike, key, value) {
Object.defineProperty(maplike, key, {
value: value,
writable: true,
enumerable: true,
configurable: true
});
return value;
}
// replacement for the for-in loop
function maplikeEach(maplike, visit) {
for (var key in maplike) {
if (!Object.prototype.hasOwnProperty.call(maplike, key))
continue;
if (visit(key, maplike[key]))
break;
}
}
Clear the prototype chain: make sure map-like objects have an empty prototype chain. Create them via Object.create(null) (available since ECMAScript 5) instead of {}. If you created them via direct object literals before, you can wrap them in Object.assign(Object.create(null), { /* ... */ }) (Object.assign is available since ECMAScript 6, but easily shimmable to earlier versions). If you follow this approach, you can use the bracket notation as usual; the only code you need to check is where you construct the map-like object.
Objects created by JSON.parse will by default will still inherit from Object.prototype (although modern engines at least add a JSON key like __proto__ directly on the constructed object itself, bypassing the setter from the prototype’s descriptor). You can either treat such objects as read-only, and guard read accesses by hasOwnProperty (as above), or strip away their prototypes by writing a reviver function that calls Object.setPrototypeOf. A reviver function can also use Object.seal to make an object immutable.
function maplikeNew(maplike) {
return Object.assign(Object.create(null), maplike);
}
function jsonParse(json) {
return JSON.parse(json, function (key, value) {
if (typeof value === 'object' && value !== null && !Array.isArray(value))
Object.setPrototypeOf(value, null);
return value;
});
}
Use Maps instead of map-like objects: using Map (available since ECMAScript 6) allows you to use keys other than strings, which isn’t possible with plain objects; but even just with string keys you have the benefit that entries of the map are completely isolated from the prototype chain of the map object itself. Items in Maps are accessed by .get and .set methods instead of the bracket notation and cannot clash with properties at all: the keys exist in a separate namespace.
There is however the problem that a Map cannot be directly serialized into JSON. You can remedy this by writing a replacer function for JSON.stringify that converts Maps into plain, prototype-free map-like objects, and a reviver function for JSON.parse that turns plain objects back into Maps. Then again, naïvely reviving every JSON object into a Map is also going to cover structures I called ‘class-like’ above, which you probably don’t want. To distinguish between them, you might need to add some kind of schema parameter to your JSON-parsing function.
function jsonParse(json) {
return JSON.parse(json, function (key, value) {
if (typeof value === 'object' && value !== null && !Array.isArray(value))
return new Map(Object.entries(value));
return value;
});
}
function jsonStringify(value) {
return JSON.stringify(value, function (key, value) {
if (value instanceof Map)
return Object.fromEntries(value.entries());
return value;
});
}
If you ask me for my preference: use Map if you don’t need to worry about pre-ES6 engines or JSON serialisation; otherwise use Object.create(null); and if you need to work with legacy JS engines where neither is possible, mangle keys (first option) and hope for the best.
Now, can all this discipline be enforced mechanically? Yes, and it’s called static typing. With good enough type definitions, TypeScript should be able to catch cases where class-like objects are accessed in map-like fashion and vice versa. It can even catch some cases where an object with an unwanted prototype appears:
type Maplike<T> = {
[K: string]: T|undefined;
constructor?: T;
propertyIsEnumerable?: T;
hasOwnProperty?: T;
toString?: T;
toLocaleString?: T;
valueOf?: T;
};
const plain: { [K: string]: string } = {};
const naked: Maplike<string> = Object.create(null); // OK
const error: Maplike<string> = {}; // type error
function f(k: string) {
naked[k] = 'yay'; // OK
plain[k] = 'yay'; // OK
document.body[k] = 'yay'; // type error
}
console.info(plain.toString()); // OK
console.info(naked.toString()); // type error
Bear in mind this is no panacea, however. The above type definition may catch the most obvious mistakes, but it’s not too hard to come up with a case which it will not detect.
I want to confirm the behavior I am seeing regarding the this reference, and the ability to modify keys (not just values) in an object using the reviver function.
If I pass my reviver function using function(key,value {...} as opposed to using an arrow function (key, value) => {...}, the this reference seems to refer to the object being revived. This is true for sub-objects in the JSON as well. I am seeing this in node.js 8.x on the server, and in Chrome current on the client.
Understandably, if I pass the function as an arrow function, the calling context is preserved.
I am relying on this to add and delete some keys as I parse the JSON.
Can I rely on this behavior?
var aTestStr = '{"prop1": "this is prop 1",'
+'"prop2": {"prop2A": 25, "prop2B": 13, "prop2C": "This is 2-c"}'
+'}';
var aTestObj = JSON.parse(aTestStr, function(key, value) {
//at this point, this refers to the object being revived
//E.g., when key == 'prop1', this is an object with prop1 and prop2
//when key == prop2B, this is an object with prop2A, prop2B and prop2C
//So is this code reliable?
if (key == this.prop2B) {
//Do something, add a prop to this:
this.prop2BDif = 100 - this.prop2B;
}
});
Yes it is documented: JSON.parse documentation in the MDN
If a reviver is specified, the value computed by parsing is transformed before being returned. Specifically, the computed value and all its properties (beginning with the most nested properties and proceeding to the original value itself) are individually run through the reviver. Then it is called, with the object containing the property being processed as this, and with the property name as a string, and the property value as arguments.
I would like to use Javascript object literals and arrays normally and have them automatically saved in local storage.
I would like to read, edit and delete members of the object literals and arrays as I would with any other object - using normal assignment operator a.b = 'c' for assigning values and normal dot-notation expressions d.e for reading.
When browser next time visits the page, the previous contents of these arrays and object literals would be automatically loaded from the local storage. I wish not to explicitly call anything to store this data, it should just work.
What are my options?
example using methods instead of vanilla properties
var SavedObject= {
b: function(val) {
if (typeof(val)!=="undefined") {
this._b=val;
this.SerialiseToLocalStorage();
}
return val;
},SerialiseToLocalStorage: function() {
//whatever
}
}
SavedObject.b(1); //set value
var x = SaveObject.b(); // get value
You'll also need to initalise the values on document load of course...
i am trying to get a value from a key stored on a string variable proyNombre, but whenever i call it via the common method "myAssociativeArray.MyKey", it gets the variable 'proyNombre' as the key, instead of getting its value and passing it as a key.
proyectos.each(function(index){
var proyNombre = this.value;
if(!(proyNombre in myArray)){ // whenever the variable is undefined, define it
myArray[proyNombre] = horas[index].value-0 + minutos[index].value/60;
}
else{
console.log(myArray.proyNombre); //This doesnt work, it tries to give me the value for the key 'proyNombre' instead of looking for the proyNombre variable
console.log(myArray.this.value); //doesnt work either
}
});
Try:
console.log(myArray[proyNombre]);
myArray is actually an object in javascript. You can access object properties with object.propertyName or, object['propertyName']. If your variable proyNombre contained the name of a property (which it does) you can use the second form, like I did above. object.proyNombre is invalid - proyNombre is a variable. You can't do for example:
var myObject = {};
myObject.test = 'test string';
var s = 'test';
console.log(myObject.s); // wrong!!
but you could then do:
console.log(myObject.test);
console.log(myObject['test']);
console.log(myObject[s]);
You need to use the same syntax you used to set the value:
console.log(myArray[proyNombre]);
Simply access the value with myArray[proyNombre].
You're doing it right in the assignment: myArray[proyNombre]. You can use the same method to retrieve the variable.
If you change:
console.log(myArray.proyNombre);
console.log(myArray.this.value);
to
console.log(myArray[proyNombre]);
console.log(myArray[this.value]);
You should get the same value (the value for the key represented by the variable proyNombre) logged twice.
It's true that Javascript doesn't have associative arrays but objects in Javascript can be treated like associative arrays when accessing their members.