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)
Related
This question already has answers here:
What is the most efficient way to deep clone an object in JavaScript?
(67 answers)
Closed 4 years ago.
So I have a property in a class called 'tiles' which contains information about a checkers board game's state. I'm trying to push this property to an array called 'moves' every time I make a legal move and at the start of the game. But the problem is, every time I push the new tiles property, the previous elements in the moves array change to the values of the latest pushed tiles.
I understand this is happening because the object is being passed by reference and hence replaces old elements in array because they now point to the same object, which is the latest value of the property tiles. So with my code given below, is there a way I can push this object not by reference but each different individual state of 'tiles' that resulted due to legal moves.
Here is my snippet: App.js
App = function () {
var self = this;
self.tiles = [];
// this is populated with objects from a json file
//code to fetch json and save it to self.tiles
//more code
this.startGame = function () {
//other code
self.moves.push(self.tiles);
};
this.makeMove = function () {
//other code
self.moves.push(self.tiles);
};
};
So what I expect is in self.moves array, the tiles should point to different objects instead of the same object. It should contain different states of self.tiles but right now, as I push the property, the elements of 'moves' array is overwritten by the latest self.tiles value.
Any help to solve this problem will be highly appreciated. Thanks!
You should use JSON.parse(JSON.stringify()) to clone a nested object.You can use Object.assign to clone shallow object
App = function () {
var self = this;
self.tiles = [];
// this is populated with objects from a json file
//code to fetch json and save it to self.tiles
//more code
this.startGame = function () {
//other code
self.moves.push(JSON.parse(JSON.stringify(self.tiles)));
};
this.makeMove = function () {
//other code
self.moves.push(JSON.parse(JSON.stringify(self.tiles)));
};
};
I've fiddled around with it for a bit and found you can use the spread operator like so:
var a = {b: 1, c: 2}
var array1 = []
array1.push({...a})
a.c=3
console.log(array1) // [0: {b: 1, c: 2}]
console.log(a) // {b: 1, c: 3}
MDN: Spread in object literals
The only way you can address your issue is to pass the clone of the object you want to push into the vector. In such cases normally you would write a clone() method to your object that returns a deep copy of itself. The object returned by this method can be pushed to the array.
In JavaScript, you can have objects, like this:
var a = { foo: 12, bar: 34 };
Or arrays with key (named) indexes, like this:
var b = [];
b['foo'] = 56;
b['bar'] = 78;
They're somewhat similar, but obviously not the same.
Now the strange thing is, JSON.stringify doesn't seem to take the array. No errors or anything, JSON.stringify(b) just results in [].
See this jsfiddle example. Am I doing something wrong, or do I just misunderstand how arrays work?
Javascript doesn't support Associative arrays (Like PHP).
var b = []; Declaring explicitly an array, when you are trying to create an Object.
Arrays in Javascript can only contain the Index approach of Arrays, while Objects are more of
Associative arrays.
If you change var b = []; to var b = {}; it will solve the problem.
var b = {} Declaring explicitly an Object.
Javascript arrays are objects. See https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Predefined_Core_Objects#Array_Object for details.
Note: if you supply a non-integer value to the array operator in the
code above, a property will be created in the object representing the
array, instead of an array element.
JSON supports only a subset of Javascript. See http://www.json.org/ for details.
JSON is built on two structures:
A collection of name/value pairs. In various languages, this is realized as an object, record, struct, dictionary, hash table, keyed
list, or associative array.
An ordered list of values. In most languages, this is realized as an array, vector, list, or sequence.
A Javascript array that has properties created in the underlying object does not fit into either of these structures because it has both a collection of name/value pairs and an ordered list of values, so there is no simple way to represent such an object directly in JSON.
The JSON.stringify method is defined in the ECMAScript specification. For example, see http://www.ecma-international.org/ecma-262/5.1/#sec-15.12.3.
While there are many details, the bit that is relevant here is how object values are stringified:
If Type(value) is Object, and IsCallable(value) is false
If the [[Class]] internal property of value is "Array" then Return the result of calling the abstract operation JA with argument value.
Else, return the result of calling the abstract operation JO with argument value.
Given your array, despite the addition of parameters to the underlying object, the result is of stringifying the ordered set of array elements, not the underlying object.
There is nothing wrong about adding parameters to an array object, but they are not part of the array and functions or methods that handle arrays might ignore them or deal with them arbitrarily. You have seen that JSON.stringify ignores the additional parameters. Other functions might do otherwise - you will have to find out in each case.
While it is not wrong, it will probably be easier to understand if you do not add properties to array objects. If you want to add properties, start with a non-array object.
Rather than:
var b = [];
b['foo'] = 56;
b['bar'] = 78;
You might use:
var b = {};
b['foo'] = 56;
b['bar'] = 78;
This snap is from IE explorer. See the array is still blank.
Actually the way of inserting the elements to the array is :
1. Use push()
2. insert the elements in the array during declaration
If you want to stringify the array you have to have the data inside the array.
So, now you want to stringify the key value pairs so you have to pass the object as the argument of JSON.stringify() as follows:
var test = {}; // Object
test['a'] = 'test';
test['b'] = []; // Array
test['b'].push('item');
test['b'].push('item2');
test['b'].push('item3');
var json = JSON.stringify(test);
alert(json);
Solution to your problem now:
Note: Console of Google Chrome is giving different result, which is a bug in Google Chrome.
This question already has answers here:
Define a custom hash() method for use with ES6 maps
(2 answers)
Closed 4 years ago.
I couldn't find more information on this but as far as I understand has method from Map and Set collections determines whether a given map or set has an object by checking for reference equality. But I'd like to check whether the objects are equal by comparing their properties.
For example:
let map = new Map();
let obj = {name: "Jack"};
map.set(obj, "myObj");
let result = map.has({name: "Jack"}); //false
result is false but the properties of the objects are identical, so in such case I'd like has to return true.
In Java I'd have to override equals and hashCode methods inside my class to achieve such functionality. Is there something similar I can do in Javascript without overriding has itself?
but the properties of the objects are identical
Really? As this answer outlines, it is usually very hard to define the identity of an object, as it is built of much more than sinple key-value pairs. Therefore it is impossible to derive a unique hash for it to be used in a hashtable, thats why javascript uses the object reference in the Hashtables. It also makes sense as you could get collisions otherwise. Additionally, if you change an object that is included in a hashtable so that it equals to another one, what should happen then?
Now in a lot of cases, you probably know how the object is built and how you can get its identity. Imagine a player that has a unique username, e.g.:
function Player(username, password) {
this.username = username;
this.password = password
}
Now when we want to build a Set to check if an username is already existing, it makes sense to just use the Players username instead of the player itself:
const a = new Player("a", "super secret");
const b = new Player("b", "****");
new Set([a.username, b.username]);
Or you could define a way to build up a unique key from the players properties:
Player.prototype.identity = function() {
return this.username + "°" + this.password;
};
So one can do:
const a = new Player("this is", "equal");
const b = new Player("this is", "equal");
console.log(
a === b, // false
a.identity() === b.identity() // true
);
const players = new Set([a.identity()]);
players.has(b.identity()); // true
This question already has answers here:
Object comparison in JavaScript [duplicate]
(10 answers)
Closed 5 years ago.
Is there a way to compare two un-referenced objects by their literal value like a string or a number?
_is = function (a, b) { /* Code */ }
This could apply to any object type, even custom objects.
_is(new X("a"), new X("a")); // Returns True;
_is(new X("a"), new Y("a")); // Returns False
You could convert it into a string, but that would be sloppy.
JSON.stringify({ x: "a" }) == JSON.stringify({ x: "a" }); // Returns True
Maybe there's a way to programatically read each key, subkey, and value of the object, and compare that way.
Any ideas?
Javascript hashes objects in memory with unique memory pointers. You're essentially creating two separate objects, and trying to compare them. Despite the fact that they look similar, they in fact are different.
Try storing the object as a variable first, and then testing against that variable.
pass = arg => { return arg }; pass({ a: "x" }); // Returns The Input
pass(({ a: "x" })) == ({ a: "x" }); // Returns False
var obj = {a: "x"};
pass(obj) === (obj); // Returns true
console.log(pass(obj) === (obj));
UPDATE:
If you want to compare two separate objects, the best and most efficient way I've found is to create a hash of both objects, and then compare the hashes (basically comparing two checksums). If the hashes match, the objects are the same. If the hashes differ, they are different objects. You can assume that the properties and values are the same in each object, since using a hashing function, even hashing functions used in some forms of crypto like md5 and sha can be utilized to generate a unique hash of the object.
Here's a 3rd Party Library I've used before to generate object hashes.
This question already has answers here:
indexOf method in an object array?
(29 answers)
Closed 7 years ago.
UPDATE: Although this question is marked as duplicated with this. But #ssube's way is neat and much smarter.
UPDATE2: Seems there is new way to do it in the comment by #Grungondola.
I am using Typescript.
This works well.
var array1 = [];
array1.push(5);
array1.push(6);
console.log("a", array2.indexOf(6));
But this does not work well. Because array2.indexOf returns -1 which means it does not find it.
var array2 = [];
array2.push({aa:5,bb:5});
array2.push({aa:6,bb:6});
console.log(array2.indexOf({aa:6,bb:6}));
Looks like indexOf does not support Object. Does TypeScript have its own ways to deal with this kind of problem? Thanks.
No. The problem is not with Object, but that you are creating two different objects.
The object literal syntax ({foo: 'bar'}) declares an object inline. When the script is executed, the object is created. Using that syntax multiple times creates multiple objects.
You can easily test that with {foo: 3} === {foo: 3}. This will evaluate to false, but they are not the same object (reference).
The indexOf method checks if the object, string, number, etc, is present in the array. You're passing a new object, which is not in the array.
If you have a reference to the object, you can use that and indexOf will work:
var foo = {aa:5,bb:5}, bar = {aa:6,bb:6};
var array2 = [];
array2.push(foo);
array2.push(bar);
console.log(array2.indexOf(foo));
Because you're referring to the same instance, this will print the index.
You can also use filter or find with a predicate to perform a deep search:
function deepIndexOf(arr, obj) {
return arr.findIndex(function (cur) {
return Object.keys(obj).every(function (key) {
return obj[key] === cur[key];
});
});
}
var array2 = [];
array2.push(foo);
array2.push(bar);
console.log(deepIndexOf(array2, foo));
This won't recurse into nested objects, but will accomplish the comparison you're looking for (equivalence on two objects and their immediate fields).