8

The Class patterns i've seen around pretty much comes to something like this:

class Foo {
    constructor(x, y, z) {
      this._x = x;
      this._y = y;
      this._z = z;
    }

    get x() {
      return this._x;
    }
    set x(value) {
      //I acctually do some stuff here
      this._x = value;
    }

    get y() {
      return this._y;
    }
    set y(value) {
      //I acctually do some stuff here
      this._y = value;
    }

    get z() {
      return this._z;
    }
    set z(value) {
      //I acctually do some stuff here
      this._z = value;
    }
}

console.log(new Foo('x', 'y', 'z')) execution output:

Foo { _x: 'x', _y: 'y', _z: 'z' }

console.log(JSON.stringify(new Foo('x', 'y', 'z'))) execution output:

{"_x":"x","_y":"y","_z":"z"}

Which gives me underscore prefixed fields, and i wasnt aiming for that, how can i get the fields to have no underscore prefix, and yet, having getters and setters triggered by instance.prop interaction.

vcorrea
  • 101
  • 2
  • 8
  • I would say to remove the **_** from behind the variables? like `constructor(x, y, z) { this.x = x; this.y = y; this.z = z; }` – Orelsanpls Nov 03 '16 at 17:14
  • 1
    Yeah, in this example there is zero advantage over just assigning the properties directly and skipping the getter. – loganfsmyth Nov 03 '16 at 17:52
  • @loganfsmyth Sorry for the poor example, but im acctually using custom setter for properties in my `real world application`, ill edit my snippet for better comprehension. – vcorrea Nov 03 '16 at 18:42
  • What are you doing with this JSON data after? I'd say generally you shouldn't rely on JSON auto-serialization of a class, if something is a class, you should have a method, either explicitly or via `.toJSON` for getting a serializable object version of it. – loganfsmyth Nov 03 '16 at 18:56
  • @loganfsmyth for now, im just persisting the data on mongodb with the native driver (which is saving the properties with underscore), and returning it in exposed JSON API (in this case, i use `.toJSON` only for classes i want to ommit fields i dont want to end up in front-ends, like passwords). – vcorrea Nov 03 '16 at 19:04

4 Answers4

15

You can add a toJSON method to adjust the output of JSON.stringify

class Foo {
    constructor(x, y, z) {
      this._x = x;
      this._y = y;
      this._z = z;
    }

    get x() {
      return this._x;
    }
    set x(value) {
      this._x = value;
    }

    get y() {
      return this._y;
    }
    set y(value) {
      this._y = value;
    }

    get z() {
      return this._z;
    }
    set z(value) {
      this._z = value;
    }

    toJSON() {
      return {
        x: this._x,
        y: this._y,
        z: this._z
      };
    }
}

var foo = new Foo('x', 'y', 'z');
console.log(JSON.stringify(foo));

outputs: "{"x":"x","y":"y","z":"z"}"

generalhenry
  • 17,227
  • 4
  • 48
  • 63
  • I saw this one in a few examples, but i would have to use it in every usage of my classes, so im avoiding it, but thanks for the reference! – vcorrea Nov 03 '16 at 18:50
5

If your problem really is only the underscores, then you could try using a naming convention more similar to C#'s properties where the get/set methods are using PascalCase but the member variables use camelCase, like so:

class Foo {
    constructor(x, y, z) {
      this.x = x;
      this.y = y;
      this.z = z;
    }

    get X() {
      return this.x;
    }
    set X(value) {
      this.x = value;
    }

    get Y() {
      return this.y;
    }
    set Y(value) {
      this.y = value;
    }

    get Z() {
      return this.z;
    }
    set Z(value) {
      this.z = value;
    }
}

Ultimately, due to how objects work in ECMAScript 6 there is no way to have both the member variable and get/set methods named 100% the same. In point of fact, this is why using the underscore format is so common. The underscore tells anyone looking at the code that the property is intended to be "private". In ECMAScript 6, the concept of private members doesn't really exist.

NemesisX00
  • 2,046
  • 2
  • 13
  • 12
  • 1
    Altough your syntax may not be the one im going for, your answer is of great importance, for this `Ultimately, due to how objects work in ECMAScript 6 there is no way to have both the member variable and get/set methods named 100% the same.` which i was trying so hard, now i can be relieved and move on, with one of the many ways you guys presentes here. Thanks! – vcorrea Nov 03 '16 at 18:58
2

If you want to skip the underscore properties, define them as non-enumerable:

class Foo {
  constructor(x, y, z) {
    this._x = x;
    this._y = y;
    this._z = z;
    Object.defineProperties(this, {
      _x: {enumerable: false},
      _y: {enumerable: false},
      _z: {enumerable: false}
    });
  }
  get x() { return this._x; }
  set x(value) { this._x = value; }
  get y() { return this._y; }
  set y(value) { this._y = value; }
  get z() { return this._z; }
  set z(value) { this._z = value; }
}
console.log(JSON.stringify(new Foo('x', 'y', 'z')))

You can also consider symbols instead of underscored properties:

class Foo {
  constructor(x, y, z) {
    this[Foo.x] = x;
    this[Foo.y] = y;
    this[Foo.z] = z;
  }
  get x() { return this[Foo.x];  }
  set x(value) { this[Foo.x] = value; }
  get y() { return this[Foo.y]; }
  set y(value) { this[Foo.y] = value; }
  get z() { return this[Foo.z]; }
  set z(value) { this[Foo.z] = value; }
}
Foo.x = Symbol('x');
Foo.y = Symbol('y');
Foo.z = Symbol('z');
console.log(JSON.stringify(new Foo('x', 'y', 'z')))
Oriol
  • 274,082
  • 63
  • 437
  • 513
0

As you said you wanted to avoid using toJSON in every class (but I also think using toJSON is the "right" thing to do).

Javascript let you do weird things, but at least you can control it in an enclosed function scope.

I guess the regex could be refined but I just wanted to show the idea, not pretty but should work.

class Foo {
  constructor(x, y, z) {
    this._x = x;
    this._y = y;
    this._z = z;
  }

  get x() {
    return this._x;
  }
  set x(value) {
    //I acctually do some stuff here
    this._x = value;
  }

  get y() {
    return this._y;
  }
  set y(value) {
    //I acctually do some stuff here
    this._y = value;
  }

  get z() {
    return this._z;
  }
  set z(value) {
    //I acctually do some stuff here
    this._z = value;
  }
}

var originalJSON = JSON;

var foo = new Foo('x', 'y', 'z');

(function () {

  var JSON = {
    stringify: function (obj) {
      var json = originalJSON.stringify(obj);
      return json.replace(/"_+(\w+)":/g, '"$1":');
    },
    parse: function(str) {
      return originalJSON.parse(str.replace(/"(\w+)":/g, '"_$1":'));
    }
  };

  console.log('Weird hack');

  var r = JSON.stringify(foo);    
  console.log('stringify');
  console.log(r);

  console.log('parse');
  console.log(JSON.parse(r));
}).call();

console.log('\nBack to normal');

var r = JSON.stringify(foo);
console.log('stringify');
console.log(r);

console.log('parse');
console.log(JSON.parse(r));

Output:

Weird hack
stringify
{"x":"x","y":"y","z":"z"}
parse
{ _x: 'x', _y: 'y', _z: 'z' }
Back to normal
stringify
{"_x":"x","_y":"y","_z":"z"}
parse
{ _x: 'x', _y: 'y', _z: 'z' }
Marcs
  • 3,768
  • 5
  • 33
  • 42