20

I'm using TypeScript to define some classes and when I create a property, it generates the equivalent to Class1 in the following plunkr:

http://plnkr.co/edit/NXUo7zjJZaUuyv54TD9i?p=preview

var Class1 = function () {
  this._name = "test1";
}

Object.defineProperty(Class1.prototype, "Name", {
  get: function() { return this._name; },
  set: function(value) { this._name = value; },
  enumerable: true
});

JSON.stringify(new Class1()); // Will be "{"_name":"test1"}"

When serializing, it doesn't output the property I just defined.

instance2 and instance3 behave as I'd expect by serializing the defined property. (see the plunkr output).

My actual question is: Is this normal?

If so, how do I work around it in the most efficient way?

5 Answers 5

25

You can define a toJSON() method on your prototype to customize how instances are serialized.

Class1.prototype.toJSON = function () {
    return {
        Name: this.Name
    };
};
JSON.stringify(new Class1()); // Will be '{"Name":"test1"}'
Sign up to request clarification or add additional context in comments.

1 Comment

I'm marking this as the answer because I ended up implementing toJSON myself. I already had a map of private members to public properties because I was using it to map some JSON from my API to my TypeScript classes. In the toJson methods, I simply use that map to create a new object with all properties with the inverted names. I'll blog about it soon.
17

If you like to push it forward, give a try to decorators proposal for TypeScript:

According to this proposal (https://github.com/wycats/javascript-decorators):

A decorator is:

  • an expression
  • that evaluates to a function
  • that takes the target, name, and property descriptor as arguments
  • and optionally returns a property descriptor to install on the target object

It goes like this (high level view):

Client code:

@serializable()
class Person {

    constructor(name: string) {
      this._name = name;
    }

    private _name: string;

    @serialize()
    get name() {
      return this._name;
    }

    @serialize('Language')
    get lang() {
      return 'JavaScript';
    }
}

Infrastructure:

const serialized = new WeakMap();

export function serializable(name?: string) {
    return function (target, propertyKey, descriptor) {
        target.prototype.toJSON = function () {
            const map = serialized.get(target.prototype);
            const props = Object.keys(map);
            return props.reduce((previous, key) => {
                previous[map[key]] = this[key];
                return previous;
            }, {});
        }

    }
}

export function serialize(name?: string) {
    return function (target, propertyKey, descriptor) {
        let map = serialized.get(target);
        if (!map) {
            map = {};
            serialized.set(target, map);
        }

        map[propertyKey] = name || propertyKey;
    }
}

UPDATE: I extracted this sample decorator into a repository at https://github.com/awerlang/es-decorators

9 Comments

Very interesting! But not a viable solution in the short term for this project. Thanks!
@Jacks perhaps you were retrieving using a class as key, instead of a class' prototype? I updated my answer with a working implementation, so take a look.
@AndréWerlang Thank you very much for your update. Yes, I was indeed in the first trying to retrieve values by passing the class and not its prototype. But for the verification, I was trying to print the whole WeakMap which was not possible because keys where not enumerable (see this). Thanks for clarifying this post years after :)
How exactly did you do that @TheRedPea? Care to share a gist?
@Apidcloud , something like this (note I removed chunks, this code won't run, but I added arrows to show the use of ClassDecorator and PropertyDecorator gist.github.com/theredpea/c4e6c7923467db57b6afd867a0e1ecc1
|
10

Yes, it is by design.

As defined in ECMA, only own enumerable properties are serialized (stringify() is defined in terms of Object.keys(), among others).

Property accessors are defined on prototypes, both in TypeScript and ES6.

And answering your last question, that is the most eficient way to perform this operation.

Beside that, only a) defining an toJSON() to each object part of the serialization, or b) passing a replacer function/array as 2nd argument to JSON.stringify().

Whitelist properties from prototype:

JSON.stringify(instance, Object.keys(instance.constructor.prototype))

1 Comment

I went with the toJSON approach. I'll blog about it soon.
2

As you've discovered, it won't serialize properties defined on prototype.

I know it's not ideal, but another option is to do this:

class Class1 {
    private _name = "test1";

    Name: string; // do this to make the compiler happy

    constructor() {
        Object.defineProperty(this, "Name", {
            get: function() { return this._name; },
            set: function(value) { this._name = value; },
            enumerable: true
        });
    }
}

Defining the property on the instance will serialize the property.

2 Comments

I had considered this, but I want a solution that uses TypeScript itself.
@ChristianDroulers yeah, I wouldn't use this solution either :)... adding a toJSON method is a better option.
2

Put something here hopefully can help others. What I have do for fix JSON.stringify not serialize properties defined on prototype

var newObject = $.extend(false, {}, orginalObj);

then I notice newObject have instance properties instead of prototype properties. I'm use typescript and get accessor.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.