Do I have to initialize every level of an object in Javascript? - 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);
}

Related

JavaScript: Adding a nested property to an object that may not exist

Suppose I have a json object in which I record the number of visitors to my site, grouped by browser / version.
let data = {
browsers: {
chrome: {
43 : 13,
44 : 11
},
firefox: {
27: 9
}
}
}
To increment a particular browser, I need to check if several keys exist, and if not, create them.
let uap = UAParser(request.headers['user-agent']);
if (typeof uap.browser !== 'undefined') {
if (typeof data.browsers === 'undefined')
data.browsers = {}
if (typeof data.browsers[uap.browser.name] === 'undefined')
data.browsers[uap.browser.name] = {}
if (typeof data.browsers[uap.browser.name][uap.browser.version] === 'undefined')
data.browsers[uap.browser.name][uap.browser.version] = 0
data.browsers[uap.browser.name][uap.browser.version] += 1;
}
The deeper my data structure the crazier things get.
It feels like there must be a neater way to do this in javascript. There's always a neater way. Can anyone enlighten me here?
This shorter code should do the trick:
if (uap.browser) { // typeof is pretty much redundant for object properties.
const name = uap.browsers.name; // Variables for readability.
const version = uap.browser.version;
// Default value if the property does not exist.
const browsers = data.browsers = data.browsers || {};
const browser = browsers[name] = browsers[name] || {};
browser[version] = browser[version] || 0;
// Finally, increment the value.
browser[version]++;
}
Note that you were using === where you should've been using = (in === {}).
Let's explain this line:
const browsers = data.browsers = data.browsers || {};
The last part: data.browsers = data.browsers || {} sets data.browsers to be itself if it exists. If it doesn't yet, it's set to be a new empty object.
Then, that whole value gets assigned to browsers, for ease of access.
Now, shorter code shouldn't be top priority, but in cases like this, you can make the code a lot more readable.
You can give up the if statements and do it like this:
uap.browser = uap.browser || {}
essentially it does the same as the if only much shorter
Here's a very clean and generic solution using Proxy() I have a second solution which is standard ECMAscript 5 if you don't need it so cleanly or need it less browser dependant.
var handler = {
get: function (target, property) {
if (property !== "toJSON" && target[property] === undefined) {
target[property] = new Proxy ({}, handler);
}
return target[property];
}
}
var jsonProxy = new Proxy ({}, handler);
jsonProxy.non.existing.property = 5;
jsonProxy.another.property.that.doesnt.exist = 2;
jsonProxy["you"]["can"]["also"]["use"]["strings"] = 20;
console.log (JSON.stringify (jsonProxy));
You can do the same thing with classes but with a more verbose syntax:
var DynamicJSON = function () {};
DynamicJSON.prototype.get = function (property) {
if (this[property] === undefined) {
this[property] = new DynamicJSON ();
}
return this[property];
};
DynamicJSON.prototype.set = function (property, value) {
this[property] = value;
};
var jsonClass = new DynamicJSON ();
jsonClass.get("non").get("existing").set("property", 5);
jsonClass.get("you").get("have").get("to").get("use").set("strings", 20);
console.log (JSON.stringify (jsonClass));

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

Searching for an instance of a custom object

I want to check if my javascript object is of a certain type.
I have an object (I cannot edit this object.) and I have made instances of that object. Now I want to loop through all the objects that are available at any given point and figure out which ones are the instances that I am looking for.
Example:
var myObject = function() { };
var obj_person = new myObject();
Now if I write
typeof obj_person
It gives me object which is not useful. I cannot use instanceof as it does not work in IE8.
Is there anyway to check if obj_person is an instance of myObject?
Thanks.
EDITED***
Here is the code
var arr_instances = [];
for(var v in window)
{
var obj_required;
if(typeof window[v] == 'object')
{
obj_required = window[v];
if(obj_required.imgSrc)
{
arr_instances.push(obj_required);
}
}
}
Then I can iterate withing arr_instance and do whatever I want to do.
for(var i=0; i<arr_instances.len; i++)
{
if(arr_instances[i].imgSrc == 'requiredImg.jpg')
{
arr_instances[i].imgSrc = 'newImg.jpg';
}
}

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

How to stringify inherited objects to JSON?

