Make json from array of objects javascript - javascript

I have a javascript object with a lot of attributes and methods, I want it to be sent to a php file. For this, I want to transform it to Json data.
But I just can`t understand how should I use json.stringify to do this, because of the complex object's class.
The objects looks like this. I have an array of objects that I have to sent over ajax.
Also, this class has array of other objects as attributes, and a bunch of other methods.
var PhotoFile = function(clientFileHandle){
PhotoFile.count = PhotoFile.count + 1;
this.specificClass = "no-" + PhotoFile.count;
this.checkbox = null;
this.attributes = [];
this.file = clientFileHandle;
this.fileExtension = null;
//meta data
this.meta = null;
this.orientation = null;
this.oDateTime = null;
this.maxWidth = 150;
this.maxHeight = 100;
//raw data
this.imgData = null;
this.imgDataWidth = null;
this.imgDataHeight = null;
this.checkSum1 = null;
this.checkSum2 = null;
//DOM stuff
this.domElement = null;
this.imgElement = null;
this.loadProgressBar = null;
this.uploadProgressBar = null;
this.imageContainer = null;
this.attributeContainer = null;
this.indexInGlobalArray = -1;
//flags
this.metaLoaded = false;
this.startedLoading = false;
this.finishedLoading = false;
this.needsUploading = true;
this.imageDisplayed = false;
//listeners
this.onFinishedLoading = function () {};
this.onFinishedUploading = function () {console.log('Called default end '+this.file.name)};
..... plus other methods.
}

You could create a function on your object that returns a serializable representation of your object.
E.g.
function SomeObject() {
this.serializeThis = 'serializeThis';
this.dontSerializeThis = 'dontSerializeThis';
}
SomeObject.prototype.toSerializable = function () {
//You can use a generic solution like below
return subsetOf(this, ['serializeThis']);
//Or a hard-coded version
// return { serializeThis: this.serializeThis };
};
//The generic property extraction algorithm would need to be more complex
//to deep-filter objects.
function subsetOf(obj, props) {
return (props || []).reduce(function (subset, prop) {
subset[prop] = obj[prop];
return subset;
}, {});
}
var o = new SomeObject();
JSON.stringify(o.toSerializable()); //{"serializeThis":"serializeThis"}
Note that using a generic property extractor algorithm would force you to leak implementation details and therefore, violate encapsulation so although it might be shorter to implement a solution using this method, it might not be the best way in some cases.
However, one thing that can usually be done to limit internals leakage is to implement property getters.

Related

Appending New Values to a Durable Object

I'm trying to write a linked list using the durable constructor method described in Nicholas Zakas' book but I am running into a conceptual problem.
I understand that the durable constructor pattern is used for security reasons and eschews the use of "this" and "new." However, I am unsure if that means I am unable to create methods that allow for appending nodes to the linked list.
All of the instances I've found have basically been taken straight from Douglas Crockford with really no variation. The example he uses only retrieves information from the object.
My questions are: Does this constructor pattern allow appending data to such data structures? If it doesn't and Crockford says we need to avoid "this" and "new" what options do I have?
edit (linked list code so far):
var linkedList = function (spec) {
var that = {};
that.addNode = function (newNode) {
if (spec.size && spec.root) {
// not sure
} else {
// not sure yet
}
};
that.getRoot = function () {
return spec.root; // for an idea of how to retrieve
};
return that;
};
// how I'd like to use the list
var firstNode = node({val: 25, next: null});
var myList = linkedList({root: firstNode, size: 1});
var secondNode = node({val: 33, next: null});
myList.add(secondNode); // I feel this isn't possible
I'm still quite not sure about the durable object, but I think what you want to do might be this:
var LinkedList = function(val) {
// A private object
var node = {
val: val,
next : null
};
// Method
var getVal = function() {
return node.val;
};
var setVal = function(val) {
node.val = val;
};
var getNext = function() {
return node.next;
};
var setNext = function(newNode) {
// You have to ensure its a node here
if (true) {
node.next = newNode;
}
};
// Append to tail
var appendVal = function(val) {
if (node.next === null) {
node.next = LinkedList(val);
} else {
node.next.appendVal(val);
}
};
var appendNode = function(newNode) {
if (node.next === null) {
node.next = newNode
} else {
node.next.appendNode(newNode);
}
};
var print = function() {
var str = '' + node.val;
if (node.next !== null) {
str += ' => ' + node.next.print();
}
return str;
}
// Only expose method to user.
return {
getVal : getVal,
setVal : setVal,
getNext: getNext,
appendVal: appendVal,
appendNode: appendNode,
print: print
};
};
When we want a root, call var root = LinkedList(val); Then the function will create a hidden object, set the value, and return methods that is able to get/set the object the value for you.
You can do things only with the exposed object, and in this way, the this and new is avoided, and you get a private object that can't be accessed directly.
And what you want to do may be write like this:
var first = LinkedList(25);
var myList = LinkedList(null);
myList.appendNode(first);
var second = LinkedList(33);
myList.appendNode(second);
console.log(myList.print()); // null => 25 => 33

Access object property within a callback

I wrote the following code:
var Request = require('./request');
function Treasure(id) {
Treasure.prototype.valid = false;
Treasure.prototype.id = id;
Treasure.prototype.contentLength = 0;
Treasure.prototype.title = null;
Treasure.prototype.seller = null;
Treasure.prototype.buyer = null;
Treasure.prototype.cost = 0;
}
Treasure.prototype.loadData = function() {
EpvpRequest.treasureRequest(Treasure.prototype.id, function(data) {
if (data.valid) {
Treasure.prototype.valid = data.valid;
Treasure.prototype.contentLength = data.contentLength;
Treasure.prototype.title = data.title;
Treasure.prototype.seller = data.seller;
Treasure.prototype.buyer = data.buyer;
Treasure.prototype.cost = data.cost;
}
});
}
module.exports = Treasure;
Please don't hit me, I just started learning javascript.
I want ot access the properties of "Treasure"; but I can't use this, because I have a callback in the loadData function and this would refer to the function which called the callback - is that correct?
But it seems that I can't access the properties the way I tried with Treasure.prototype.property.
What is the correct way to to this?
First of all, you should be assigning instance variables in the constructor instead of assiginng to the prototype. The prototype is for methods and other things that will be shared by all Treasure instances.
function Treasure(id) {
this.valid = false;
this.id = id;
this.contentLength = 0;
this.title = null;
this.seller = null;
this.buyer = null;
this.cost = 0;
}
As for your problem with this inside callbacks, the usual workaround is to store the value of this in a regular variable and then use that variable inside the callback.
Treasure.prototype.loadData = function() {
// Nothing special about "that"
// Its just a regular variable.
var that = this;
EpvpRequest.treasureRequest(that.id, function(data) {
if (data.valid) {
that.valid = data.valid;
that.contentLength = data.contentLength;
that.title = data.title;
that.seller = data.seller;
that.buyer = data.buyer;
that.cost = data.cost;
}
});
}
Since this pattern comes up very often, some people choose to always use the same name for the "this-storage" variable. Some of the more popular names are self and that.

stringify javascript function

I am in the final stages of a game development and i have a bunch of objects like this;
roomBedroom = function () {
this.title = "Bedroom";
this.description = "I'm in a bedroom";
this.noun = "bed";
this.entities = new Array();
}
var bedroom = new roomBedroom();
What I want to do now is place all of my game objects into an array;
var savedGameObjects = {};
savedGameObjects['bedroom'] = bedroom;
var jsonGame = JSON.stringify(savedGameObjects);
The plan is to then save the savedGameObjects array and then recall it when the user loads the game again.
If I replace savedGameObjects['bedroom'] = bedroom; with savedGameObjects['bed'] = 'slappy'; it works but not when I have the object.
I really need to save the objects in their current state. I'd rather not go through each object saving key pieces of information one by one.
This feels like a bit of a hack, but its the best I can come up with right now
Your serialization/deserializtion utility
This is going to attach obj.constructor.name to obj.__prototype before serialization. Upon deserializing, the prototype will be put back in place.
(function(global) {
function serialize(obj) {
obj.__prototype = obj.constructor.name;
return JSON.stringify(obj);
};
function deserialize(json) {
var obj = JSON.parse(json);
obj.__proto__ = global[obj.__prototype].prototype;
return obj;
}
global.serialize = serialize;
global.deserialize = deserialize;
})(window);
A sample "class"
(function(global) {
function Foo() {
this.a = "a";
this.b = "b";
}
Foo.prototype.hello = function() {
console.log("hello");
}
global.Foo = Foo;
})(window);
Let's try it out
var foo = new Foo();
var json = serialize(foo);
console.log(json);
var newFoo = deserialize(json);
console.log('a', newFoo.a); // a
console.log('b', newFoo.b); // b
newFoo.hello(); // hello
Watch out for some gotchas
If you use an expression to define your "class", you will have a nameless constructor
var Foo = function() {};
var foo = new Foo();
foo.constructor.name; // ""
As opposed to a named function
function Foo() {}
var foo = new Foo();
foo.constructor.name; // Foo
In order for serialize and deserialize to work, you will need to use named functions
Another gotcha
The deserialize method expects your "classes" to exist on the in the same namespace (window in this case). You could encapsulate your game object classes in another way, just make sure that you reconfigure the deserialize method so that it can find the prototypes as needed.
Making this better
Instead of attaching serialize to the global window, you could have serialize live on (e.g.) the GameObject.prototype then your individual classes could inherit from GameObject. Serializing an object would then be as simple as
var json = foo.serialize();
// {"a":"a","b":"b","__prototype":"Foo"}
You could then define deserialize as GameObject.deserialize and restoring foo would be
var foo = GameObject.deserialize(json);
An alternative solution
Instead of implementing a custom serializer and deserializer, you could make very clever use of the Factory Method Pattern.
This might be a little verbose, but it does give you individual control over how a game object should be deserialized/restored.
var savedData = // your normal JSON here
var player = Player.create(savedData.player);
var items = [];
for (var i=0, i<savedData.items.length; i++) {
items.push(Item.create(savedData.items[i]));
}
var map = Map.create(savedData.map);
This was a pretty interesting problem and I'm sure you're not the first to encounter it. I'm really curious to see what other people come up with.
If I run the following code in a browser there is no problem getting the JSON string of the bedroom object, not sure what the problem is.
Note that JSON is data and bedroom is an object, bedroom may have behaviour like turnOffLight() that JSON doesn't have.
roomBedroom = function () {
this.title = "Bedroom";
this.description = "I'm in a bedroom";
this.noun = "bed";
this.entities = new Array();
}
var bedroom = new roomBedroom();
var savedGameObjects = {};
savedGameObjects['bedroom'] = bedroom;
//logs {"bedroom":{"title":"Bedroom","description":
// "I'm in abedroom","noun":"bed","entities":[]}}
console.log(JSON.stringify(savedGameObjects));
So if you want to re create object instances from JSON data then you can change your constructor:
roomBedroom = function (args) {
//following fails fast and loud, you could silently
//fail by setting args to {}
if(typeof args!=="object")
throw new Error("Have to create roomBedroom by passing an object");
//or do args={} to silently fail
this.title = args.title||"Bedroom";
this.description = args.description||"I'm in a bedroom";
this.noun = args.noun||"bed";
//if entities are objects with behavior
// you have to re create them here passing the JSON data
// as I've done with roomBedroom
this.entities = args.entities||new Array();
}
var jsonString='{"bedroom":{"title":"Bedroom",'+
'"description":"I\'m in a bedroom",'+
'"noun":"bed","entities":[]}}';
var bedroom = new roomBedroom({});
bedroom.entities.push({hi:"there"});
bedroom.title="Master Bedroom";
//serialize bedroom to a json string
var jsonString = JSON.stringify(bedroom);
//create a roomBedroom instance named br2 using
// the serialized string
var br2=new roomBedroom(JSON.parse(jsonString));
//compare if they are the same
console.log(JSON.stringify(bedroom)===JSON.stringify(br2));//true
I have an approach that might work for you. You can see it in action on JSFiddle.
The main point is to use the reviver parameter to JSON.parse to reconstruct your object when it's parsed.
I do this with a general-purpose reviver that can be configured for multiple different types, although here the only one used is the RoomBedroom constructor. This implementation assumes that you have simple copy constructors that create new objects using a reference to an existing one. (For other, more sophisticated possibilities, see an answer to another question I gave in February.) To make it easy to have a copy constructor, I have one more function that accepts a very simple constructor function and a set of default values and builds a copy constructor function for you.
var MultiReviver = function(types) {
return function(key, value) {
var type;
for (var i = 0; i < types.length; i++) {
type = types[i];
if (type.test(value)) {
return new type.constructor(value);
}
}
return value;
};
};
var makeCloningConstructor = (function() {
var clone = function(obj) {return JSON.parse(JSON.stringify(obj));};
var F = function() {};
return function(Constructor, defaults) {
var fn = function(obj) {
Constructor.call(this);
var self = this;
var config = obj || {};
Object.keys(defaults).forEach(function(key) {
self[key] = clone(defaults[key]);
});
Object.keys(config).forEach(function(key) {
self[key] = clone(config[key]);
});
};
F.prototype = Constructor.prototype;
fn.prototype = new F();
fn.constructor = Constructor;
return fn;
};
})();
// Note: capitalize constructor functions
var RoomBedroom = makeCloningConstructor(function RoomBedroom() {}, {
title: "Bedroom",
description: "I'm in a bedroom",
noun: "bed",
entities: [] // Note: use `[]` instead of `new Array()`.
});
RoomBedroom.prototype.toggleLight = function() {
this.lightOn = !this.lightOn;
};
RoomBedroom.prototype.checkLights = function() {
return "light is " + (this.lightOn ? "on" : "off");
};
var bedroom = new RoomBedroom();
bedroom.windowCount = 3; // add new property
bedroom.noun = "king-sized bed"; // adjust property
bedroom.toggleLight(); // create new propery, use prototype function
console.log(bedroom.checkLights());
var savedGameObjects = {};
savedGameObjects['bedroom'] = bedroom;
var jsonGame = JSON.stringify(savedGameObjects);
var reviver = new MultiReviver([{
constructor: RoomBedroom,
test: function(obj) {
var toString = Object.prototype.toString, str = "[object String]",
arr = "[object Array]";
return toString.call(obj.title) == str &&
toString.call(obj.description) == str &&
toString.call(obj.noun) == str &&
toString.call(obj.entities) == arr;
}
}]);
var retrievedGameObjects = JSON.parse(jsonGame, reviver);
// data comes back intact
console.log(JSON.stringify(retrievedGameObjects, null, 4));
// constructor is as expected
console.log("Constructor: " + retrievedGameObjects.bedroom.constructor.name);
// prototype functions work
console.log(retrievedGameObjects.bedroom.checkLights());
I don't know if it's precisely what you were looking for, but I think it's at least an interesting approach.
the faster route
It is better — from an optimisation point of view — to do as Adeneo states, which is power each of your Game Objects by an exportable simple object i.e:
roomBedroom = function(){
this.data = {};
this.data.title = 'Bedroom'
/// and so on...
}
These can then be easily stored and re-imported just by JSON.Stringifying and overwriting the data property. For example, you could set-up the system that Maček mentions (+1) which is to give each of your game objects serialize and deserialize functions:
roomBedroom.prototype.serialize = function(){
return JSON.stringify( this.data );
};
roomBedroom.prototype.deserialize = function( jstr ){
this.data = JSON.parse(jstr);
};
the quicker way
However, you can make a simple addition to what you already have using the following:
First enhance your Game Objects with an objectName property. This is because constructor.name and function.name are unreliable and do strange things the further back in time you go, far better to use a string you have set in stone.
var roomBedroom = function ( title ) {
this.objectName = "roomBedroom";
this.title = title;
this.description = "I'm in a bedroom";
this.noun = "bed";
this.entities = new Array();
};
Then the additional code to help with storage:
var storage = {};
/// add your supported constructors to this list, there are more programmatic
/// ways to get at the constructor but it's better to be explicit.
storage.constructors = {
'roomBedroom' : roomBedroom
};
/// take an instance and convert to simple object
storage.to = function( obj ){
if ( obj.toStorage ) {
return obj.toStorage();
}
else {
var keep = {};
for ( var i in obj ) {
if ( obj.hasOwnProperty(i) && !obj[i].call ) {
keep[i] = obj[i];
}
}
return keep;
}
}
/// take simple object and convert to an instance of constructor
storage.from = function( obj ){
var n = obj && obj.objectName, c = storage.constructors[n];
if ( n && c ) {
if ( c.fromStorage ) {
return c.fromStorage( obj );
}
else {
var inst = new c();
for ( var i in obj ) {
if ( obj.hasOwnProperty(i) ) {
inst[i] = obj[i];
}
}
return inst;
}
}
else {
throw new Error('`' + n + '` undefined as storage constructor');
}
}
Once you have that you can use it like so:
var savedGameObjects = {};
savedGameObjects['bedroom'] = storage.to(new roomBedroom("bedroom"));
savedGameObjects['bedroom2'] = storage.to(new roomBedroom("bedroom2"));
var jsonGame = JSON.stringify(savedGameObjects);
console.log(jsonGame);
savedGameObjects = JSON.parse(jsonGame);
for( var i in savedGameObjects ) {
savedGameObjects[i] = storage.from(savedGameObjects[i]);
console.log(savedGameObjects[i]);
}
extras
You can also be specific about the way objects get stored/unstored by supplying toStorage and fromStorage methods on your constructed instances and constructors respectively. For example, you could use the following if you only wanted to store titles of roomBedrooms. Obviously this is an unrealistic use-case, you'd more often use this to avoid storing cached or computed sub-objects and properties.
roomBedroom.prototype.toStorage = function( obj ){
var ret = {};
ret.title = obj.title;
return ret;
};
roomBedroom.fromStorage = function( obj ){
var inst = new roomBedroom();
inst.title = obj.title;
return inst;
};
The above also means you can take advantage of improving your Game Object construction by providing parameters, rather than iterating over properties which can be slow and error-prone.
roomBedroom.fromStorage = function( obj ){
return new roomBedroom( obj.title );
};
Or even:
roomBedroom.fromStorage = function( obj ){
return new roomBedroom( obj ); // <-- the constructor processes the import.
};
fiddle
http://jsfiddle.net/XTUdp/
disclaimer
The above code relies on the existence of hasOwnProperty which is not present cross-browser yet, a polyfill should be used until it is... or, if you aren't doing anything complicated with prototype inheritance you don't need to worry and can remove it from the code.
you can declare a big variable like
var world = {};
and each small variable declare as
var bedroom = world.bed = (world.bed || new roomBedroom());
remember never change bedroom to another object, i think this will work fine, but looks too long winded

Do I have to initialize every level of an object in Javascript?

I'm not terribly good with Javascript so I'm wondering if there is a better way of doing this:
if (games[level] === undefined) {
games[level] = {};
games[level]['pending'] = {};
}
if (!games[level]['pending'].length) {
return game.create(level);
}
In PHP I can just test empty($games[$level]['pending']). Is there a better way of testing for this? Basically all I want to do is create the object if it does not exist.
if (games[level] === undefined) {
games[level] = game.create(level);
}
If there is no such level game create should be called to initialize all of the data needed. I don`t see any point of making it an object and then checking for "pending". It will be always empty, because you just created the object.
If your the second if returns something for games[level]['pending'].length you have a big problem with your code. You can`t create an empty object ( games[level]['pending'] = {} ) and find that it already has properties.
In addition:
games[level] = {};
// games[level]['pending'] = {}; - bad
games[level].pending = {}; // this way object properties should be used
you can make yourself a function to do that, pass it the games object a a string like "level.pending.id.something.something" and goes on and creates the objects.
function makeObj(obj, path) {
var parts = path.split("."), tmp = obj, name;
while (parts.length) {
name = parts.shift();
if (typeof tmp[name] === 'undefined') {
tmp[name] = {};
}
tmp = tmp[name];
}
}
var x = {};
makeObj(x, "this.is.a.test");
games[level] = games[level] || {pending: {}};
if (!games[level].pending.length) {
return game.create(level);
}

Do the keys of JavaScript associative arrays need to be strings, or can they be any object?

Do the keys of JavaScript associative arrays need to be strings, or can they be any object?
There are no native associative arrays in JavaScript, only objects. Objects have properties. The names of properties are always strings: even the numeric indices of arrays will be converted to strings before the 'array magic' happens.
If you're looking for associative arrays with arbitrary keys, look here.
I've implemented JavaScript HashMap which code can be obtained from http://github.com/lambder/HashMapJS/tree/master
The keys and values can be arbitrary JavaScript objects.
There aren't any requirements on objects used as keys or values.
The mechanism is trivial. For every key there is generated a unique id (per HashMap instance).
That id is injected to the key object under a high unlikely to collide field name ;)
That id is then used to keying in the underlying baking standard JavaScript association object.
Here is the code:
/*
=====================================================================
#license MIT
#author Lambder
#copyright 2009 Lambder.
#end
=====================================================================
*/
var HashMap = function() {
this.initialize();
}
HashMap.prototype = {
hashkey_prefix: "<#HashMapHashkeyPerfix>",
hashcode_field: "<#HashMapHashkeyPerfix>",
initialize: function() {
this.backing_hash = {};
this.code = 0;
},
/*
Maps value to key, returning the previous association
*/
put: function(key, value) {
var prev;
if (key && value) {
var hashCode = key[this.hashcode_field];
if (hashCode) {
prev = this.backing_hash[hashCode];
} else {
this.code += 1;
hashCode = this.hashkey_prefix + this.code;
key[this.hashcode_field] = hashCode;
}
this.backing_hash[hashCode] = value;
}
return prev;
},
/*
Returns value associated with given key
*/
get: function(key) {
var value;
if (key) {
var hashCode = key[this.hashcode_field];
if (hashCode) {
value = this.backing_hash[hashCode];
}
}
return value;
},
/*
Deletes association by given key.
Returns true if the association existed, false otherwise
*/
del: function(key) {
var success = false;
if (key) {
var hashCode = key[this.hashcode_field];
if (hashCode) {
var prev = this.backing_hash[hashCode];
this.backing_hash[hashCode] = undefined;
if(prev !== undefined)
success = true;
}
}
return success;
}
}
//// Usage
// Creation
var my_map = new HashMap();
// Insertion
var a_key = {};
var a_value = {struct: "structA"};
var b_key = {};
var b_value = {struct: "structB"};
var c_key = {};
var c_value = {struct: "structC"};
my_map.put(a_key, a_value);
my_map.put(b_key, b_value);
var prev_b = my_map.put(b_key, c_value);
// Retrieval
if(my_map.get(a_key) !== a_value){
throw("fail1")
}
if(my_map.get(b_key) !== c_value){
throw("fail2")
}
if(prev_b !== b_value){
throw("fail3")
}
// Deletion
var a_existed = my_map.del(a_key);
var c_existed = my_map.del(c_key);
var a2_existed = my_map.del(a_key);
if(a_existed !== true){
throw("fail4")
}
if(c_existed !== false){
throw("fail5")
}
if(a2_existed !== false){
throw("fail6")
}
Are you talking about JavaScript objects (JSON)?
The specification says that the keys should be strings.
But the JavaScript interpreter allows both {"key": "val"} and {key: "val"}.
Building on Lambder's idea, I've implemented a small DataStructures library.
I've tested it a little and everything seems to work.
It also automatically assigns a unique id to each HashTable/HashSet used to uniquely identify the object's key property.
var DataStructure = {};
DataStructure.init = function(){
DataStructure.initHashables();
delete DataStructure.initHashables;
}
DataStructure.initHashables = function(){
var objectHashableIndexer = new DataStructure.Indexer();
DataStructure.Hashable = function(){
var self = this;
// Constant
//
//
const ERROR_KEY_DOES_NOT_EXIST = "Key doesn't exists in Hashable when trying to pop. Associated Key Object and Hashable logged to console.";
const HASH_MAP_KEY_PROPERTY_BASE = "DATA_STRUCTURE_HASH_MAP_KEY_PROPERTY_";
// Attributes
//
//
var tableNumber = objectHashableIndexer.getIndex();
var tableKeyProperty = HASH_MAP_KEY_PROPERTY_BASE + tableNumber.toString();
self.tableKeyProperty = tableKeyProperty;
var indexer = new DataStructure.Indexer();
var data = {};
self.data = data;
// Methods
//
//
self.getObjectKey = function(){
return indexer.getIndex().toString();
}
self.putBackObjectKey = function(index){
indexer.putBackIndex(parseInt(index));
}
var getObjectKey = self.getObjectKey;
var putBackObjectKey = self.putBackObjectKey;
self.exists = function(key){
if (!(tableKeyProperty in key))
return false;
var realKey = key[tableKeyProperty];
if (!(realKey in data))
return false;
return true;
}
self.pop = function(key){
if (!self.exists(key)){
console.log(key);
console.log(self);
throw ERROR_KEY_DOES_NOT_EXIST;
}
else{
var realKey = key[tableKeyProperty];
delete key[tableKeyProperty];
delete data[realKey];
putBackObjectKey(realKey);
}
}
self.destroy = function(){
objectHashableIndexer.putBackIndex(tableNumber);
delete self;
}
}
/*
Class DataStructure.ObjectHashMap
Purpose: Provides a way to hash arbitrary objects to values.
Prototype Arguments:
Attributes:
Methods:
Notes:
Should call inherited method destroy() when done with table to preserve indexes
*/
DataStructure.ObjectHashMap = function(){
DataStructure.Hashable.call(this);
var self = this;
// Constant
//
//
const ERROR_KEY_EXISTS = "Key already exists in ObjectHashMap when trying to push. Associated Key Object and ObjectHashMap logged to console.";
const ERROR_KEY_DOES_NOT_EXIST = "Key doesn't exists in ObjectHashMap when trying to getValue. Associated Key Object and ObjectHashMap logged to console.";
// Attributes
//
//
var tableKeyProperty;
var data;
// Initialization
//
//
self.init = function(){
self.privatize();
delete self.privatize;
}
self.privatize = function(){
tableKeyProperty = self.tableKeyProperty;
delete self.tableKeyProperty;
getObjectKey = self.getObjectKey;
delete self.getObjectKey;
putBackObjectKey = self.putBackObjectKey;
delete self.putBackObjectKey;
data = self.data;
delete self.data;
}
// Methods
//
//
var getObjectKey;
var putBackObjectKey;
self.push = function(key, value){
if (self.exists(key)){
console.log(key);
console.log(self);
throw ERROR_KEY_EXISTS;
}
else{
var realKey = getObjectKey();
key[tableKeyProperty] = realKey;
data[realKey] = value;
}
}
self.getValue = function(key){
if(!self.exists(key)){
console.log(key);
console.log(self);
throw ERROR_KEY_DOES_NOT_EXIST;
}
else{
var realKey = key[tableKeyProperty];
return data[realKey];
}
}
self.init();
delete self.init;
}
/*
Class DataStructure.ObjectHashSet
Purpose: Provides a way to store arbitrary objects and check that they exist.
Prototype Arguments:
Attributes:
Methods:
Notes:
Should call inherited method destroy() when done with table to preserve indexes
*/
DataStructure.ObjectHashSet = function(){
DataStructure.Hashable.call(this);
var self = this;
// Constant
//
//
const ERROR_KEY_EXISTS = "Key already exists in ObjectHashSet when trying to push. Associated Key Object and ObjectHashSet logged to console.";
const ERROR_KEY_DOES_NOT_EXIST = "Key doesn't exists in ObjectHashSet when trying to getValue. Associated Key Object and ObjectHashSet logged to console.";
// Attributes
//
//
var tableKeyProperty;
var data;
// Initialization
//
//
self.init = function(){
self.privatize();
delete self.privatize;
}
self.privatize = function(){
tableKeyProperty = self.tableKeyProperty;
delete self.tableKeyProperty;
getObjectKey = self.getObjectKey;
delete self.getObjectKey;
putBackObjectKey = self.putBackObjectKey;
delete self.putBackObjectKey;
data = self.data;
delete self.data;
}
// Methods
//
//
var getObjectKey;
var putBackObjectKey;
self.push = function(key){
if (self.exists(key)){
console.log(key);
console.log(self);
throw ERROR_KEY_EXISTS;
}
else{
var realKey = getObjectKey();
key[tableKeyProperty] = realKey;
data[realKey] = "";
}
}
self.init();
delete self.init;
}
}
DataStructure.Indexer = function(){
var self = this;
// Constant
//
//
const DEFAULT_SIZE = 1000;
// Attributes
//
//
var nextIndex = 0;
var availableIndicies = 0;
var freeIndicies = [];
// Initialization
//
//
self.init = function(){
freeIndicies.length = DEFAULT_SIZE;
}
// Methods
//
//
self.getIndex = function(){
var index = 0;
if (availableIndicies === 0){
index = nextIndex;
++nextIndex;
}
else{
--availableIndicies;
index = freeIndicies[availableIndicies];
}
return index;
}
self.putBackIndex = function(index){
if (availableIndicies === freeIndicies.length)
freeIndicies.push(index);
else
freeIndicies[availableIndicies] = index;
++availableIndicies;
}
self.init();
delete self.init;
}
DataStructure.init();
delete DataStructure.init;
It depends on what you mean by "associative arrays". There is nothing called "associative arrays" in JavaScript, there are objects and there are maps.
Objects are the ones that can be accessed using the [] notation, for example foo["bar"], and there the keys have to be strings, as Christoph's answer explains.
There are also maps, which can have any object as keys. However, to access them, you can't use [] as for objects, you must use the get and set methods. Here is an example how to use them:
let myMap = new Map(); // Create the map
myMap.set("key", "value"); // To set a value, use the set method.
// The first argument is the key, the second one is the value.
myMap.set(Math, "bar"); // You can really use any object as key
myMap.set(console.log, "hello"); // Including functions
myMap.set(document.body, "world"); // And even DOM elements
// To get the value associated to a key, use the get method
console.log(myMap.get(Math)); // "bar"
console.log(myMap.get(document.body)); // "world"
In this example I used built-in objects as keys in order to avoid cluttering the example with defining new objects to use as keys, but it's of course possible to use your own objects as keys.
Be careful, however, not to use [] to access elements of a map. Doing myMap[whatever] is valid code so it won't throw an error, but it won't work as expected:
// Don't do this
myMap[Math] = 3;
myMap["[object Math]"] = 4;
console.log(myMap[Math]); //4
console.log(myMap.get(Math)); // 'undefined'
// Do this instead
myMap.set(Math, 3);
myMap.set("[object Math]", 4);
console.log(myMap.get(Math)); //3
To learn more about maps, see Map.

Categories

Resources