Apologise if the title is worded poorly.
What I am essentially tying to do is create a custom type which has methods on it. It's intended that this act as a generic type, so it can accept a string, int, etc
const foo = new CustomType('value')
console.log(foo) // 'value'
console.log(foo + 'hello') // 'valuehello'
foo.method() // Do something
const bar = new CustomType([])
bar.push('foobar')
If I use a class to store the value, I couldn't do operations on it.
class CustomType {
constructor(value) {
this.value = value
}
method() {}
}
const foo = new CustomType('value')
console.log(foo + 'hello') // [object Object]hello"
Similar to how you can use new Array() or new String()
What you're trying to do is possible with prototypes (which is
mimicked by classes in JS)
function CustomType(param) {
this.default = param;
}
CustomType.prototype.toString = function(postfix = "") {
return this.default + postfix;
};
CustomType.prototype.doSomething = function() {
console.log("I am doing something")
};
let customTypedObject = new CustomType("value");
console.log(customTypedObject.toString());// "value
console.log(customTypedObject.toString("hello")); // "valuehello
//Prototypal function inherited
customTypedObject.doSomething() //"I am doing something"
Coercion Overriding
What you're actually explaining sounds a lot like overriding coercion rules in javascript which defines how a custom type behaves with another primitive type. This is also possible.
function CustomType(param) {
this.default = param;
}
CustomType.prototype.valueOf = function() {
return this.default;
}
let customTypedObject = new CustomType("value")
console.log(customTypedObject + "hello"); //valuehello
Read more here
If you are trying to override the browser's default approach to "toString()" an object. This is not possible. However there is a way to do this inside nodejs though. Node internally calls "inspect" on an object which is available in node js's root object prototype. This can be overriden
//Works on NodeJS code (not in browser)
CustomType.prototype.inspect = function() {
return this.default;
};
let customTypedObject = new CustomType("value")
console.log(customTypedObject); // "value"
It's nearly impossible for the code, as given, to produce the desired results. When you do console.log(foo), for that to result in 'value' (and only 'value') being logged, foo can only contain the exact string 'value'. It can't even be 'value' wrapped in a String (or custom String) object.
console.log(new String('value'));
console.log, if passed a single variable, will log exactly what that variable passed to it is. If the variable is an object, it'll log that object, not anything else (like a string or a number). If the variable is a plain string or a number, then it won't have other methods on it, unless you mutate String.prototype or Number.prototype, which you should not do.
The best you can probably achieve would be to have an instance which, when coerced to a string, returns the desired stringified value:
class CustomType {
constructor(item) {
this.item = item;
}
toString() {
return this.item;
}
method() {
console.log('doing something');
}
}
const foo = new CustomType('value')
console.log(foo) // CustomType {item: "value"} - look in browser console, not snippet
console.log(foo + 'hello') // 'valuehello'
foo.method() // Do something
I said nearly impossible, not absolutely impossible. If you mutate the prototype of the passed object, you can put method on it to make foo.method() work:
const addCustomMethod = (item) => {
Object.getPrototypeOf(item).method = () => {
console.log('doing something');
};
};
const foo = 'value';
addCustomMethod(foo);
console.log(foo) // 'value'
console.log(foo + 'hello') // 'valuehello'
foo.method() // Do something
But mutating built-in prototypes is a horrible idea. The above snippet is for informational purposes only, please do not use it.
I am having an issue with JavaScript returning undefined (which is probably more due to my understanding of JS).
I have 2 functions like so:
getData = function(name) {
var string = "You're name is " + name;
alert("created string is: " + string); // this alerts "You're name is John"
return string;
}
firstFunction = function() {
newString = getData("John");
alert(newString); // this allerts "undefined"
}
firstFunction();
What would be the cause of getData returning undefined to firstFunction?
Code :
<script>
const getData = name => {
var string = `You're name is ${name}`;
alert(`created string is: ${string}`); // this alerts "You're name is John"
// The return string command is not required
}
const firstFunction = () => {
getData("John");
}
firstFunction();
</script>
Edits :
Used arrow function instead of normal one
used string literals instead of normal string
Possibly fixed the error
Reading :
string literals
Arrow function
I was trying to do something like this.
var myFunc = function() {}
myFunc.prototype = new String();
myFunc.prototype.replace = function() {return 'hii, Mr '+ this.toString();}
var oVal = new myFunc('Jyotirmay');
oVal.replace();
o/p :: Uncaught TypeError: String.prototype.toString is not generic(…)
Why "function not generic" error comes actually in general?
As to be more clear, How can i pass my argument i.e Jyotirmay from inherited class to base class i.e string. So that i can get that value by calling any proper string function.
I don't want to get my passed value from my function by handling that variable in it.
I want that to be handled by parent class. You can say super() in other languages.
It is unclear what exactly you are trying to achieve from your question and comments, but perhaps this is all you are trying to do?
function myFunc(inputArg) {
this.inputArg = inputArg;
}
myFunc.prototype = {
replace: function () {
return 'hii, Mr ' + this.inputArg;
},
toString: function () {
return '' + this.inputArg;
}
};
myFunc.prototype.valueOf = myFunc.prototype.toString;
function log(inputArg) {
document.getElementById('out').appendChild(document.createTextNode(inputArg + '\n'));
}
var oVal = new myFunc('Jyotirmay');
log(oVal);
log(oVal.replace());
<pre id="out"></pre>
As to Why is toString not generic, this is because not all objects can be represented as a string by the same conversion method.
Update based on your latest comment
Native objects are notoriously difficult, if not impossible, to subclass in Javascript. There are a few hacks that will allow you partial success, but I would not recommend them and good luck across different environments.
Two (but not the only) such hacks are:
Stealing from an iframe
function stealObject(objectName, myVariableName) {
var iframe = document.createElement('iframe');
iframe.style.display = 'none';
iframe.src = 'javascript:parent.' + myVariableName + ' = ' + objectName;
document.body.appendChild(iframe);
document.body.removeChild(iframe);
return window[myVariableName];
}
function log(inputArg) {
document.getElementById('out').appendChild(document.createTextNode(inputArg + '\n'));
}
try {
stealObject('String', 'MyString');
MyString.prototype.replace = function () {
return 'hii, Mr ' + this;
};
var oVal = new MyString('Jyotirmay');
log(oVal);
log(oVal.toUpperCase());
log(oVal.replace());
} catch (e) {
log(e);
}
<pre id="out"></pre>
Doesn't work in SO snippets because SecurityError: Sandbox access violation: but can see it on this jsFiddle. typeof oVal will return object and not string and oVal instanceof String will be false. oVal.constructor === String will return false.
Another hack
function MyString() {
this.str = '' + arguments[0];
};
with(MyString.prototype = new String()) {
toString = valueOf = function () {
return this.str;
};
}
MyString.prototype.replace = function () {
return 'hii, Mr ' + this;
};
function log(inputArg) {
document.getElementById('out').appendChild(document.createTextNode(inputArg + '\n'));
}
var oVal = new MyString('Jyotirmay');
log(oVal);
log(oVal.toUpperCase());
log(oVal.replace());
<pre id="out"></pre>
The magic length property is broken in this one and you would need to call oVal.toString().length instead. typeof oVal will return object and not string but oVal instanceof String will be true. oVal.constructor === String will return true.
I have seen examples of how to do this that rely heavily on the browser environment but not examples that work in the native node.js environment.
I need to cast JSON objects to javascript classes of a type that does not yet exist but is given by an input string.
I found some nice code on stackoverflow to retype JSON to known class but I have not figured out how to do this when the class type is a string and the class does not exist.
In software terms I need to:
var className = 'Bar';
console.log(global[className]); // false - class Bar is not defined
var jsonIn = '{ "name": "Jason" }';
var retypedJson = retypeJSON(jsonIn, className);
console.log(retypedJson instanceof Bar) // true
The code for recasting JSON. (Nice as it doesn't call eval or explicitly copy property names.)
// define a class
var Foo = function(name) {
this.name = name;
}
// make a method
Foo.prototype.shout = function() {
console.log("I am " + this.name);
}
// make a simple object from JSON:
var x = JSON.parse('{ "name": "Jason" }');
// force its class to be Foo
x.__proto__ = Foo.prototype;
// the method works
x.shout();
console.log(x instanceof Foo); // true
Thanks!
I have an answer that makes some use of eval and __proto__. Eval is only used to create the first prototype.
// create prototype - called once
var on = 'Apple';
var estr = 'function ' + on + '() {} ' + on + '.prototype.getInfo = function() { return this.name; }; ';
eval.apply(global, [estr]);
// make a simple object from JSON:
// called many times
var apl = JSON.parse('{ "name": "Jason" }');
// force its class to be Foo
apl.__proto__ = global[on].prototype;
// this method works
console.log(apl.getInfo());
Maybe this?
var constructors = {
"Obj1": ObjConstructor1(){},
"Obj2": ObjConstructor2(){},
}
var obj = new constructors.Obj1();
var jsonObj = {"a":1,"b":2};
for (var x in jsonObj) {
obj[x] = jsonObj[x];
}
I have an app that allows users to generate objects, and store them (in a MySQL table, as strings) for later use. The object could be :
function Obj() {
this.label = "new object";
}
Obj.prototype.setLabel = function(newLabel) {
this.label = newLabel;
}
If I use JSON.stringify on this object, I will only get the information on Obj.label (the stringified object would be a string like {label: "new object"}. If I store this string, and want to allow my user to retrieve the object later, the setLabel method will be lost.
So my question is: how can I re-instantiate the object, so that it keeps the properties stored thanks to JSON.stringify, but also gets back the different methods that should belong to its prototype. How would you do that ? I was thinking of something along "create a blank object" and "merge it with the stored one's properties", but I can't get it to work.
To do this, you'll want to use a "reviver" function when parsing the JSON string (and a "replacer" function or a toJSON function on your constructor's prototype when creating it). See Section 15.12.2 and 15.12.3 of the specification. If your environment doesn't yet support native JSON parsing, you can use one of Crockford's parsers (Crockford being the inventor of JSON), which also support "reviver" functions.
Here's a simple bespoke example that works with ES5-compliant browsers (or libraries that emulate ES5 behavior) (live copy, run in Chrome or Firefox or similar), but look after the example for a more generalized solution.
// Our constructor function
function Foo(val) {
this.value = val;
}
Foo.prototype.nifty = "I'm the nifty inherited property.";
Foo.prototype.toJSON = function() {
return "/Foo(" + this.value + ")/";
};
// An object with a property, `foo`, referencing an instance
// created by that constructor function, and another `bar`
// which is just a string
var obj = {
foo: new Foo(42),
bar: "I'm bar"
};
// Use it
display("obj.foo.value = " + obj.foo.value);
display("obj.foo.nifty = " + obj.foo.nifty);
display("obj.bar = " + obj.bar);
// Stringify it with a replacer:
var str = JSON.stringify(obj);
// Show that
display("The string: " + str);
// Re-create it with use of a "reviver" function
var obj2 = JSON.parse(str, function(key, value) {
if (typeof value === "string" &&
value.substring(0, 5) === "/Foo(" &&
value.substr(-2) == ")/"
) {
return new Foo(value.substring(5, value.length - 2));
}
return value;
});
// Use the result
display("obj2.foo.value = " + obj2.foo.value);
display("obj2.foo.nifty = " + obj2.foo.nifty);
display("obj2.bar = " + obj2.bar);
Note the toJSON on Foo.prototype, and the function we pass into JSON.parse.
The problem there, though, is that the reviver is tightly coupled to the Foo constructor. Instead, you can adopt a generic framework in your code, where any constructor function can support a fromJSON (or similar) function, and you can use just one generalized reviver.
Here's an example of a generalized reviver that looks for a ctor property and a data property, and calls ctor.fromJSON if found, passing in the full value it received (live example):
// A generic "smart reviver" function.
// Looks for object values with a `ctor` property and
// a `data` property. If it finds them, and finds a matching
// constructor that has a `fromJSON` property on it, it hands
// off to that `fromJSON` fuunction, passing in the value.
function Reviver(key, value) {
var ctor;
if (typeof value === "object" &&
typeof value.ctor === "string" &&
typeof value.data !== "undefined") {
ctor = Reviver.constructors[value.ctor] || window[value.ctor];
if (typeof ctor === "function" &&
typeof ctor.fromJSON === "function") {
return ctor.fromJSON(value);
}
}
return value;
}
Reviver.constructors = {}; // A list of constructors the smart reviver should know about
To avoid having to repeat common logic in toJSON and fromJSON functions, you could have generic versions:
// A generic "toJSON" function that creates the data expected
// by Reviver.
// `ctorName` The name of the constructor to use to revive it
// `obj` The object being serialized
// `keys` (Optional) Array of the properties to serialize,
// if not given then all of the objects "own" properties
// that don't have function values will be serialized.
// (Note: If you list a property in `keys`, it will be serialized
// regardless of whether it's an "own" property.)
// Returns: The structure (which will then be turned into a string
// as part of the JSON.stringify algorithm)
function Generic_toJSON(ctorName, obj, keys) {
var data, index, key;
if (!keys) {
keys = Object.keys(obj); // Only "own" properties are included
}
data = {};
for (index = 0; index < keys.length; ++index) {
key = keys[index];
data[key] = obj[key];
}
return {ctor: ctorName, data: data};
}
// A generic "fromJSON" function for use with Reviver: Just calls the
// constructor function with no arguments, then applies all of the
// key/value pairs from the raw data to the instance. Only useful for
// constructors that can be reasonably called without arguments!
// `ctor` The constructor to call
// `data` The data to apply
// Returns: The object
function Generic_fromJSON(ctor, data) {
var obj, name;
obj = new ctor();
for (name in data) {
obj[name] = data[name];
}
return obj;
}
The advantage here being that you defer to the implementation of a specific "type" (for lack of a better term) for how it serializes and deserializes. So you might have a "type" that just uses the generics:
// `Foo` is a constructor function that integrates with Reviver
// but doesn't need anything but the generic handling.
function Foo() {
}
Foo.prototype.nifty = "I'm the nifty inherited property.";
Foo.prototype.spiffy = "I'm the spiffy inherited property.";
Foo.prototype.toJSON = function() {
return Generic_toJSON("Foo", this);
};
Foo.fromJSON = function(value) {
return Generic_fromJSON(Foo, value.data);
};
Reviver.constructors.Foo = Foo;
...or one that, for whatever reason, has to do something more custom:
// `Bar` is a constructor function that integrates with Reviver
// but has its own custom JSON handling for whatever reason.
function Bar(value, count) {
this.value = value;
this.count = count;
}
Bar.prototype.nifty = "I'm the nifty inherited property.";
Bar.prototype.spiffy = "I'm the spiffy inherited property.";
Bar.prototype.toJSON = function() {
// Bar's custom handling *only* serializes the `value` property
// and the `spiffy` or `nifty` props if necessary.
var rv = {
ctor: "Bar",
data: {
value: this.value,
count: this.count
}
};
if (this.hasOwnProperty("nifty")) {
rv.data.nifty = this.nifty;
}
if (this.hasOwnProperty("spiffy")) {
rv.data.spiffy = this.spiffy;
}
return rv;
};
Bar.fromJSON = function(value) {
// Again custom handling, for whatever reason Bar doesn't
// want to serialize/deserialize properties it doesn't know
// about.
var d = value.data;
b = new Bar(d.value, d.count);
if (d.spiffy) {
b.spiffy = d.spiffy;
}
if (d.nifty) {
b.nifty = d.nifty;
}
return b;
};
Reviver.constructors.Bar = Bar;
And here's how we might test that Foo and Bar work as expected (live copy):
// An object with `foo` and `bar` properties:
var before = {
foo: new Foo(),
bar: new Bar("testing", 42)
};
before.foo.custom = "I'm a custom property";
before.foo.nifty = "Updated nifty";
before.bar.custom = "I'm a custom property"; // Won't get serialized!
before.bar.spiffy = "Updated spiffy";
// Use it
display("before.foo.nifty = " + before.foo.nifty);
display("before.foo.spiffy = " + before.foo.spiffy);
display("before.foo.custom = " + before.foo.custom + " (" + typeof before.foo.custom + ")");
display("before.bar.value = " + before.bar.value + " (" + typeof before.bar.value + ")");
display("before.bar.count = " + before.bar.count + " (" + typeof before.bar.count + ")");
display("before.bar.nifty = " + before.bar.nifty);
display("before.bar.spiffy = " + before.bar.spiffy);
display("before.bar.custom = " + before.bar.custom + " (" + typeof before.bar.custom + ")");
// Stringify it with a replacer:
var str = JSON.stringify(before);
// Show that
display("The string: " + str);
// Re-create it with use of a "reviver" function
var after = JSON.parse(str, Reviver);
// Use the result
display("after.foo.nifty = " + after.foo.nifty);
display("after.foo.spiffy = " + after.foo.spiffy);
display("after.foo.custom = " + after.foo.custom + " (" + typeof after.foo.custom + ")");
display("after.bar.value = " + after.bar.value + " (" + typeof after.bar.value + ")");
display("after.bar.count = " + after.bar.count + " (" + typeof after.bar.count + ")");
display("after.bar.nifty = " + after.bar.nifty);
display("after.bar.spiffy = " + after.bar.spiffy);
display("after.bar.custom = " + after.bar.custom + " (" + typeof after.bar.custom + ")");
display("(Note that after.bar.custom is undefined because <code>Bar</code> specifically leaves it out.)");
You can indeed create an empty instance and then merge the instance with the data. I recommend using a library function for ease of use (like jQuery.extend).
You had some errors though (function ... = function(...), and JSON requires keys to be surrounded by ").
http://jsfiddle.net/sc8NU/1/
var data = '{"label": "new object"}'; // JSON
var inst = new Obj; // empty instance
jQuery.extend(inst, JSON.parse(data)); // merge
Note that merging like this sets properties directly, so if setLabel is doing some checking stuff, this won't be done this way.
So far as I know, this means moving away from JSON; you're now customizing it, and so you take on all of the potential headaches that entails. The idea of JSON is to include data only, not code, to avoid all of the security problems that you get when you allow code to be included. Allowing code means that you have to use eval to run that code and eval is evil.
If you want to use the setters of Obj :
Obj.createFromJSON = function(json){
if(typeof json === "string") // if json is a string
json = JSON.parse(json); // we convert it to an object
var obj = new Obj(), setter; // we declare the object we will return
for(var key in json){ // for all properties
setter = "set"+key[0].toUpperCase()+key.substr(1); // we get the name of the setter for that property (e.g. : key=property => setter=setProperty
// following the OP's comment, we check if the setter exists :
if(setter in obj){
obj[setter](json[key]); // we call the setter
}
else{ // if not, we set it directly
obj[key] = json[key];
}
}
return obj; // we finally return the instance
};
This requires your class to have setters for all its properties.
This method is static, so you can use like this :
var instance = Obj.createFromJSON({"label":"MyLabel"});
var instance2 = Obj.createFromJSON('{"label":"MyLabel"}');
From ECMAScript 6 onwards you can just do:
Object.assign(new Obj(), JSON.parse(rawJsonString))
Note: You create a new empty object of the defined type first and then override its properties with the parsed JSON. Not the other way around.
The methods define behaviour and contain no variable data. They are "stored" as your code. So you don't actually have to store them in the database.
You would have to write your own stringify method that stores functions as properties by converting them to strings using the toString method.
JavaScript is prototype based programming language which is classless language where object orientation achieved by process of cloning existing objects that serve as prototypes.
Serializing JSON would be considering any methods, for instance if you have an object
var x = {
a: 4
getText: function() {
return x.a;
}
};
You will get just { a:4 } where getText method is skipped by the serializer.
I ran into this same trouble a year back and I had to maintain a separate helper class for each of my domain object and used $.extend() it to my deserialized object when need, just more like having methods to a base class for the domain objects.
Try to use toString on the method.
Update:
Iterate over the methods in obj and store them as string, and then instantiate them with new Function.
storedFunc = Obj.prototype.setLabel.toString();
Obj2.prototype['setLabel'] = new Function("return (" + storedFunc + ")")();