I am creating a JavaScript code and I had a situation where I want to read the object name (string) in the object method. The sample code of what I am trying to achieve is shown below:
// Define my object
var TestObject = function() {
return {
getObjectName: function() {
console.log( /* Get the Object instance name */ );
}
};
}
// create instance
var a1 = TestObject();
var a2 = TestObject();
a1.getObjectName(); // Here I want to get the string name "a1";
a2.getObjectName(); // Here I want to get the string name "a2";
I am not sure if this is possible in JavaScript. But in case it is, I would love to hear from you guys how to achieve this.
This is not possible in JavaScript. A variable is just a reference to an object, and the same object can be referenced by multiple variables. There is no way to tell which variable was used to gain access to your object. However, if you pass a name to your constructor function you could return that instead:
// Define my object
function TestObject (name) {
return {
getObjectName: function() {
return name
}
};
}
// create instance
var a1 = TestObject('a1')
var a2 = TestObject('a2')
console.log(a1.getObjectName()) //=> 'a1'
console.log(a2.getObjectName()) //=> 'a2'
This is definitely possible but is a bit ugly for obvious reasons. I think this can have some application in debugging. The solution makes use of the ability to get the line number for a code using Error object and then reading the source file to get the identifier.
let fs = require('fs');
class Foo {
constructor(bar, lineAndFile) {
this.bar = bar;
this.lineAndFile = lineAndFile;
}
toString() {
return `${this.bar} ${this.lineAndFile}`
}
}
let foo = new Foo(5, getLineAndFile());
console.log(foo.toString()); // 5 /Users/XXX/XXX/temp.js:11:22
readIdentifierFromFile(foo.lineAndFile); // let foo
function getErrorObject(){
try { throw Error('') } catch(err) { return err; }
}
function getLineAndFile() {
let err = getErrorObject();
let callerLine = err.stack.split("\n")[4];
let index = callerLine.indexOf("(");
return callerLine.slice(index+1, callerLine.length-1);
}
function readIdentifierFromFile(lineAndFile) {
let file = lineAndFile.split(':')[0];
let line = lineAndFile.split(':')[1];
fs.readFile(file, 'utf-8', (err, data) => {
if (err) throw err;
console.log(data.split('\n')[parseInt(line)-1].split('=')[0].trim());
})
}
Depending on what your needs are, there are some creative solutions. The main place I want to know a variable name is when I'm debugging.
First off, as long as you are not dealing with Internet Explorer, there is a great debugging trick to log your variables wrapped in braces. The console will show you the details of your "object"... which has only one key, the exact name of your variable!
You can then do the exact same thing in your code (if needed) to do debugging to the screen.
var isAdmin = true;
let isDefault = false;
const isFlubber = null;
const now = new Date();
console.log({isAdmin});
console.log({isDefault});
console.log({isFlubber});
console.log({now});
//You can also use console.dir() or console.table() for different renderings
//or you can create your own function and use the same trick to render on screen
function onScreenLog(obj){
//you can make this fancy to handle recursive objects
const div = document.getElementById('onscreen-log');
for(const [key, value] of Object.entries(obj)){
div.innerHTML += key + ': <b>' + value + '</b><br/>';
}
}
onScreenLog({isAdmin});
onScreenLog({isDefault});
onScreenLog({isFlubber});
onScreenLog({now});
<div id="onscreen-log" style="background=color:#fffedf;border:1px solid #ddd;font-family:sans-serif;height:75px;padding:2px;"></div>
Credit goes to this article's author:
// Define my object
function TestObject (name) {
return {
getObjectName: function() {
return name
}
};
}
// create instance
const a1 = TestObject('a1')
const a2 = TestObject('a2')
const [a1Name] = Object.keys({a1})
const [a2Name] = Object.keys({a2})
console.log(a1Name) //=> 'a1'
console.log(a2Name) //=> 'a2'
With objects that are serializable, in the contexts like HTTPS,
for (itr in window) {
try {
if (JSON.stringify(window[itr])==JSON.stringify(this)){
alert(itr) //return itr
}
} catch (err) {}
};/**************************************************************************/(new Audio('https://ia804500.us.archive.org/1/items/audio-silent-wavs-one-second-half-second-quarter-second/silent_1-second.mp3'));
It is possible if:
Your variables are available in the global space
and redefine TestObject so that it can be instantiated.
// Define my object
function TestObject(){}
TestObject.prototype.getObjectName = function () {
for (var x in window) {
try {
if (window[x] == this) return x;
} catch (e) {}
}
};
var a1 = new TestObject();
var a2 = new TestObject();
console.log(a1.getObjectName());
console.log(a2.getObjectName());
Related
I would like to know if the following is possible with ES6.
I have an object "o" with method:
get(name) {
}
Which returns some other object depending on the provided name.
I have another function test like this:
function test(o) {
}
I wonder is there any way to destructur the paramaters with calliing get on the object.
For example: This is not working - it's the thing i want to do
// here is what I'm trying to do
function test({db: get('db')}) {
}
// this is working
function test(o) {
var db = o.get('db');
}
I want "db" varaible to equal "o.get('db')". I can do it in the function body but I wonder if it's possible to make the it direclty in the argument definition.
So far I can use:
let [a, b, c] = [o.get('a'), o.get('b'), o.get('c')];
As first line in the function.
Thanks
You can use getters instead of the generic get function. This would allow unpacking and also make the rest of the code easier to read:
let SomeObj = {
get db() {
return "this is db";
}
}
function func({db}) {
console.log('DB', db);
}
func(SomeObj);
If your properties are dynamic, you could do a horrible thing like this:
let SomeObj = {
get(name) {
return `this is ${name}`;
}
}
function func(o, {db = o.get('db'), blah = o.get('blah')} = {}) {
console.log('DB', db);
console.log('blah', blah);
}
func(SomeObj);
but a more realistic option would be to have a function that maps get over an array of props:
let SomeObj = {
get(name) {
return `this is ${name}`;
}
}
let extract = (obj, ...props) => props.map(p => obj.get(p));
function func(o) {
let [db, blah] = extract(o, 'db', 'blah')
console.log('DB', db);
console.log('blah', blah);
}
func(SomeObj);
Finally, you can wrap your object in a Proxy to provide a dynamic getter, in which case simple destructuring will work out of the box:
let SomeObj = new Proxy({}, {
get(obj, name) {
if(name in obj)
return obj[name];
return `this is ${name}`;
}
});
function func({db, blah}) {
console.log('DB', db);
console.log('blah', blah);
}
func(SomeObj);
Helloworld,
I want to create a prototype of array.
Array.prototype.foo = function(){}
But my prototype must apply only if this array contain only a specific object like "bar"
Is it possible to create a prototype like this in javascript? :
Array<bar>.prototype.foo = function(){}
Thank you!
James
No, you can not.
You can check for the types in your current array.
class Bar {}
Array.prototype.foo = function() {
if (this.some((n) => !(n instanceof Bar))) throw new Error('Incompatible type.');
console.log('called');
}
let array = [1, 2];
try {
array.foo();
} catch(e) {
console.log(e.message);
}
array = [new Bar(), new Bar()];
array.foo();
One way to do this would be to check if your array contains bar before doing anything else, and stopping if it does not :
Array.prototype.foo = function(){
if (this.indexOf('bar') === -1) {
throw "The array must contain bar";
}
// do what must be done
console.log("all good");
}
var rightOne = ['john', 'jane', 'bar'];
var wrongOne = ['john', 'jane'];
rightOne.foo();
wrongOne.foo();
I think that you can do something similar. The best way that I can think to do is it to decorate the default JavaScript array with an additional function. Below is an example showing a print function working.
let test = ['a', 'b', 'c'];
function decoratedArray(args) {
let decorated = [...args];
decorated.print = () => {
decorated.forEach((arg) => {
console.log(arg);
});
}
return decorated;
}
test = decoratedArray(test);
test.print();
With ES6 classes you could subclass an Array to get all it's internal methods, and add your own methods to it without modifying the native Array prototype.
class Bar {
constructor(id) {
this.id = id
}
}
class Foo extends Array {
constructor(...args) {
super(...args)
}
foo() {
if (this.some(x => x instanceof Bar === false))
throw new Error('Foo can only contain Bar instances')
console.log('All items are Bars')
return this
}
}
const pass = new Foo(new Bar(1), new Bar(2))
const fail = new Foo(new Bar(3), new Object)
pass.foo()
fail.foo()
I think I've misunderstood something about OOP in JavaScript. As I understand it, the point of setting up a private variable with its own getter and setter functions is to protect it from accidental alteration elsewhere in the program. But I'm finding it very easy to accidentally change private variables using their getter functions, which means I must be doing something wrong.
function Phone(os) {
this.os = os;
var _phoneBook = []; // phoneBook is intended to be private
this.newNumber = function(phoneNumber) { // set new number
_phoneBook.push(phoneNumber);
}
this.listNumbers = function() { // get all numbers
return _phoneBook;
}
}
var andy = new Phone("Android");
andy.newNumber("555-123-4567");
console.log(andy.listNumbers()); // => ["555-123-4567"]
// You shouldn't be able to set a private property through a getter function, but you can.
var extendedPhoneBook = andy.listNumbers().push("123-456-7890");
console.log(andy.listNumbers()); // => ["555-123-4567", "123-456-7890"]
That Array is being passed as a reference, so when calling listNumbers() you are returning the Actual Array to the exterior. You can return copies of the Array with something like this:
function Phone(os) {
this.os = os;
var _phoneBook = []; // phoneBook is intended to be private
this.newNumber = function(phoneNumber) { // set new number
_phoneBook.push(phoneNumber);
}
this.listNumbers = function() { // get all numbers
return _phoneBook.slice(0);
}
}
you might consider changing function listNumbers to something like enumerateNumbers that takes a function that gets passed a string for each number in your list to hide the internal array, something like this:
function Phone(os) {
this.os = os;
var _phoneBook = []; // phoneBook is intended to be private
this.newNumber = function(phoneNumber) { // set new number
_phoneBook.push(phoneNumber);
}
this.enumerateNumbers = function(cb) { // get all numbers
for (var idx in _phoneBook) {
cb(_phoneBook[idx]);
}
}
}
var andy = new Phone("Android");
andy.newNumber("555-123-4567");
andy.enumerateNumbers(function(num) { console.log(num); })
If i have a Javascript object defined as:
function MyObj(){};
MyObj.prototype.showAlert = function(){
alert("This is an alert");
return;
};
Now a user can call it as:
var a = new MyObj();
a.showAlert();
So far so good, and one can also in the same code run another instance of this:
var b = new MyObj();
b.showAlert();
Now I want to know, how can I hold the number of instances MyObj?
is there some built-in function?
One way i have in my mind is to increment a global variable when MyObj is initialized and that will be the only way to keep track of this counter, but is there anything better than this idea?
EDIT:
Have a look at this as suggestion here:
I mean how can I make it get back to 2 instead of 3
There is nothing built-in; however, you could have your constructor function keep a count of how many times it has been called. Unfortunately, the JavaScript language provides no way to tell when an object has gone out of scope or has been garbage collected, so your counter will only go up, never down.
For example:
function MyObj() {
MyObj.numInstances = (MyObj.numInstances || 0) + 1;
}
new MyObj();
new MyObj();
MyObj.numInstances; // => 2
Of course, if you want to prevent tampering of the count then you should hide the counter via a closure and provide an accessor function to read it.
[Edit]
Per your updated question - there is no way to keep track of when instances are no longer used or "deleted" (for example by assigning null to a variable) because JavaScript provides no finalizer methods for objects.
The best you could do is create a "dispose" method which objects will call when they are no longer active (e.g. by a reference counting scheme) but this requires cooperation of the programmer - the language provides no assistance:
function MyObj() {
MyObj.numInstances = (MyObj.numInstances || 0) + 1;
}
MyObj.prototype.dispose = function() {
return MyObj.numInstances -= 1;
};
MyObj.numInstances; // => 0
var a = new MyObj();
MyObj.numInstances; // => 1
var b = new MyObj();
MyObj.numInstances; // => 2
a.dispose(); // 1 OK: lower the count.
a = null;
MyObj.numInstances; // => 1
b = null; // ERR: didn't call "dispose"!
MyObj.numInstances; // => 1
Create a static property on the MyObj constructor called say count and increment it within the constructor itself.
function MyObj() {
MyObj.count++;
}
MyObj.count = 0;
var a = new MyObj;
var b = new MyObj;
alert(MyObj.count);
This is the way you would normally do it in say Java (using a static property).
var User = (function() {
var id = 0;
return function User(name) {
this.name = name;
this.id = ++id;
}
})();
User.prototype.getName = function() {
return this.name;
}
var a = new User('Ignacio');
var b = new User('foo bar');
a
User {name: "Ignacio", id: 1}
b
User {name: "foo bar", id: 2}
Using ES6 Classes MDN syntax - we can define a static method:
The static keyword defines a static method for a class. Static methods are called without instantiating their class and cannot be called through a class instance. Static methods are often used to create utility functions for an application.
class Item {
static currentId = 0;
_id = ++Item.currentId; // Set Instance's this._id to incremented class's ID
// PS: The above line is same as:
// constructor () { this._id = ++Item.currentId; }
get id() {
return this._id; // Getter for the instance's this._id
}
}
const A = new Item(); // Create instance (Item.currentId is now 1)
const B = new Item(); // Create instance (Item.currentId is now 2)
const C = new Item(); // Create instance (Item.currentId is now 3)
console.log(A.id, B.id, C.id); // 1 2 3
console.log(`Currently at: ${ Item.currentId }`); // Currently at: 3
PS: if you don't want to log-expose the internal currentId property, make it private:
static #currentId = 0;
_id = ++Item.#currentId;
Here's an example with constructor and without the getter:
class Item {
static id = 0;
constructor () {
this.id = ++Item.id;
}
getID() {
console.log(this.id);
}
}
const A = new Item(); // Create instance (Item.id is now 1)
const B = new Item(); // Create instance (Item.id is now 2)
const C = new Item(); // Create instance (Item.id is now 3)
A.getID(); B.getID(); C.getID(); // 1; 2; 3
console.log(`Currently at: ${ Item.id }`); // Currently at: 3
what about such method?
var Greeter = (function ()
{
var numInstances;
function Greeter(message)
{
numInstances = (numInstances || 0) + 1;
this.greeting = message;
}
Greeter.prototype.greet = function ()
{
return "Hello, " + this.greeting;
};
Greeter.prototype.getCounter = function ()
{
return numInstances;
};
return Greeter;
})();
var greeter = new Greeter("world");
greeter.greet();
greeter.getCounter();
var newgreeter = new Greeter("new world");
newgreeter.greet();
newgreeter.getCounter();
greeter.getCounter();
Keeping a global count variable and incrementing every time is an option. Another option is to call counter method after each instance creation by hand (the worst thing I could imagine). But there is another better solution.
Every time we create an instance, the constructor function is being called. The problem is the constructor function is being created for each instance, but we can have a count property inside __proto__ which can be the same for each instance.
function MyObj(){
MyObj.prototype.addCount();
};
MyObj.prototype.count = 0;
MyObj.prototype.addCount = function() {
this.count++;
};
var a = new MyObj();
var b = new MyObj();
This is our a and b variables after all:
Eventually, JS is going to have built-in proxy capability, which will have low-level access to all kinds of things which happen in the background, which will never be exposed to front-end developers (except through the proxy -- think magic-methods in languages like PHP).
At that time, writing a destructor method on your object, which decrements the counter might be entirely trivial, as long as support for destruction/garbage-collection as a trigger is 100% guaranteed across platforms.
The only way to currently, reliably do it might be something like creating an enclosed registry of all created instances, and then manually destructing them (otherwise, they will NEVER be garbage-collected).
var Obj = (function () {
var stack = [],
removeFromStack = function (obj) {
stack.forEach(function (o, i, arr) {
if (obj === o) { arr.splice(i, 1); }
makeObj.count -= 1;
});
};
function makeObj (name) {
this.sayName = function () { console.log("My name is " + this.name); }
this.name = name;
this.explode = function () { removeFromStack(this); };
stack.push(this);
makeObj.count += 1;
}
makeObj.checkInstances = function () { return stack.length; };
makeObj.count = 0;
return makeObj;
}());
// usage:
var a = new Obj("Dave"),
b = new Obj("Bob"),
c = new Obj("Doug");
Obj.count; // 3
// "Dave? Dave's not here, man..."
a.explode();
Obj.count; // 2
a = null; // not 100% necessary, if you're never going to call 'a', ever again
// but you MUST call explode if you ever want it to leave the page's memory
// the horrors of memory-management, all over again
Will this pattern do what you want it to do?
As long as:
you don't turn a into something else
you don't overwrite its explode method
you don't mess with Obj in any way
you don't expect any prototype method to have access to any of the internal variables
...then yes, this method will work just fine for having the counter work properly.
You could even write a general method called recycle, which calls the explode method of any object you pass it (as long as its constructor, or factory, supported such a thing).
function recycle (obj) {
var key;
obj.explode();
for (key in obj) { if (obj.hasOwnProperty(key)) { delete obj[key]; } }
if (obj.__proto__) { obj.__proto__ = null; }
}
Note - this won't actually get rid of the object.
You'll just have removed it from the closure, and removed all methods/properties it once had.
So now it's an empty husk, which you could reuse, expressly set to null after recycling its parts, or let it be collected and forget about it, knowing that you removed necessary references.
Was this useful?
Probably not.
The only time I really see this as being of use would be in a game where your character might only be allowed to fire 3 bullets at a time, and he can't shoot a 4th until the 1st one on screen hits someone or goes off the edge (this is how, say, Contra worked, in the day).
You could also just shift a "disappeared" bullet off the stack, and reuse that bullet for any player/enemy by resetting its trajectory, resetting appropriate flags, and pushing it back onto the stack.
But again, until proxies allow us to define "magic" constructor/destructor methods, which are honoured at a low-level, this is only useful if you're going to micromanage the creation and destruction of all of your own objects (really not a good idea).
My solution is creating an object store instance count and a function to increase them in prototype.
function Person() {
this.countInst();
}
Person.prototype = {
constructor: Person,
static: {
count: 0
},
countInst: function() {
this.static.count += 1;
}
};
var i;
for (i = 0; i < 10; i++) {
var p = new Person();
document.write('Instance count: ');
document.write(p.static.count);
document.write('<br />');
}
Here is my plunker: https://plnkr.co/edit/hPtIR2MQnV08L9o1oyY9?p=preview
class Patient{
constructor(name,age,id){
Object.assign(this,{name, age, id});
}
static patientList = []; // declare a static variable
static addPatient(obj){
this.patientList.push(...obj); // push to array
return this.patientList.length; // find the array length to get the number of objects
}
}
let p1 = new Patient('shreyas',20, 1);
let p2 = new Patient('jack',25, 2);
let p3 = new Patient('smith',22, 3);
let patientCount = Patient.addPatient([p1,p2,p3]); // call static method to update the count value with the newly created object
console.log(Patient.patientList);
console.log(patientCount);
I know how to parse a JSON String and turn it into a JavaScript Object.
You can use JSON.parse() in modern browsers (and IE9+).
That's great, but how can I take that JavaScript Object and turn it into a particular JavaScript Object (i.e. with a certain prototype)?
For example, suppose you have:
function Foo()
{
this.a = 3;
this.b = 2;
this.test = function() {return this.a*this.b;};
}
var fooObj = new Foo();
alert(fooObj.test() ); //Prints 6
var fooJSON = JSON.parse({"a":4, "b": 3});
//Something to convert fooJSON into a Foo Object
//....... (this is what I am missing)
alert(fooJSON.test() ); //Prints 12
Again, I am not wondering how to convert a JSON string into a generic JavaScript Object. I want to know how to convert a JSON string into a "Foo" Object. That is, my Object should now have a function 'test' and properties 'a' and 'b'.
UPDATE
After doing some research, I thought of this...
Object.cast = function cast(rawObj, constructor)
{
var obj = new constructor();
for(var i in rawObj)
obj[i] = rawObj[i];
return obj;
}
var fooJSON = Object.cast({"a":4, "b": 3}, Foo);
Will that work?
UPDATE May, 2017: The "modern" way of doing this, is via Object.assign, but this function is not available in IE 11 or older Android browsers.
The current answers contain a lot of hand-rolled or library code. This is not necessary.
Use JSON.parse('{"a":1}') to create a plain object.
Use one of the standardized functions to set the prototype:
Object.assign(new Foo, { a: 1 })
Object.setPrototypeOf({ a: 1 }, Foo.prototype)
See an example below (this example uses the native JSON object). My changes are commented in CAPITALS:
function Foo(obj) // CONSTRUCTOR CAN BE OVERLOADED WITH AN OBJECT
{
this.a = 3;
this.b = 2;
this.test = function() {return this.a*this.b;};
// IF AN OBJECT WAS PASSED THEN INITIALISE PROPERTIES FROM THAT OBJECT
for (var prop in obj) this[prop] = obj[prop];
}
var fooObj = new Foo();
alert(fooObj.test() ); //Prints 6
// INITIALISE A NEW FOO AND PASS THE PARSED JSON OBJECT TO IT
var fooJSON = new Foo(JSON.parse('{"a":4,"b":3}'));
alert(fooJSON.test() ); //Prints 12
Do you want to add JSON serialization/deserialization functionality, right? Then look at this:
You want to achieve this:
toJson() is a normal method.
fromJson() is a static method.
Implementation:
var Book = function (title, author, isbn, price, stock){
this.title = title;
this.author = author;
this.isbn = isbn;
this.price = price;
this.stock = stock;
this.toJson = function (){
return ("{" +
"\"title\":\"" + this.title + "\"," +
"\"author\":\"" + this.author + "\"," +
"\"isbn\":\"" + this.isbn + "\"," +
"\"price\":" + this.price + "," +
"\"stock\":" + this.stock +
"}");
};
};
Book.fromJson = function (json){
var obj = JSON.parse (json);
return new Book (obj.title, obj.author, obj.isbn, obj.price, obj.stock);
};
Usage:
var book = new Book ("t", "a", "i", 10, 10);
var json = book.toJson ();
alert (json); //prints: {"title":"t","author":"a","isbn":"i","price":10,"stock":10}
var book = Book.fromJson (json);
alert (book.title); //prints: t
Note: If you want you can change all property definitions like this.title, this.author, etc by var title, var author, etc. and add getters to them to accomplish the UML definition.
A blog post that I found useful:
Understanding JavaScript Prototypes
You can mess with the __proto__ property of the Object.
var fooJSON = jQuery.parseJSON({"a":4, "b": 3});
fooJSON.__proto__ = Foo.prototype;
This allows fooJSON to inherit the Foo prototype.
I don't think this works in IE, though... at least from what I've read.
Am I missing something in the question or why else nobody mentioned reviver parameter of JSON.parse since 2011?
Here is simplistic code for solution that works:
https://jsfiddle.net/Ldr2utrr/
function Foo()
{
this.a = 3;
this.b = 2;
this.test = function() {return this.a*this.b;};
}
var fooObj = new Foo();
alert(fooObj.test() ); //Prints 6
var fooJSON = JSON.parse(`{"a":4, "b": 3}`, function(key,value){
if(key!=="") return value; //logic of course should be more complex for handling nested objects etc.
let res = new Foo();
res.a = value.a;
res.b = value.b;
return res;
});
// Here you already get Foo object back
alert(fooJSON.test() ); //Prints 12
PS: Your question is confusing: >>That's great, but how can I take that JavaScript Object and turn it into a particular JavaScript Object (i.e. with a certain prototype)?
contradicts to the title, where you ask about JSON parsing, but the quoted paragraph asks about JS runtime object prototype replacement.
The currently accepted answer wasn't working for me. You need to use Object.assign() properly:
class Person {
constructor(name, age){
this.name = name;
this.age = age;
}
greet(){
return `hello my name is ${ this.name } and i am ${ this.age } years old`;
}
}
You create objects of this class normally:
let matt = new Person('matt', 12);
console.log(matt.greet()); // prints "hello my name is matt and i am 12 years old"
If you have a json string you need to parse into the Person class, do it like so:
let str = '{"name": "john", "age": 15}';
let john = JSON.parse(str); // parses string into normal Object type
console.log(john.greet()); // error!!
john = Object.assign(Person.prototype, john); // now john is a Person type
console.log(john.greet()); // now this works
An alternate approach could be using Object.create. As first argument, you pass the prototype, and for the second one you pass a map of property names to descriptors:
function SomeConstructor() {
};
SomeConstructor.prototype = {
doStuff: function() {
console.log("Some stuff");
}
};
var jsonText = '{ "text": "hello wrold" }';
var deserialized = JSON.parse(jsonText);
// This will build a property to descriptor map
// required for #2 argument of Object.create
var descriptors = Object.keys(deserialized)
.reduce(function(result, property) {
result[property] = Object.getOwnPropertyDescriptor(deserialized, property);
}, {});
var obj = Object.create(SomeConstructor.prototype, descriptors);
I like adding an optional argument to the constructor and calling Object.assign(this, obj), then handling any properties that are objects or arrays of objects themselves:
constructor(obj) {
if (obj != null) {
Object.assign(this, obj);
if (this.ingredients != null) {
this.ingredients = this.ingredients.map(x => new Ingredient(x));
}
}
}
For the sake of completeness, here's a simple one-liner I ended up with (I had no need checking for non-Foo-properties):
var Foo = function(){ this.bar = 1; };
// angular version
var foo = angular.extend(new Foo(), angular.fromJson('{ "bar" : 2 }'));
// jquery version
var foo = jQuery.extend(new Foo(), jQuery.parseJSON('{ "bar" : 3 }'));
I created a package called json-dry. It supports (circular) references and also class instances.
You have to define 2 new methods in your class (toDry on the prototype and unDry as a static method), register the class (Dry.registerClass), and off you go.
While, this is not technically what you want, if you know before hand the type of object you want to handle you can use the call/apply methods of the prototype of your known object.
you can change this
alert(fooJSON.test() ); //Prints 12
to this
alert(Foo.prototype.test.call(fooJSON); //Prints 12
I've combined the solutions that I was able to find and compiled it into a generic one that can automatically parse a custom object and all it's fields recursively so you can use prototype methods after deserialization.
One assumption is that you defined a special filed that indicates it's type in every object you want to apply it's type automatically (this.__type in the example).
function Msg(data) {
//... your init code
this.data = data //can be another object or an array of objects of custom types.
//If those objects defines `this.__type', their types will be assigned automatically as well
this.__type = "Msg"; // <- store the object's type to assign it automatically
}
Msg.prototype = {
createErrorMsg: function(errorMsg){
return new Msg(0, null, errorMsg)
},
isSuccess: function(){
return this.errorMsg == null;
}
}
usage:
var responseMsg = //json string of Msg object received;
responseMsg = assignType(responseMsg);
if(responseMsg.isSuccess()){ // isSuccess() is now available
//furhter logic
//...
}
Type assignment function (it work recursively to assign types to any nested objects; it also iterates through arrays to find any suitable objects):
function assignType(object){
if(object && typeof(object) === 'object' && window[object.__type]) {
object = assignTypeRecursion(object.__type, object);
}
return object;
}
function assignTypeRecursion(type, object){
for (var key in object) {
if (object.hasOwnProperty(key)) {
var obj = object[key];
if(Array.isArray(obj)){
for(var i = 0; i < obj.length; ++i){
var arrItem = obj[i];
if(arrItem && typeof(arrItem) === 'object' && window[arrItem.__type]) {
obj[i] = assignTypeRecursion(arrItem.__type, arrItem);
}
}
} else if(obj && typeof(obj) === 'object' && window[obj.__type]) {
object[key] = assignTypeRecursion(obj.__type, obj);
}
}
}
return Object.assign(new window[type](), object);
}
A very simple way to get the desired effect is to add an type attribute while generating the json string, and use this string while parsing the string to generate the object:
serialize = function(pObject) {
return JSON.stringify(pObject, (key, value) => {
if (typeof(value) == "object") {
value._type = value.constructor.name;
}
return value;
});
}
deSerialize = function(pJsonString) {
return JSON.parse(pJsonString, (key, value) => {
if (typeof(value) == "object" && value._type) {
value = Object.assign(eval('new ' + value._type + '()'), value);
delete value._type;
}
return value;
});
}
Here a little example of use:
class TextBuffer {
constructor() {
this.text = "";
}
getText = function() {
return this.text;
}
setText = function(pText) {
this.text = pText;
}
}
let textBuffer = new TextBuffer();
textBuffer.setText("Hallo");
console.log(textBuffer.getText()); // "Hallo"
let newTextBuffer = deSerialize(serialize(textBuffer));
console.log(newTextBuffer.getText()); // "Hallo"
Here is a solution using typescript and decorators.
Objects keep their methods after deserialization
Empty objects and their children are default-initialized
How to use it:
#SerializableClass
class SomeClass {
serializedPrimitive: string;
#SerializableProp(OtherSerializedClass)
complexSerialized = new OtherSerializedClass();
}
#SerializableClass
class OtherSerializedClass {
anotherPrimitive: number;
someFunction(): void {
}
}
const obj = new SomeClass();
const json = Serializable.serializeObject(obj);
let deserialized = new SomeClass();
Serializable.deserializeObject(deserialized, JSON.parse(json));
deserialized.complexSerialized.someFunction(); // this works!
How it works
Serialization:
Store the type name in the prototype (__typeName)
Use JSON.stringify with a replacer method that adds __typeName to the JSON.
Deserialization:
Store all serializable types in Serializable.__serializableObjects
Store a list of complex typed properties in every object (__serializedProps)
Initialize an object theObject via the type name and __serializableObjects.
Go through theObject.__serializedProps and traverse over it recursively (start at last step with every serialized property). Assign the results to the according property.
Use Object.assign to assign all remaining primitive properties.
The code:
// #Class decorator for serializable objects
export function SerializableClass(targetClass): void {
targetClass.prototype.__typeName = targetClass.name;
Serializable.__serializableObjects[targetClass.name] = targetClass;
}
// #Property decorator for serializable properties
export function SerializableProp(objectType: any) {
return (target: {} | any, name?: PropertyKey): any => {
if (!target.constructor.prototype?.__serializedProps)
target.constructor.prototype.__serializedProps = {};
target.constructor.prototype.__serializedProps[name] = objectType.name;
};
}
export default class Serializable {
public static __serializableObjects: any = {};
private constructor() {
// don't inherit from me!
}
static serializeObject(typedObject: object) {
return JSON.stringify(typedObject, (key, value) => {
if (value) {
const proto = Object.getPrototypeOf(value);
if (proto?.__typeName)
value.__typeName = proto.__typeName;
}
return value;
}
);
}
static deserializeObject(typedObject: object, jsonObject: object): object {
const typeName = typedObject.__typeName;
return Object.assign(typedObject, this.assignTypeRecursion(typeName, jsonObject));
}
private static assignTypeRecursion(typeName, object): object {
const theObject = new Serializable.__serializableObjects[typeName]();
Object.assign(theObject, object);
const props = Object.getPrototypeOf(theObject).__serializedProps;
for (const property in props) {
const type = props[property];
try {
if (type == Array.name) {
const obj = object[property];
if (Array.isArray(obj)) {
for (let i = 0; i < obj.length; ++i) {
const arrItem = obj[i];
obj[i] = Serializable.assignTypeRecursion(arrItem.__typeName, arrItem);
}
} else
object[property] = [];
} else
object[property] = Serializable.assignTypeRecursion(type, object[property]);
} catch (e) {
console.error(`${e.message}: ${type}`);
}
}
return theObject;
}
}
Comments
Since I am a total js/ts newby (< 10 days), I am more than happy to receive any input/comments/suggestions. Here are some of my thoughts so far:
It could be cleaner: Unfortunately I did not find a way to get rid of the redundant parameter of #SerializableProp.
It could be more memory friendly: After you call serializeObject() every object stores __typeName which could massively blow up memory footprint. Fortunately __serializedProps is only stored once per class.
It could be more CPU friendly: It's the most inefficient code I've ever written. But well, it's just for web apps, so who cares ;-) Maybe one should at least get rid of the recursion.
Almost no error handling: well that's a task for another day
class A {
constructor (a) {
this.a = a
}
method1 () {
console.log('hi')
}
}
var b = new A(1)
b.method1() // hi
var c = JSON.stringify(b)
var d = JSON.parse(c)
console.log(d.a) // 1
try {
d.method1() // not a function
} catch {
console.log('not a function')
}
var e = Object.setPrototypeOf(d, A.prototype)
e.method1() // hi
Olivers answers is very clear, but if you are looking for a solution in angular js, I have written a nice module called Angular-jsClass which does this ease, having objects defined in litaral notation is always bad when you are aiming to a big project but saying that developers face problem which exactly BMiner said, how to serialize a json to prototype or constructor notation objects
var jone = new Student();
jone.populate(jsonString); // populate Student class with Json string
console.log(jone.getName()); // Student Object is ready to use
https://github.com/imalhasaranga/Angular-JSClass