i have two objects, a master and a temp
the master looks like
{
"gnome":{
"child":{
name:"child",
race:"gnome"
},
"youngling":{
name:"youngling",
race:"gnome"
}
},
"human":{...},
...
}
and the temp looks like
{
"gnome":{
"man":{
name:"man",
race:"gnome"
}
}
what i am trying to do is have the temp be added to the master like
{
"gnome":{
"child":{...},
"youngling":{...},
"man":{...}
},
"human":{...},
...
}
what i currently have
let obj = {}
function generateJson() {
let race = getinput("race").value
let name = getinput("name").value
let temp = {}
temp[`${race}`] = {}
temp[`${race}`][`${name}`] = {
name: name,
race: race
}
obj = Object.assign(obj, temp)
}
all it does is empties and override the first duplicate key with the temp value
a.e. {gnome:{man:{..}}}
earlier this question was closed because it should have been awnsered with How can I merge properties of two JavaScript objects dynamically?
which sadly it didn't, all of the solutions override objects within objects, i want to add to similar keys
Based on your example, this should work
<script>
function merge_without_override(master, temp) {
for(var key in temp) {
if( !temp.hasOwnProperty(key) )
continue;
if(master[key] !== undefined) // key already exists in master.
continue;
master[key] = Object.assign(temp[key]); // key doesnt exist, assign it.
}
}
var master = {
"gnome":{
"child":{
name:"child",
race:"gnome"
},
"youngling":{
name:"youngling",
race:"gnome"
}
},
"human":{}
};
var temp = {
"gnome":{
"man":{
name:"man",
race:"gnome"
}
}
};
console.log("master before merge");
console.log(master);
merge_without_override(master["gnome"], temp["gnome"]);
console.log("master after merge");
console.log(master);
</script>
Output (in jsfiddle):
{
gnome: {
child: { ... },
man: { ... },
youngling: { ... }
},
human: { ... }
}
JsFiddle: https://jsfiddle.net/h10fpcx2/
Chris Ferdinandi wrote a helper for this here;
I've added an updated version below, but wanted to add a fix for my biggest frustration with Object.assign.
It mutates the source material. To fix this, you can add an empty object as the first argument of the deepAssign function, or use function copyDeepAssign below.
// mutates the source material
function deepAssign(...args) {
// Make sure there are objects to merge
const len = args.length;
if (len < 1) return;
const main = args[0];
if (len < 2) return main
// Merge all objects into first
let i = 0,
curr;
while (i < len) {
curr = args[i];
for (var key in curr) {
// If it's an object, recursively merge
// Otherwise, push to key
if (Object.prototype.toString.call(curr[key]) === '[object Object]') {
main[key] = deepAssign(main[key] || {}, curr[key]);
} else {
main[key] = curr[key];
}
}
i++;
}
return main;
}
// Doesn't mutate the source material
function copyDeepAssign(...args) {
const base = {};
return deepAssign(base, ...args);
}
Related
I was wondering if it is possible to dynamically generate an object with an array of strings in dot notation. I would like to dynamically build a JSON object from a CSV file. The goal is to build the CSV as JSON, then filter the properties and make a new JSON object.
So I would like to pass in something like this..
var obj = {};
var keyArray = ['meta', 'logos', 'warranty', 'specs', 'specs.engine', 'specs.engine.hp', 'specs.engine.rpm', 'specs.engine.manufacturer'];
The end result would be something like this...
obj = {
meta: {
},
logos: {
},
specs: {
engine: {
hp: {
}
}
}
}
Here is the main function
function addObjectsByKey(obj, keyArray) {
for (var key in keyArray) {
// If keyword is not in object notation
if (!(keyArray[key].match(/\./))) {
// If the object property is not set, set it
if (!(obj[keyArray[key]])) {
obj[keyArray[key]] = {};
}
} else {
// Split array element (in dot notation) into an array of strings
// These strings will be object properties
var pathAsArray = keyArray[key].split('.');
var path = null;
for (var k in pathAsArray) {
if (path == null) {
obj[pathAsArray[k]] = {};
path = pathAsArray[k];
} else {
obj[path][pathAsArray[k]] = {};
path += '.' + pathAsArray[k];
}
}
// throw Error('end');
}
}
// return obj;
}
You can use forEach loop and inside you can split each element on . and then use reduce method to build nested object.
var keyArray = ['meta', 'logos', 'warranty', 'specs', 'specs.engine', 'specs.engine.hp', 'specs.engine.rpm', 'specs.engine.manufacturer'];
const result = {}
keyArray.forEach(key => {
// Loop array of keys
// Split each key with . and use reduce on that
// In each iteration of reduce return r[e] which is going to be value if property exists
// or new object if it doesn't
// This way you can go to any object depth as long as keys match existing keys in object.
key.split('.').reduce((r, e) => r[e] = (r[e] || {}), result)
})
console.log(result)
Here is another approach using for loops that will return the same result.
var keyArray = ['meta', 'logos', 'warranty', 'specs', 'specs.engine', 'specs.engine.hp', 'specs.engine.rpm', 'specs.engine.manufacturer' ];
const result = {}
for(var i = 0; i < keyArray.length; i++) {
const keys = keyArray[i].split('.');
let ref = result;
for(var j = 0; j < keys.length; j++) {
const key = keys[j];
if(!ref[key]) ref[key] = {}
ref = ref[key]
}
}
console.log(result)
You can use the function reduce along with a nested forEach to build the path.
The first reduce will accumulate the nested operation.
The nested forEach will build the object and its children according to the current path separated by dots.
let keyArray = ['meta', 'logos', 'warranty', 'specs', 'specs.engine', 'specs.engine.hp', 'specs.engine.rpm', 'specs.engine.manufacturer'],
newObj = keyArray.reduce((accum, path) => {
let previous = accum;
path.split('.').forEach(key => {
if (previous[key]) previous = previous[key];
else previous = previous[key] = {};
});
return accum;
}, {});
console.log(newObj);
.as-console-wrapper { max-height: 100% !important; top: 0; }
This question already has answers here:
Convert string in dot notation to get the object reference [duplicate]
(6 answers)
Closed 6 years ago.
If I have a base object which is always the same: project but sometimes I have to access its fields dynamically, how can I access its fields when it can be 1 or more nested objects for example:
function (myPath){
return project[myPath];
}
This works when using project["oneField"] in myPath("oneField")
but it does not work when it is nested to or more levels:
myPath("one.two.fieldName") does not work: project["one.two.fieldName"]
Neither like this: project."one.two.fieldName"
You could do it like this (ES6):
function getVal(object, path){
return path.split('.').reduce ( (res, prop) => res[prop], object );
}
// sample data
var object = { one: { two: { fieldName: 'gotcha' } } };
// get inner field value
console.log( getVal(object, 'one.two.fieldName') );
You can further extend this function to also support square brackets:
function getVal(object, path){
const regex = /(?<=\[)(?!")[^\]]+|(?<=\[")[^"]+|[^."[\]]+/g;
return path.match(regex).reduce ( (res, prop) => res[prop], object );
}
let object = { 'a': [{ 'b': { 'c': 3 } }] };
console.log( getVal(object, 'a[0].b["c"]') );
option #1 (not recommended) - use the eval function:
var projects = {
a : {
b : {
c : 1
}
}
}
function get(myPath) {
debugger;
return eval("projects." + myPath)
}
console.log(get('a.b.c'))
option #2 - split by . and go over the elements in the object:
var projects = {
a: {
b : {
c : '1'
}
}
}
function get(path) {
if (path.indexOf('.')) {
subs = path.split(".")
ret = projects;
for (var i = 0; i < subs.length; i++) {
ret = ret[subs[i]]
}
return ret;
} else {
return projects[path];
}
}
console.log(get('a'))
console.log(get('a.b'))
console.log(get('a.b.c'))
Usually if I'm doing something like this, I'll use a recursive function:
var data = {a: {b: {c: 5}}};
function getValue(obj, key) {
var parts = key.split('.');
return obj
&& (parts.length === 1
&& obj[key] || getValue(obj[parts[0]], parts.slice(1).join('.')))
|| null;
}
console.log(getValue(data, "a.b.c"));
JSFiddle
It's a bit concisely written, but basically if it has a key with a dot in it, it'll call it down a level. If it ever gets an obj that doesn't exist, it'll return null. Otherwise, once it's down to the last level, it'll return the value it found.
A more expanded, maybe easier to understand, version is:
function getValue(obj, key) {
var parts = key.split('.');
if (!obj) {
return null;
} else if (parts.length === 1) {
return obj[key];
}
return getValue(obj[parts.slice(1).join('.')], key);
}
I'm new to JavaScript and I'm really lost here. Here is some data produced by PHP json_encode() (and limited to most pertinent keys) :
[
{
"product_option_id":"229",
"product_option_value":
[
{
"product_option_value_id":"21",
"option_value_id":"51",
"price":"1,22 €",
"price_prefix":"+"
},
{
"product_option_value_id":"22",
"option_value_id":"52",
"price":false,
"price_prefix":"+"
},
{
"product_option_value_id":"23",
"option_value_id":"53",
"price":"2,42 €",
"price_prefix":"+"
}
],
"option_id":"14",
"type":"radio",
"value":""
},
{
"product_option_id":"228",
"product_option_value":
[
{
"product_option_value_id":"19",
"option_value_id":"49",
"price":"1,22 €",
"price_prefix":"+"
},
{
"product_option_value_id":"20",
"option_value_id":"50",
"price":"2,42 €",
"price_prefix":"+"
}
],
"option_id":"13",
"type":"select",
"value":""
}
]
I need to access price and price_prefix values (in JavaScript) knowing product_option_id and product_option_value_id.
How do I do that ? Should I go for a loop ?
Update :
Thanks for replies. Unless I missed something, it appears that in my case arrays (as ugly as they may be…) are much more efficient than all the proposed solutions (I'll try another approach, formatting a JSON object corresponding to my needs with PHP rather than using the "default" one, but it's off topic here). Though I'm not fond of adding libraries and it's a bit slower than most other solutions, I'll accept Matt's solution because it really seems to make life easier as far as JSON access is concerned. But it should be noted that Yeldard and Barmar's (almost cloned) solutions are faster than other propositions.
lodash would make this easier and neater. It provides _.find or _.filter depending on if your id's are unique or not.
var record = _.find( data_structure, {
"product_option_id": "229"
})
if ( !record ) throw new Error("Record not found");
var value = _.find( record.product_option_value, {
"product_option_value_id":"22"
})
if ( !value ) throw new Error("Value not found");
console.log( "price[%s] prefix[%s]", value.price, value.price_prefix )
Demo
For more complex data selection, you might want to look at sift.js. It's based on mongodb's query system.
var records = sift({
"product_option_id": "229",
"product_option_value": {
$elemMatch: {
"product_option_value_id": "22"
}
}
},
data_structure
)
you can do like this
for(var i in jsonData) {
var item = jsonData[i];
if(item.product_option_id == 229) {
for(var j in item.product_option_value){
var item1 = item.product_option_value[j];
if(item1.product_option_value_id == 21) {
//your item here
break;
}
}
break;
}
}
This should do it:
var productOptionId = 229;
var productOptionValue = 22;
var matchingOuter = yourData.filter(function(i){
return i.product_option_id === productOptionId;
})[0];
if (matchingOuter) {
var matchingInner = matchingOuter.product_option_value.filter(function(i){
return i.product_option_value === productOptionValue;
})[0];
}
If a matching item exists it will be assigned to matchingInner
Following would do:
function getProductValues(products, product_option_id, product_option_value_id) {
if (!product_option_id || !product_option_value_id) {
return;
}
return products.filter(function(product) {
return +product.product_option_id === product_option_id;
}).map(function (product) {
var option_values = product.product_option_value;
return option_values.filter(function (option) {
return +option.option_value_id === product_option_value_id;
})[0] || [];
})[0] || [];
}
Usage:
getProductValues(data, 229, 51)
Result:
{product_option_value_id: "21", option_value_id: "51", price: "1,22 €", price_prefix: "+"}
Use filter on the main array to grab the right object, filter again on the option_value_id, then map on the returned array to get a single price/prefix object. map and filter both return arrays which is why you see the code picking up the first element ([0]) in a couple of places.
function getData(data, options) {
return data.filter(function (product) {
return product.product_option_id === options.id;
})[0].product_option_value.filter(function (details) {
return details.product_option_value_id === options.optionId;
}).map(function(el) {
return { price: el.price, prefix: el.price_prefix }
})[0];
}
getData(data, { id: '229', optionId: '23' }); // { price: "2,42 €", prefix: "+" }
DEMO
Use nested loops to search through the main array and the sub-arrays, looking for the matching element.
function find_product(product_option_id, product_option_value_id) {
for (var i = 0; i < products.length; i++) {
var product = products[i];
if (product.product_option_id == product_option_id) {
for (var j = 0; j < product.product_option_value.length; j++) {
var value = product.product_option_value[j];
if (value.product_option_value_id == product_option_value_id) {
return { price: value.price, price_prefix: value.price_prefix }
}
}
}
}
}
Yes, you need to enumerate through the array and find your items:
Here is the working code which outputs price_prefix and price of product with product_option_id = 228 and product_option_value_id = 19. You can replace these values with your own.
for (var i = 0; i < obj.length; i++) // Enumerate through array
{
var item = obj[i];
if (item.product_option_id === "228") // Filtering items by product_option_id
{
// When necessary product_option_id found
for (var j = 0; j < item.product_option_value.length; j++) // Enumerate through its products
{
var productItem = item.product_option_value[j];
if (productItem.product_option_value_id === "19") // Filtering by product_option_value_id
{
// here it is. productItem is found! do whatever you want with it
alert(productItem.price_prefix + " " + productItem.price);
}
}
}
}
Working JSFiddle demo.
I use the following function to dynamically check the properties of a variable number of objects.
// FUNCTION: Check Objects
var ObjectsN = 4;
function CheckObjects()
{
for (var i=0; i<=ObjectsN; i++)
{
if ((eval("Object"+i+".property")==0)
{
if (i==ObjectsN)
{
alert("Function finished");
}
}
else
{
return; // end function
}
}
}
I need to check if each object has the same property value.
Is there a way to do the same without using eval ?
A real example would be really much appreciated.
The structure you're looking for is an Array. So use an array to store your objects:
/* assuming `var Object1 = new Object(), Object2 = new Object();` and so on */
var objs = [ Object1, Object2, Object3 ];
Then testing for a property should implement hasOwnProperty while iterating:
for (var o = 0; i < objs.length; o++){
if (objs[o].hasOwnProperty('property')){
// Property exists, test its value:
}
}
You could then test against objs[o].property to retrieve its value and see if it matches what you're expecting.
And for those browsers that may not have the function available, here is a cross-browser version of hasOwnProperty (source, but originally from here):
/*
Cross-browser hasOwnProperty solution, based on answers from:
https://stackoverflow.com/questions/135448/how-do-i-check-to-see-if-an-object-has-an-attribute-in-javascript
*/
if ( !Object.prototype.hasOwnProperty ) {
Object.prototype.hasOwnProperty = function(prop) {
var proto = obj.__proto__ || obj.constructor.prototype;
return (prop in this) && (!(prop in proto) || proto[prop] !== this[prop]);
};
}
assuming Object0, Object1, Object2,Object3 & Object4 are global variables.
you could access the variables by using window
var ObjectsN = 4;
function CheckObjects()
{
for (var i=0; i<=ObjectsN; i++)
{
if (window['Object'+i].property == 0)
{
if (i==ObjectsN)
{
alert("Function finished");
}
}
else
{
return; // end function
}
}
}
Whenever you are thinking about numbering variables. Use an array instead.
Don't have Object1, Object2, etc. Have [ x, y, x ]
Then you can do:
for (var i = 0; i < objects.length; i++) {
var obj = objects[i];
if ("property" in obj) {
// Whatever
}
}
Similar to Neverever's answer, but passing a reference to the global object instead of assuming there's a window object available.
var ObjectsN = 4;
var CheckObjects = (function(global) {
for (var i=0; i<=ObjectsN; i++) {
if ( global['Object' + i].property == 0) {
if (i == ObjectsN) {
alert("Function finished");
}
} else {
return; // end function
}
}
}
Are you sure that == 0 is the test you want? That will return true even if the property doesn't exist. The function isn't particularly robust, if an object from 0 to n doesn't exist, an error will be thrown.
The function will continue only as long as Objectn.property == 0 returns true, so it will exit as soon as it gets a truethy result (e.g. Object0.property = 'foo').
I want to create a hash table with Object keys that are not converted into String.
Some thing like this:
var object1 = new Object();
var object2 = new Object();
var myHash = new HashTable();
myHash.put(object1, "value1");
myHash.put(object2, "value2");
alert(myHash.get(object1), myHash.get(object2)); // I wish that it will print value1 value2
EDIT: See my answer for full solution
Here is a simple Map implementation that will work with any type of key, including object references, and it will not mutate the key in any way:
function Map() {
var keys = [], values = [];
return {
put: function (key, value) {
var index = keys.indexOf(key);
if(index == -1) {
keys.push(key);
values.push(value);
}
else {
values[index] = value;
}
},
get: function (key) {
return values[keys.indexOf(key)];
}
};
}
While this yields the same functionality as a hash table, it's not actually implemented using a hash function since it iterates over arrays and has a worst case performance of O(n). However, for the vast majority of sensible use cases this shouldn't be a problem at all. The indexOf function is implemented by the JavaScript engine and is highly optimized.
Here is a proposal:
function HashTable() {
this.hashes = {};
}
HashTable.prototype = {
constructor: HashTable,
put: function( key, value ) {
this.hashes[ JSON.stringify( key ) ] = value;
},
get: function( key ) {
return this.hashes[ JSON.stringify( key ) ];
}
};
The API is exactly as shown in your question.
You can't play with the reference in js however (so two empty objects will look like the same to the hashtable), because you have no way to get it. See this answer for more details: How to get javascript object references or reference count?
Jsfiddle demo: http://jsfiddle.net/HKz3e/
However, for the unique side of things, you could play with the original objects, like in this way:
function HashTable() {
this.hashes = {},
this.id = 0;
}
HashTable.prototype = {
constructor: HashTable,
put: function( obj, value ) {
obj.id = this.id;
this.hashes[ this.id ] = value;
this.id++;
},
get: function( obj ) {
return this.hashes[ obj.id ];
}
};
Jsfiddle demo: http://jsfiddle.net/HKz3e/2/
This means that your objects need to have a property named id that you won't use elsewhere. If you want to have this property as non-enumerable, I suggest you take a look at defineProperty (it's not cross-browser however, even with ES5-Shim, it doesn't work in IE7).
It also means you are limited on the number of items you can store in this hashtable. Limited to 253, that is.
And now, the "it's not going to work anywhere" solution: use ES6 WeakMaps. They are done exactly for this purpose: having objects as keys. I suggest you read MDN for more information: https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/WeakMap
It slightly differs from your API though (it's set and not put):
var myMap = new WeakMap(),
object1 = {},
object2 = {};
myMap.set( object1, 'value1' );
myMap.set( object2, 'value2' );
console.log( myMap.get( object1 ) ); // "value1"
console.log( myMap.get( object2 ) ); // "value2"
Jsfiddle demo with a weakmap shim: http://jsfiddle.net/Ralt/HKz3e/9/
However, weakmaps are implemented in FF and Chrome (only if you enable the "Experimental javascript features" flag in chrome however). There are shims available, like this one: https://gist.github.com/1269991. Use at your own risk.
You can also use Maps, they may more suit your needs, since you also need to store primitive values (strings) as keys. Doc, Shim.
I took #Florian Margaine's suggestion to higher level and came up with this:
function HashTable(){
var hash = new Object();
this.put = function(key, value){
if(typeof key === "string"){
hash[key] = value;
}
else{
if(key._hashtableUniqueId == undefined){
key._hashtableUniqueId = UniqueId.prototype.generateId();
}
hash[key._hashtableUniqueId] = value;
}
};
this.get = function(key){
if(typeof key === "string"){
return hash[key];
}
if(key._hashtableUniqueId == undefined){
return undefined;
}
return hash[key._hashtableUniqueId];
};
}
function UniqueId(){
}
UniqueId.prototype._id = 0;
UniqueId.prototype.generateId = function(){
return (++UniqueId.prototype._id).toString();
};
Usage
var map = new HashTable();
var object1 = new Object();
map.put(object1, "Cocakola");
alert(map.get(object1)); // Cocakola
//Overriding
map.put(object1, "Cocakola 2");
alert(map.get(object1)); // Cocakola 2
// String key is used as String
map.put("myKey", "MyValue");
alert(map.get("myKey")); // MyValue
alert(map.get("my".concat("Key"))); // MyValue
// Invalid keys
alert(map.get("unknownKey")); // undefined
alert(map.get(new Object())); // undefined
Here is a proposal, combining #Florian's solution with #Laurent's.
function HashTable() {
this.hashes = [];
}
HashTable.prototype = {
constructor: HashTable,
put: function( key, value ) {
this.hashes.push({
key: key,
value: value
});
},
get: function( key ) {
for( var i = 0; i < this.hashes.length; i++ ){
if(this.hashes[i].key == key){
return this.hashes[i].value;
}
}
}
};
It wont change your object in any way and it doesn't rely on JSON.stringify.
I know that I am a year late, but for all others who stumble upon this thread, I've written the ordered object stringify to JSON, that solves the above noted dilemma: http://stamat.wordpress.com/javascript-object-ordered-property-stringify/
Also I was playing with custom hash table implementations which is also related to the topic: http://stamat.wordpress.com/javascript-quickly-find-very-large-objects-in-a-large-array/
//SORT WITH STRINGIFICATION
var orderedStringify = function(o, fn) {
var props = [];
var res = '{';
for(var i in o) {
props.push(i);
}
props = props.sort(fn);
for(var i = 0; i < props.length; i++) {
var val = o[props[i]];
var type = types[whatis(val)];
if(type === 3) {
val = orderedStringify(val, fn);
} else if(type === 2) {
val = arrayStringify(val, fn);
} else if(type === 1) {
val = '"'+val+'"';
}
if(type !== 4)
res += '"'+props[i]+'":'+ val+',';
}
return res.substring(res, res.lastIndexOf(','))+'}';
};
//orderedStringify for array containing objects
var arrayStringify = function(a, fn) {
var res = '[';
for(var i = 0; i < a.length; i++) {
var val = a[i];
var type = types[whatis(val)];
if(type === 3) {
val = orderedStringify(val, fn);
} else if(type === 2) {
val = arrayStringify(val);
} else if(type === 1) {
val = '"'+val+'"';
}
if(type !== 4)
res += ''+ val+',';
}
return res.substring(res, res.lastIndexOf(','))+']';
}
Based on Peters answer, but with proper class design (not abusing closures), so the values are debuggable. Renamed from Map to ObjectMap, because Map is a builtin function. Also added the exists method:
ObjectMap = function() {
this.keys = [];
this.values = [];
}
ObjectMap.prototype.set = function(key, value) {
var index = this.keys.indexOf(key);
if (index == -1) {
this.keys.push(key);
this.values.push(value);
} else {
this.values[index] = value;
}
}
ObjectMap.prototype.get = function(key) {
return this.values[ this.keys.indexOf(key) ];
}
ObjectMap.prototype.exists = function(key) {
return this.keys.indexOf(key) != -1;
}
/*
TestObject = function() {}
testA = new TestObject()
testB = new TestObject()
om = new ObjectMap()
om.set(testA, true)
om.get(testB)
om.exists(testB)
om.exists(testA)
om.exists(testB)
*/
When you say you don't want your Object keys converted into Strings, I'm going to assume it's because you just don't want the entire code contents of your Objects being used as keys. This, of course, makes perfect sense.
While there is no "hash table" in Javascript per-se, you can accomplish what you're looking for by simply overriding your Object's prototype.toString and returning a valid key value that will be unique to each instance. One way to do this is with Symbol():
function Obj () {
this.symbol = Symbol() // Guaranteed to be unique to each instance
}
Obj.prototype.toString = function () {
return this.symbol // Return the unique Symbol, instead of Obj's stringified code
}
let a = new Obj()
let b = new Obj()
let table = {}
table[a] = 'A'
table[b] = 'B'
console.log(table) // {Symbol(): 'A', Symbol(): 'B'}
console.log(table[a]) // A
console.log(table[b]) // B
Using JSON.stringify() is completely awkward to me, and gives the client no real control over how their keys are uniquely identified. The objects that are used as keys should have a hashing function, but my guess is that in most cases overriding the toString() method, so that they will return unique strings, will work fine:
var myMap = {};
var myKey = { toString: function(){ return '12345' }};
var myValue = 6;
// same as myMap['12345']
myMap[myKey] = myValue;
Obviously, toString() should do something meaningful with the object's properties to create a unique string. If you want to enforce that your keys are valid, you can create a wrapper and in the get() and put() methods, add a check like:
if(!key.hasOwnProperty('toString')){
throw(new Error('keys must override toString()'));
}
But if you are going to go thru that much work, you may as well use something other than toString(); something that makes your intent more clear.
So a very simple proposal would be:
function HashTable() {
this.hashes = {};
}
HashTable.prototype = {
constructor: HashTable,
put: function( key, value ) {
// check that the key is meaningful,
// also will cause an error if primitive type
if( !key.hasOwnProperty( 'hashString' ) ){
throw( new Error( 'keys must implement hashString()' ) );
}
// use .hashString() because it makes the intent of the code clear
this.hashes[ key.hashString() ] = value;
},
get: function( key ) {
// check that the key is meaningful,
// also will cause an error if primitive type
if( !key.hasOwnProperty( 'hashString' ) ){
throw( new Error( 'keys must implement hashString()' ) );
}
// use .hashString() because it make the intent of the code clear
return this.hashes[ key.hashString() ];
}
};
Inspired by #florian, here's a way where the id doesn't need JSON.stringify:
'use strict';
module.exports = HashTable;
function HashTable () {
this.index = [];
this.table = [];
}
HashTable.prototype = {
constructor: HashTable,
set: function (id, key, value) {
var index = this.index.indexOf(id);
if (index === -1) {
index = this.index.length;
this.index.push(id);
this.table[index] = {};
}
this.table[index][key] = value;
},
get: function (id, key) {
var index = this.index.indexOf(id);
if (index === -1) {
return undefined;
}
return this.table[index][key];
}
};
I took #Ilya_Gazman solution and improved it by setting '_hashtableUniqueId' as a not enumerable property (it won't appear in JSON requests neither will be listed in for loops). Also removed UniqueId object, since it is enough using only HastTable function closure. For usage details please see Ilya_Gazman post
function HashTable() {
var hash = new Object();
return {
put: function (key, value) {
if(!HashTable.uid){
HashTable.uid = 0;
}
if (typeof key === "string") {
hash[key] = value;
} else {
if (key._hashtableUniqueId === undefined) {
Object.defineProperty(key, '_hashtableUniqueId', {
enumerable: false,
value: HashTable.uid++
});
}
hash[key._hashtableUniqueId] = value;
}
},
get: function (key) {
if (typeof key === "string") {
return hash[key];
}
if (key._hashtableUniqueId === undefined) {
return undefined;
}
return hash[key._hashtableUniqueId];
}
};
}
The best solution is to use WeakMap when you can (i.e. when you target browsers supporting it)
Otherwise you can use the following workaround (Typescript written and collision safe):
// Run this in the beginning of your app (or put it into a file you just import)
(enableObjectID)();
const uniqueId: symbol = Symbol('The unique id of an object');
function enableObjectID(): void {
if (typeof Object['id'] !== 'undefined') {
return;
}
let id: number = 0;
Object['id'] = (object: any) => {
const hasUniqueId: boolean = !!object[uniqueId];
if (!hasUniqueId) {
object[uniqueId] = ++id;
}
return object[uniqueId];
};
}
Then you can simply get a unique number for any object in your code (like if would have been for pointer address)
let objectA = {};
let objectB = {};
let dico = {};
dico[(<any>Object).id(objectA)] = "value1";
// or
dico[Object['id'](objectA);] = "value1";
// If you are not using typescript you don't need the casting
dico[Object.id(objectA)] = "value1"
I know I'm late, but here's a simple HashMap implementation:
Function.prototype.toJSON = Function.prototype.toString;
//taken from https://stackoverflow.com/questions/1249531/how-to-get-a-javascript-objects-class
function getNativeClass(obj) {
if (typeof obj === "undefined") return "undefined";
if (obj === null) return "null";
return Object.prototype.toString.call(obj).match(/^\[object\s(.*)\]$/)[1];
}
function globals() {
if (typeof global === "object") //node
return global;
return this;
}
function lookup(x) {
return globals()[x];
}
function getAnyClass(obj) {
if (typeof obj === "undefined") return "undefined";
if (obj === null) return "null";
return obj.constructor.name;
}
//taken from https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Errors/Cyclic_object_value#examples
var getCircularReplacer = () => {
const seen = new WeakSet();
return (key, value) => {
if (typeof value === "object" && value !== null) {
if (seen.has(value)) {
return "[Circular]";
}
seen.add(value);
}
return value;
};
};
function encode(x) {
if (typeof x === "object" && x !== null) {
var y = myClone(x);
x = Object.getPrototypeOf(x);
for (var i = 0; i < Object.getOwnPropertyNames(y).length; i++) { //Make enumerable
x[Object.getOwnPropertyNames(y)[i]] = y[Object.getOwnPropertyNames(y)[i]];
}
}
return getAnyClass(x) + " " + JSON.stringify(x, getCircularReplacer());
}
function decode(x) {
var a = x.split(" ").slice(1).join(" "); //OBJECT
if (typeof lookup(x.split(" ")[0])) {
return new (lookup(x.split(" ")[0]))(JSON.parse(a))
} else {
return JSON.parse(a);
}
}
//taken from https://github.com/feross/fromentries/blob/master/index.js
/*! fromentries. MIT License. Feross Aboukhadijeh <https://feross.org/opensource> */
function fromEntries(iterable) {
return [...iterable].reduce((obj, [key, val]) => {
obj[key] = val
return obj
}, {})
}
var toEnumerable = (obj) => {
return fromEntries(
Object.getOwnPropertyNames(obj).map(prop => [prop, obj[prop]])
);
};
//taken from https://stackoverflow.com/questions/41474986/how-to-clone-a-javascript-es6-class-instance
function myClone(instanceOfBlah) {
if (typeof instanceOfBlah !== "object" || !instanceOfBlah) { return instanceOfBlah; }
const clone = Object.assign({}, toEnumerable(instanceOfBlah));
const Blah = instanceOfBlah.constructor;
Object.setPrototypeOf(clone, Blah.prototype);
return clone;
}
function HashMap(a) {
if (typeof a === "undefined") {
a = [];
}
a = Array.from(a);
a = a.map((e) => [encode(e[0]), e[1]]);
this.a = a;
}
HashMap.from = function (a) {
var temp = myClone(a);
//convert to array
a = [];
for (var i = 0; i < Object.getOwnPropertyNames(temp).length; i++) {
a.push([Object.getOwnPropertyNames(temp)[i], temp[Object.getOwnPropertyNames(temp)[i]]]);
}
return new HashMap(a);
}
HashMap.prototype.put = function (x, y) {
this.a.push([encode(x), y]);
}
HashMap.prototype.get = function (x) {
var t1 = this.a.map((e) => e[0]);
return this.a[t1.indexOf(encode(x))][1];
}
HashMap.prototype.length = function () {
return this.a.length;
}
HashMap.prototype.toString = function () {
var result = [];
for (var i = 0; i < this.length(); i++) {
result.push(JSON.stringify(decode(this.a[i][0]), getCircularReplacer()) + " => " + this.a[i][1]);
}
return "HashMap {" + result + "}";
}
var foo = new HashMap();
foo.put("SQRT3", Math.sqrt(3));
foo.put({}, "bar");
console.log(foo.get({}));
console.log(foo.toString());
Note that it is ordered.
Methods:
put: Adds an item
get: Access an item
from (static): Convert from a JavaScript object
toString: Convert to string
Minified and without the test:
function getNativeClass(t){return void 0===t?"undefined":null===t?"null":Object.prototype.toString.call(t).match(/^\[object\s(.*)\]$/)[1]}function globals(){return"object"==typeof global?global:this}function lookup(t){return globals()[t]}function getAnyClass(t){return void 0===t?"undefined":null===t?"null":t.constructor.name}Function.prototype.toJSON=Function.prototype.toString;var getCircularReplacer=()=>{const t=new WeakSet;return(e,r)=>{if("object"==typeof r&&null!==r){if(t.has(r))return"[Circular]";t.add(r)}return r}};function encode(t){if("object"==typeof t&&null!==t){var e=myClone(t);t=Object.getPrototypeOf(t);for(var r=0;r<Object.getOwnPropertyNames(e).length;r++)t[Object.getOwnPropertyNames(e)[r]]=e[Object.getOwnPropertyNames(e)[r]]}return getAnyClass(t)+" "+JSON.stringify(t,getCircularReplacer())}function decode(t){var e=t.split(" ").slice(1).join(" ");return lookup(t.split(" ")[0]),new(lookup(t.split(" ")[0]))(JSON.parse(e))}function fromEntries(t){return[...t].reduce((t,[e,r])=>(t[e]=r,t),{})}var toEnumerable=t=>fromEntries(Object.getOwnPropertyNames(t).map(e=>[e,t[e]]));function myClone(t){if("object"!=typeof t||!t)return t;const e=Object.assign({},toEnumerable(t)),r=t.constructor;return Object.setPrototypeOf(e,r.prototype),e}function HashMap(t){void 0===t&&(t=[]),t=(t=Array.from(t)).map(t=>[encode(t[0]),t[1]]),this.a=t}HashMap.from=function(t){var e=myClone(t);t=[];for(var r=0;r<Object.getOwnPropertyNames(e).length;r++)t.push([Object.getOwnPropertyNames(e)[r],e[Object.getOwnPropertyNames(e)[r]]]);return new HashMap(t)},HashMap.prototype.put=function(t,e){this.a.push([encode(t),e])},HashMap.prototype.get=function(t){var e=this.a.map(t=>t[0]);return this.a[e.indexOf(encode(t))][1]},HashMap.prototype.length=function(){return this.a.length},HashMap.prototype.toString=function(){for(var t=[],e=0;e<this.length();e++)t.push(JSON.stringify(decode(this.a[e][0]),getCircularReplacer())+" => "+this.a[e][1]);return"HashMap {"+t+"}"};
Also, you can customize the encoder and decoder by changing encode and decode functions.
As in florian's answer, you can't play with the reference in js however (so two empty objects will look like the same to the hashtable).
class Dict{
constructor(){
this.keys = [];
this.values = [];
this.set = this.set.bind(this);
}
set(key, value){
this.keys.push(key);
this.values.push(value);
}
get(key){
return this.values[this.keys.indexOf(key)];
}
all(){
return this.keys.map((kk, ii)=>[kk, this.values[ii]]);
}
}
let d1 = new Dict();
let k1 = {1: 'a'};
d1.set(k1, 2);
console.log(d1.get(k1)); // 2
let k2 = {2: 'b'};
d1.set(k2, 3);
console.log(d1.all());
// [ [ { '1': 'a' }, 2 ], [ { '2': 'b' }, 3 ] ]
Just use the strict equality operator when looking up the object: ===
var objects = [];
objects.push(object1);
objects.push(object2);
objects[0] === object1; // true
objects[1] === object1; // false
The implementation will depend on how you store the objects in the HashTable class.