json2.js seems to ignore members of the parent object when using JSON.stringify(). Example:
require('./json2.js');
function WorldObject(type) {
this.position = 4;
}
function Actor(val) {
this.someVal = 50;
}
Actor.prototype = new WorldObject();
var a = new Actor(2);
console.log(a.position);
console.log(JSON.stringify(a));
The output is:
4
{"someVal":50}
I would expect this output:
4
{"position":0, "someVal":50}
Well that's just the way it is, JSON.stringify does not preserve any of the not-owned properties of the object. You can have a look at an interesting discussion about other drawbacks and possible workarounds here.
Also note that the author has not only documented the problems, but also written a library called HydrateJS that might help you.
The problem is a little bit deeper than it seems at the first sight. Even if a would really stringify to {"position":0, "someVal":50}, then parsing it later would create an object that has the desired properties, but is neither an instance of Actor, nor has it a prototype link to the WorldObject (after all, the parse method doesn't have this info, so it can't possibly restore it that way).
To preserve the prototype chain, clever tricks are necessary (like those used in HydrateJS). If this is not what you are aiming for, maybe you just need to "flatten" the object before stringifying it. To do that, you could e.g. iterate all the properties of the object, regardless of whether they are own or not and re-assign them (this will ensure they get defined on the object itself instead of just inherited from the prototype).
function flatten(obj) {
var result = Object.create(obj);
for(var key in result) {
result[key] = result[key];
}
return result;
}
The way the function is written it doesn't mutate the original object. So using
console.log(JSON.stringify(flatten(a)));
you'll get the output you want and a will stay the same.
Another option would be to define a toJSON method in the object prototype you want to serialize:
function Test(){}
Test.prototype = {
someProperty: "some value",
toJSON: function() {
var tmp = {};
for(var key in this) {
if(typeof this[key] !== 'function')
tmp[key] = this[key];
}
return tmp;
}
};
var t = new Test;
JSON.stringify(t); // returns "{"someProperty" : "some value"}"
This works since JSON.stringify searches for a toJSON method in the object it receives, before trying the native serialization.
Check this fiddle: http://jsfiddle.net/AEGYG/
You can flat-stringify the object using this function:
function flatStringify(x) {
for(var i in x) {
if(!x.hasOwnProperty(i)) {
// weird as it might seem, this actually does the trick! - adds parent property to self
x[i] = x[i];
}
}
return JSON.stringify(x);
}
Here is a recursive version of the snippet #TomasVana included in his answer, in case there is inheritance in multiple levels of your object tree:
var flatten = function(obj) {
if (obj === null) {
return null;
}
if (Array.isArray(obj)) {
var newObj = [];
for (var i = 0; i < obj.length; i++) {
if (typeof obj[i] === 'object') {
newObj.push(flatten(obj[i]));
}
else {
newObj.push(obj[i]);
}
}
return newObj;
}
var result = Object.create(obj);
for(var key in result) {
if (typeof result[key] === 'object') {
result[key] = flatten(result[key]);
}
else {
result[key] = result[key];
}
}
return result;
}
And it keeps arrays as arrays. Call it the same way:
console.log(JSON.stringify(flatten(visualDataViews)));
While the flatten approach in general works, the snippets in other answers posted so far don't work for properties that are not modifiable, for example if the prototype has been frozen. To handle this case, you would need to create a new object and assign the properties to this new object. Since you're just stringifying the resulting object, object identity and other JavaScript internals probably don't matter, so it's perfectly fine to return a new object. This approach is also arguably more readable than reassigning an object's properties to itself, since it doesn't look like a no-op:
function flatten(obj) {
var ret = {};
for (var i in obj) {
ret[i] = obj[i];
}
return ret;
}
JSON.stringify takes three options
JSON.stringify(value[, replacer[, space]])
So, make use of the replacer, which is a function, that is called recursively for every key-value-pair.
Next Problem, to get really everything, you need to follow the prototpes and you must use getOwnPropertyNames to get all property names (more than you can catch with keysor for…in):
var getAllPropertyNames = () => {
const seen = new WeakSet();
return (obj) => {
let props = [];
do {
if (seen.has(obj)) return [];
seen.add(obj);
Object.getOwnPropertyNames(obj).forEach((prop) => {
if (props.indexOf(prop) === -1) props.push(prop);
});
} while ((obj = Object.getPrototypeOf(obj)));
return props;
};
};
var flatten = () => {
const seen = new WeakSet();
const getPropertyNames = getAllPropertyNames();
return (key, value) => {
if (value !== null && typeof value === "object") {
if (seen.has(value)) return;
seen.add(value);
let result = {};
getPropertyNames(value).forEach((k) => (result[k] = value[k]));
return result;
}
return value;
};
};
Then flatten the object to JSON:
JSON.stringify(myValue, flatten());
Notes:
I had a case where value was null, but typeof value was "object"
Circular references must bee detected, so it needs seen

Categories

Resources