I have been examining the spec for the upcoming Map data structure in ES6. It's supposed to be great because anything can be a key in a Map, not just strings, but when I tried it with a few object examples, I could not retrieve the values I had inserted into the Map.
var _projects = new Map();
_projects.set({}, [...]);
_projects.set({page: 2}, [...]);
_projects.has({page:2})
false
Then I saw this in the Mozilla developers site:
Key equality is based on the "same-value" algorithm: NaN is considered
the same as NaN (even though NaN !== NaN) and all other values are
considered equal according to the semantics of the === operator.
If this is the case, then what are my options if I want to retrieve the values for an object based on its value, rather than its identity?
Remember that {x:5} !== {x:5} is true because they are two different objects.
You have to reference them.
Meaning you can't just _projects.has({x:5}) because that's a new object.
You have to do the following:
var _projects = new Map();
var obj = {x:5};
_projects.set(obj, [1,2]);
_projects.has(obj) //true;
Related
This question already has answers here:
How do I persist a ES6 Map in localstorage (or elsewhere)?
(10 answers)
Convert ES6 Class with Symbols to JSON
(6 answers)
Is there a way of specifying what data of an object is passed to the serializer when it's being serialized?
(2 answers)
Closed 5 years ago.
I am developing an Angular app. I use Typescript and have a certain class which contains the built-in Set and a Map types. My problem is that I want to store instances of my class in the browser's localStorage as JSON, but when I try to stringify them I get empty strings for these types.
Example from chrom's console:
> inst = new Object({
'elem': new Set(),
'str': 'somestring'
})
: Object {elem: Set(0), str: "somestring"}elem: Set(0)str: "somestring"}
> inst.elem.add('123');
: Set(1) {"123"}
> inst.elem.add('123');
: Set(1) {"123"}size: (...)__proto__: Set[[Entries]]: Array(1)0: "123"length: 1
> JSON.stringify(inst)
: "{"elem":{},"str":"somestring"}"
The only thing I could think of is recursively converting Sets and Maps to Arrays and Objects respectively before stringifying. But it sounds like much effort.
Is there a better way? Thanks.
Converting a Map to a simple Object won't work, unless the keys happen to be all strings. Remember that Maps can have anything as keys, including things that are not equal by reference but that will result in the same string when coerced.
The obvious way to serialise Maps and Sets would be, indeed, to convert them to arrays and then serialise that. Fortunately, you don't have to write much code yourself, just use the built-in iterators:
const map = new Map([['a', 1], ['b', 2]])
const set = new Set([2,3,5,7,11])
serializedMap = JSON.stringify([...map]) // => [["a", 1], ["b", 2]]
serializedSet = JSON.stringify([...set]) // => [2,3,5,7,11]
Now, as you noticed, you'll have some trouble with nested Maps and Sets. To avoid writing specific code to dive into objects and turn their deeply nested Map and Set values into arrays, you can define the toJSON method on Map and Set prototypes. The method is called implicitly by JSON.stringify:
Map.prototype.toJSON = function () {
return [...this]
}
// or, if you really want to use objects:
Map.prototype.toJSON = function () {
var obj = {}
for(let [key, value] of this)
obj[key] = value
return obj
}
// and for Sets:
Set.prototype.toJSON = function () {
return [...this]
}
Be careful, toJSON may be defined for Sets and Maps sometime in the future, with a different behaviour than this. Although it seems unlikely.
Now, whenever you call JSON.stringify and it sees a Map or Set, it will notice the toJSON method and use it instead of just copying the object's properties.
Another, similar and more ECMA-approved solution is to use JSON.stringify's second argument. Define a helper function that will preprocess values, replacing Maps and Sets with appropriate arrays or objects:
function mapReplacer(key, value) {
if(value instanceof Map || value instanceof Set) {
return [...value]
// of course you can separate cases to turn Maps into objects
}
return value
}
Now just pass mapReplacer to JSON.stringify:
JSON.stringify(map, mapReplacer)
I'm looking to implement a c++ map variable type in JavaScript but can not seeing anything on the internet. When I used SFML I created a std::map of type String and SFML::Texture so I could store a texture inside of the map and get the texture value by using the key, how would I implement this into JavaScript?
The reason I want to do this is I want to load all image assets at the start of my HTML5 game, and then I can call on the image map and get the image reference using the key.
The standard way in JavaScript is just an object:
var map = {};
map["any key you like"] = any_value_you_like;
// Also:
map.any_name_you_like_that_fits_identifer_name_rules = any_value_you_like;
The keys are always strings (for now, ES6 will add symbol/name keys), the values are any JavaScript value. When you use dot notation (obj.foo), the property name is a literal (and subject to JavaScript's rules for IdentifierName); when you use brackets notation and a string (obj["foo"]), the property name can be just about anything (and can be the result of any expression, such as a variable lookup).
Example: Live Copy
var map = {};
map["a"] = "alpha";
map["b"] = "beta";
map["c"] = "gamma";
["a", "b", "c"].forEach(function(key) {
display("map['" + key + "'] = " + map[key]);
});
Output:
map['a'] = alpha
map['b'] = beta
map['c'] = gamma
dandavis raises an interesting point: When you create an object via {}, it inherits properties from Object.prototype, but as of ES5 you can use Object.create(null), which doesn't:
var map = Object.create(null);
map["any key you like"] = any_value_you_like;
// Also:
map.any_name_you_like_that_fits_identifer_name_rules = any_value_you_like;
That way, if you happen to have the potential for keys with names like toString or valueOf (which are on Object.prototype), you won't get false positives when looking them up on an object where you haven't set them explicitly.
You can now use Map data structure. It was introduced in JavaScript via ES6 and allows to create collections of key-value pairs. Unlike objects, a map object can hold both objects and primitives as keys and values. Here is example:
const items = new Map();
// add items
items.set('πΆ', 'Dog');
items.set('π¦
', 'Eagle');
items.set('π', 'Train');
items.set(45, 'Number');
items.set(true, 'Boolean');
// iterate over map
for (const [key, value] of items) {
console.log(`${key}: ${value}`);
}
Output:
πΆ: Dog
π¦
: Eagle
π: Train
45: Number
true: Boolean
More examples
Any programming language which provides arrays (lists, vectors, tuples etc.) must decide whether they have reference or value semantics, with the usual/obvious choice being reference semantics for mutable arrays and value semantics for immutable ones.
JavaScript which provides mutable arrays appears to have chosen reference semantics e.g. given
var a = [1, 2, 3]
var b = [1, 2, 3]
then a != b, as expected because though they have the same contents, they are different arrays.
However when you use them as keys in an object, the picture changes; if you set obj[a] to a value, then obj[b] gets the same value. Furthermore, this remains true if you change the contents of the arrays; at least when I tested it in Rhino, it behaves as though the interpreter were recursively comparing the full contents of the supplied and stored key arrays on every lookup, complete with a check for the infinite loop that would occur if one of the arrays were made to point to itself.
Is this the intended/specified behavior in all implementations?
Does it also apply to objects used as keys?
Is there any way to get the other behavior, i.e. to look up values using arrays as keys with reference semantics?
When arrays are used as property names they are cast to a string:
[1,2,3].toString() == '1,2,3'
Once turned into a string value, arrays with the same contents would map to the same property.
To answer your last question, you can't use objects to reference property names (keys) whereby only the same object maps to the same property (1:1 mapping).
obj[a] and obj[b] will run the toString function on the arrays and produce the same result for both. It doesn't try to use the arrays as keys.
var a = [1,2,3];
var x = {};
x[a] = "test";
var i;
for(i in x)
{
alert(i); //"1,2,3"
}
jsFiddle example
I was trying to stringify an array-like object that was declared as an array object and found that JSON.stringify wasn't processing correctly array-like object when it is defined as an array object.
See below for more clarity, --> jsFiddle
var simpleArray = []; //note that it is defined as Array Object
alert(typeof simpleArray); // returns object -> Array Object
simpleArray ['test1'] = 'test 1';
simpleArray ['test2'] = 'test 2';
alert(JSON.stringify(simpleArray)); //returns []
It worked fine and returned me {"test1":"test 1","test2":"test 2"} when I changed
var simpleArray = []; to var simpleArray = {};.
Can someone shed some light or some reference where I can read more?
Edit:
Question: When typeof simpleArray = [] and simpleArray = {} returned object, why JSON.stringify wasn't able to return {"test1":"test 1","test2":"test 2"} in both cases?
The difference is the indexes. When you use an array [] the indexes can only be positive integers.
So the following is wrong:
var array = [ ];
array['test1'] = 'test 1';
array['test2'] = 'test 2';
because test1 and test2 are not integers. In order to fix it you need to use integer based indexes:
var array = [ ];
array[0] = 'test 1';
array[1] = 'test 2';
or if you declare a javascript object then the properties can be any strings:
var array = { };
array['test1'] = 'test 1';
array['test2'] = 'test 2';
which is equivalent to:
var array = { };
array.test1 = 'test 1';
array.test2 = 'test 2';
You don't want an array. When you want an "associative array" in JavaScript, you really want an object, {}.
You can distinguish them with instanceof Array:
[] instanceof Array // true
({}) instanceof Array // false
EDIT: it can process it. It serializes all of the elements of the array. However, to be an element, there must be a numeric key. In this case, there are none.
This is nothing unique to JSON. It's consistent with toSource and the length property.
"Can someone shed some light or some reference where I can read more?"
When you're dealing with JSON data, you can refer to json.org to read about the requirements of the specification.
In JSON, an Array is an order list of values separated by ,.
So the JSON.stringify() is simply ignoring anything that couldn't be represented as a simple ordered list.
So if you do...
var simpleArray = [];
simpleArray.foo = 'bar';
...you're still giving an Array, so it is expecting only numeric indices, and ignoring anything else.
Because JSON is language independent, the methods for working with JSON need to make a decision about which language structure is the best fit for each JSON structure.
So JSON has the following structures...
{} // object
[] // array
You should note that though the look very similar to JavaScript objects and arrays, they're not strictly the same.
Any JavaScript structures used to create JSON structures must conform to what the JSON structures will allow. This is why the non-numeric properties are removed excluded.
While JavaScript doesn't mind them, JSON won't tolerate them.
When JSON.stringify encounters an array, it iterates in a similar way to a for loop from 0 to simpleArray.length to find the values. For example:
var a = [];
a[5] = "abc";
alert(JSON.stringify(a)); // alerts [null,null,null,null,null,"abc"]
Therefore setting properties on it will be completely invisible to JSON.
However defining the object with {} makes JSON treat it as an object, and therefore it loops over the object's properties (excluding inherited properties from the prototype chain). In this manner it can find your test1 and test2 properties, and successfully returns what you expect.
The differences between Array instances and other objects are specified in the ECMAScript Language Specification, Edition 5.1, section 15.4.
You will find there that while you can use the bracket property accessor syntax with all object references --
objRef[propertyName]
-- Array instances are special. Only using a parameter value which string representation is that of an unsigned 32-bit integer value less than 232-1 accesses an element of the encapsulated array structure, and write access affects the value of the Array instance's length property.
Section 15.12 specifies the JSON object and its stringify method accordingly.
In Javascript, everything is an object, so even Arrays.
This is why you get:
>> var l = [1, 2];
>> typeof l
"object"
Arrays are stored the same way as objects. So, in fact, arrays are only hashtable objects. The index you provide when accessing e.g.
>> l[0]
is not interpreted as an offset, but is hashed and then searched for.
So Arrays, are just objects (with some builtin array-methods) and so you can treat them like objects and put a value under a certain key.
But only those keys indexed by numbers are used to compute the length.
>> var l = []
>> l.length
0
>> l[5] = 1;
>> l.length
6
>> l
[undefined, undefined, undefined, undefined, undefined, 1]
>> l.hello = "Hello"
>> l.length
6
Read Array - MDN for more information
and Associative Arrays considered harmful why you should not do this.
Your code is not semantically correct, but because most JavaScript engines are really nice on the programmer they allow these types of mistakes.
A quick test case:
var a = [];
a.b = "c";
console.log(JSON.stringify(a));//yields [], and not {"b":"c"} as you might expect
This might be because of some strictness in JSON.stringify which still treats a like an Array.
I wouldn't worry about it overly much. This is a situation that should not occur in your program because it is an error. JSLint is a popular tool to catch these and many more potential problems in your code.
After reading this description: http://wiki.ecmascript.org/doku.php?id=harmony:weak_maps
I'm trying to get a hang of it, but I do not get the overall picture. What is it all about? It seems to be supported in Firefox 6: http://kangax.github.com/es5-compat-table/non-standard/
A weak reference is a special object containing an object-pointer, but does not keep that object alive.
One application of weak references are implemented in Weak Maps:
βThe experienced JavaScript programmer will notice that this API could be implemented in JavaScript with two arrays (one for keys, one for values) shared by the 4 API methods. Such an implementation would have two main inconveniences. The first one is an O(n) search (n being the number of keys in the map). The second one is a memory leak issue. With manually written maps, the array of keys would keep references to key objects, preventing them from being garbage collected. In native WeakMaps, references to key objects are held βweaklyβ, which means that they do not prevent garbage collection in case there would be no other reference to the object.β Source
(See also my post when ECMAScript Harmony was first released with Firefox... )
WeakMap
WeakMaps basically allow you to have a HashTable with a key that isn't a String.
So you can set the key to be, i.e. [1] and then can say Map.get([1])
Example from the MDN:
var wm1 = new WeakMap(),
wm2 = new WeakMap();
var o1 = {},
o2 = function(){},
o3 = window;
wm1.set(o1, 37);
wm1.set(o2, "azerty");
wm2.set(o1, o2); // a value can be anything, including an object or a function
wm2.set(o3, undefined);
wm2.set(wm1, wm2); // keys and values can be any objects. Even WeakMaps!
wm1.get(o2); // "azerty"
wm2.get(o2); // undefined, because there is no value for o2 on wm2
wm2.get(o3); // undefined, because that is the set value
wm1.has(o2); // true
wm2.has(o2); // false
wm2.has(o3); // true (even if the value itself is 'undefined')
wm1.has(o1); // true
wm1.delete(o1);
wm1.has(o1); // false
The reason for its existance is:
in order to fix a memory leak present in many uses of weak-key tables.
Apparently emulating weakmaps causes memory leaks. I don't know the details of those memory leaks.
WeakMap allows to use objects as keys.
It does not have any method to know the length of the map. The length is always 1.
The key can't be primitive values
A word of caution about using object as key is, since all the objects are by default singletons in JavaScript we should be creating an object reference and use it.
This is because when we create anonymous objects they are different.
if ( {} !== {} ) { console.log('Objects are singletons') };
// will print "Objects are singletons"
So in the following scenario, we can't expect to get the value
var wm = new WeakMap()
wm.set([1],'testVal');
wm.get([1]); // will be undefined
And the following snippet will work as expected.
var a = [1];
wm.set(a, 'testVal');
wm.get(a); // will return 'testVal'