I have a JavaScript ES6 class that has a property set with set and accessed with get functions. It is also a constructor parameter so the class can be instantiated with said property.
class MyClass {
constructor(property) {
this.property = property
}
set property(prop) {
// Some validation etc.
this._property = prop
}
get property() {
return this._property
}
}
I use _property to escape the JS gotcha of using get/set that results in an infinite loop if I set directly to property.
Now I need to stringify an instance of MyClass to send it with a HTTP request. The stringified JSON is an object like:
{
//...
_property:
}
I need the resulting JSON string to preserve property so the service I am sending it to can parse it correctly. I also need property to remain in the constructor because I need to construct instances of MyClass from JSON sent by the service (which is sending objects with property not _property).
How do I get around this? Should I just intercept the MyClass instance before sending it to the HTTP request and mutate _property to property using regex? This seems ugly, but I will be able to keep my current code.
Alternatively I can intercept the JSON being sent to the client from the service and instantiate MyClass with a totally different property name. However this means a different representation of the class either side of the service.
You can use toJSON method to customise the way your class serialises to JSON:
class MyClass {
constructor(property) {
this.property = property
}
set property(prop) {
// Some validation etc.
this._property = prop
}
get property() {
return this._property
}
toJSON() {
return {
property: this.property
}
}
}
If you want to avoid calling toJson, there is another solution using enumerable and writable:
class MyClass {
constructor(property) {
Object.defineProperties(this, {
_property: {writable: true, enumerable: false},
property: {
get: function () { return this._property; },
set: function (property) { this._property = property; },
enumerable: true
}
});
this.property = property;
}
}
I made some adjustments to the script of Alon Bar. Below is a version of the script that works perfectly for me.
toJSON() {
const jsonObj = Object.assign({}, this);
const proto = Object.getPrototypeOf(this);
for (const key of Object.getOwnPropertyNames(proto)) {
const desc = Object.getOwnPropertyDescriptor(proto, key);
const hasGetter = desc && typeof desc.get === 'function';
if (hasGetter) {
jsonObj[key] = this[key];
}
}
return jsonObj;
}
As mentioned by #Amadan you can write your own toJSON method.
Further more, in order to avoid re-updating your method every time you add a property to your class you can use a more generic toJSON implementation.
class MyClass {
get prop1() {
return 'hello';
}
get prop2() {
return 'world';
}
toJSON() {
// start with an empty object (see other alternatives below)
const jsonObj = {};
// add all properties
const proto = Object.getPrototypeOf(this);
for (const key of Object.getOwnPropertyNames(proto)) {
const desc = Object.getOwnPropertyDescriptor(proto, key);
const hasGetter = desc && typeof desc.get === 'function';
if (hasGetter) {
jsonObj[key] = desc.get();
}
}
return jsonObj;
}
}
const instance = new MyClass();
const json = JSON.stringify(instance);
console.log(json); // outputs: {"prop1":"hello","prop2":"world"}
If you want to emit all properties and all fields you can replace const jsonObj = {}; with
const jsonObj = Object.assign({}, this);
Alternatively, if you want to emit all properties and some specific fields you can replace it with
const jsonObj = {
myField: myOtherField
};
Use private fields for internal use.
class PrivateClassFieldTest {
#property;
constructor(value) {
this.property = value;
}
get property() {
return this.#property;
}
set property(value) {
this.#property = value;
}
}
class Test {
constructor(value) {
this.property = value;
}
get property() {
return this._property;
}
set property(value) {
this._property = value;
}
}
class PublicClassFieldTest {
_property;
constructor(value) {
this.property = value;
}
get property() {
return this.property;
}
set property(value) {
this._property = value;
}
}
class PrivateClassFieldTest {
#property;
constructor(value) {
this.property = value;
}
get property() {
return this.#property;
}
set property(value) {
this.#property = value;
}
}
console.log(JSON.stringify(new Test("test")));
console.log(JSON.stringify(new PublicClassFieldTest("test")));
console.log(JSON.stringify(new PrivateClassFieldTest("test")));
I've made an npm module named esserializer to solve such problem: stringify an instance of JavaScript class, so that it can be sent with HTTP request:
// Client side
const ESSerializer = require('esserializer');
const serializedText = ESSerializer.serialize(anInstanceOfMyClass);
// Send HTTP request, with serializedText as data
On service side, use esserializer again to deserialize the data into a perfect copy of anInstanceOfMyClass, with all getter/setter fields (such as property) retained:
// Node.js service side
const deserializedObj = ESSerializer.deserialize(serializedText, [MyClass]);
// deserializedObj is a perfect copy of anInstanceOfMyClass
I ran into the same issue but I have no access to the class construction and I'm not able to add or override the ToJson method
here is the solution that helped me solve it
a simple class with getters and properties
class MyClass {
jack = "yoo"
get prop1() {
return 'hello';
}
get prop2() {
return 'world';
}
}
a class with a child class and also child object with getters
class MyClassB {
constructor() {
this.otherClass = new MyClass()
}
joe = "yoo"
otherObject = {
youplaboum: "yoo",
get propOtherObject() {
return 'propOtherObjectValue';
}
}
get prop1() {
return 'helloClassB';
}
get prop2() {
return 'worldClassB';
}
}
here is the magic recursive function inspired by the ToJSON made by #bits
const objectWithGetters = function (instance) {
const jsonObj = Object.assign({}, instance);
const proto = Object.getPrototypeOf(instance);
for (const key of Object.getOwnPropertyNames(proto)) {
const desc = Object.getOwnPropertyDescriptor(proto, key);
const hasGetter = desc && typeof desc.get === 'function';
if (hasGetter) {
jsonObj[key] = desc.get();
}
}
for (let i in jsonObj) {
let value = jsonObj[i];
if (typeof value === "object" && value.constructor) {
jsonObj[i] = objectWithGetters(value);
}
}
return jsonObj;
}
const instance = new MyClassB();
const jsonObj = objectWithGetters(instance)
console.log(jsonObj)
let json = JSON.parse(jsonObj);
console.log(json)
Related
When I tried to log the isCheckedOut setter on the console, I am getting an error testLib.isCheckedOut is not a function
I'm having a hard time figuring out why. Any help would be very great
/* Parent Class */
class Library {
constructor(title) {
this._title = title;
this._isCheckedOut = false;
this._ratings = [];
}
get title() {
return this._title;
}
get isCheckedOut() {
return this._isCheckedOut;
}
set isCheckedOut(value) {
this._isCheckedOut = value;
}
get ratings() {
return this._ratings;
}
getAverageRating() {
}
toggleCheckOutStatus() {
}
addRating() {
}
}
const testLib = new Library;
console.log(testLib.isCheckedOut(true));
Setters obfuscate the fact that they're functions to callers. When you have an object with a setter, to invoke the setter, assign to the property:
someObj.theSetterPropertyName = theArgumentToPassToSetter;
Similarly, to invoke a getter, reference the property as an expression:
someObj.theGetterPropertyName
So, you want:
class Library {
constructor(title) {
this._title = title;
this._isCheckedOut = false;
this._ratings = [];
}
get title() {
return this._title;
}
get isCheckedOut() {
return this._isCheckedOut;
}
set isCheckedOut(value) {
this._isCheckedOut = value;
}
get ratings() {
return this._ratings;
}
}
const testLib = new Library;
testLib.isCheckedOut = true; // invoke setter
console.log(testLib.isCheckedOut); // invoke getter
const testLib = new Library;
console.log(testLib.isCheckedOut = true);
JS setter
The set syntax binds an object property to a function to be called when there is an attempt to set that property.
You can't call setter like function.
a setter can be used to execute a function whenever a specified property
is attempted to be changed
Set a property using a setter:
const testLib = new Library;
testLib.isCheckedOut = true;
console.log(testLib.isCheckedOut);
Calling a function:
const testLib = new Library;
testLib.addRating();
I was having the same issue at this lesson, only used empty brackets instead of .isCheckedOut(true). Got the same error.
console.log(testLib.isCheckedOut(true));
Remove the brackets altogether after the isCheckedOut property and it'll work:
console.log(testLib.isCheckedOut);
I would expect an instance of the following class to return both the "private" variable AND it's getter, but the getter variable is not returned at all. Why?
class Foo {
_myPrivateVar = null;
get myPublicVar() {
return this._myPrivateVar;
}
set myPublicVar(v) {
if (v > 4) {
this._myPrivateVar = v;
}
}
}
const f = new Foo();
console.log(f) // Foo { _myPrivateVar: 5 }
I expected { _myPrivateVar: 5, myPublicVar: 5 }. I understand I can access f.myPublicVar directly, but the problem is when return the instance from an Express server, for example, the resulting JSON object is devoid of the getter property myPublicVar. Why?
The getter and setter is on the prototype, whereas _myPrivateVar is put on the instance itself.
When an object is serialized with JSON.stringify, such as for transfer over the network, only enumerable own properties will be included in the resulting JSON:
const obj = Object.create({ foo: 'foo' });
obj.bar = 'bar';
console.log(JSON.stringify(obj));
Another issue is that class instances usually can't be meaningfully stringified - when parsed on the other side, they'll be interpreted as plain objects, arrays, and values.
If you want to do something like this, you'll have to come up with a way to serialize the instance such that it can be turned into a proper instance on the other side. For a very basic example, if you put all instances into a foos array when sending, you can create a new object with an internal prototype ofFoo.prototype on the other side for every item in the array:
// Sending
class Foo {
_myPrivateVar = null;
get myPublicVar() {
return this._myPrivateVar;
}
set myPublicVar(v) {
if (v > 4) {
this._myPrivateVar = v;
}
}
}
const f1 = new Foo();
f1.myPublicVar = 5;
const f2 = new Foo();
f2.myPublicVar = 1;
console.log(JSON.stringify({ foos: [f1, f2] }));
// Receiving
class Foo {
_myPrivateVar = null;
get myPublicVar() {
return this._myPrivateVar;
}
set myPublicVar(v) {
if (v > 4) {
this._myPrivateVar = v;
}
}
}
const json = `{"foos":[{"_myPrivateVar":5},{"_myPrivateVar":null}]}`;
const { foos } = JSON.parse(json);
const properFoos = foos.map(({ _myPrivateVar }) => {
const foo = Object.create(Foo.prototype);
foo._myPrivateVar = _myPrivateVar;
return foo;
});
console.log(properFoos[0].myPublicVar);
console.log(properFoos[1].myPublicVar);
JavaScript.info
The property name is not placed into User.prototype. Instead, it is created by new before calling the constructor, it’s a property of the object itself.
class User {
name = "Anonymous";
sayHi() {
console.log(`Hello, ${this.name}!`);
}
}
new User().sayHi();
console.log('sayHi:',User.prototype.sayHi); // placed in User.prototype
console.log('name:',User.prototype.name); // undefined, not placed in User.prototype
is there a way to listen for a property call on a JavaScript Class
for example when i go something like this:
myForm = new Form();
myForm.name = 'Name';
-> when i set the name i dont only want to set the property but i also want to update my Vuex store.
Same thing with get i would like to read from Vuex store.
I knoew there are thins like Proxy but for this i need to wrap my Class with a Proxy object. Not so sure if i like this.
module.exports = new Proxy(new Form({}), {
get (receiver, name) {
console.log('getting property from Vuex Store');
}
});
What i need is something like this:
module.exports = class Form {
//this should be triggered when form.something
get(property) {
return this[property];
}
//this should be triggered when from.something = 'something'
set(property, value) {
return this[property] = value;
}
};
it there a best practice for this?
Javascript supports getters and setters
class Form{
set foo(val){
console.log("setting foo")
this.fooValue = val;
}
get foo(){
console.log("getting foo");
return this.fooValue;
}
}
let frm = new Form();
frm.foo = "bar";
console.log(frm.foo);
You could make this more dynamic by writing a withGetterSetter method which wraps each property of an object with a getter/setter.
var form = {
a: "aValue",
b: "bValue"
}
function withGetterSetter(obj){
var keys = Object.keys(obj);
var result = {};
for(var i=0;i<keys.length;i++){
var key = keys[i];
result[key+"_internal"] = obj[key];
(function(k){
Object.defineProperty(result,k, {
get:function() {
console.log("getting property:",k);
return this[k + "_internal"];
},
set: function(x) {
console.log("setting property:",k);
this[k + "_internal"] = x
}
});
})(key)
}
return result;
}
var setterObj = withGetterSetter(form);
console.log(setterObj.a);
setterObj.a = "updated";
console.log(setterObj.a);
It works by copying each property p to a new object with p_internal and creating a dynamic get/set for the original property name.
class Test {
constructor() {
this.childObject = new Child();
}
parentHello() {
console.log("Parent Hello World!");
}
}
class Child {
childHello() {
// How do I call parentHello() from here?
}
}
const obj = new Test();
obj.childObject.childHello();
I'm trying to call the parentHello() from the childHello(). The only way I came up with is to change the structure to circular like this:
class Test {
constructor() {
this.childObject = new Child(this);
}
parentHello() {
console.log("Parent Hello World!");
}
}
class Child {
constructor(parent) {
this.parent = parent;
}
childHello() {
this.parent.parentHello();
}
}
const obj = new Test();
obj.childObject.childHello();
But after doing this I'm no longer able to convert it to JSON. Is there a proper way to do this?
EDIT: I also tried super() but it only works when extending.
Your solution with reference to parent via constructor is ok I think. To allow serialization to JSON, you can use second parameter of JSON.stringify (replacer):
A function that alters the behavior of the stringification process, or an array of String and Number objects that serve as a whitelist for selecting/filtering the properties of the value object to be included in the JSON string. If this value is null or not provided, all properties of the object are included in the resulting JSON string.
class Test {
constructor() {
this.childObject = new Child(this);
}
parentHello() {
console.log("Parent Hello World!");
}
}
class Child {
constructor(parent) {
this.parent = parent;
}
childHello() {
this.parent.parentHello();
}
}
const obj = new Test();
var cache = [];
var json = JSON.stringify(obj, (key, value) => {
if (typeof value === 'object' && value !== null) {
if (cache.includes(value)) {
return;
}
cache.push(value);
}
return value;
});
console.log(json);
Here is the current method I use, where everything is either all public or private. However I would like to differentiate between static and instance. How would I add this in?
obj holds the class or object have you, and config_module determines what type of module this is.
All private, All public ( a collection of statics ), or instance based on a constructor method.
If I do use an instance based, how do I differentiate between statics and instance based properties?
$P.support = $P.parsel = function (obj, config_module) {
$R.Parsel[obj.Name] = obj;
// all properties are private
if (!config_module) {
return undefined;
}
// all properties are public
if (config_module === true) {
return obj;
}
// constructor based, all properties are public
if (config_module === 'constructor') {
var object_public;
if (obj.constructor) {
object_public = obj.constructor;
delete obj.constructor;
}
$A.someKey(obj, function (val, key) {
// like this ?
if (/^s_/.test(key)) {
object_public[key] = val;
// like this ?
} else if (/^p_/.test(key)) {
object_public.prototype[key] = val;
} else {
object_public.prototype[key] = val;
}
});
return object_public;
}
};
You can have (pseudo) static stuff by adding properties to the constructor:
function Something(){}
Something.getStaticFoo = function(){ return 'foo'; }
Something.getStaticFoo();
var instance = new Something();
instance.getStaticFoo(); // error
If I understand your code, that's the same as object_public[key] = val;.