Typescript: Expose getters to JSON.stringify by generating a toJSON method

Created on 30 Jun 2017  路  5Comments  路  Source: microsoft/TypeScript

TypeScript Version: All

Problem

Currently getters are not called when an initialized Typescript object is serialized into JSON. While it is logical runtime behaviour of javascript, it feels not like the desired behaviour from a 'Typescript point of view', the transpiled Class behaves not as the getter implies, to be a public property of the class.

export class TestClass
{
    private _value:string = "value";

    get value():string
    {
        return this._value;
    }
}

let obj = new TestClass();
console.log(JSON.stringify(obj)); //{"_value":"value"}

The outputted JSON contains the private value property: {"_value":"value"}, instead of an evaluated getter. {"value":"value"}

In order to fix this a toJSON method could be implemented:

export class TestClass
{
    private _value:string = "value";

    get value():string
    {
        return this._value;
    }

    public toJSON()
    {
        return {
            value: this.value
        };
    }
}

let obj = new TestClass();
console.log(JSON.stringify(obj)); //{"value":"value"}

Suggestion

It is my suggestion that Typescript would automatically add a toJSON method on transpilation, if the following conditions apply:

  • There are getters present in a class
  • There is not already a toJSON method implemented in the class or its parent classes.

The generated toJSON method could expose (optionally only) all public properties and getters.

Out of Scope

Most helpful comment

I handle it at runtime by using a general toJSON() function like this:

toJSON() {
  const proto = Object.getPrototypeOf(this);
  const jsonObj: any = Object.assign({}, this);

  Object.entries(Object.getOwnPropertyDescriptors(proto))
    .filter(([key, descriptor]) => typeof descriptor.get === 'function')
    .map(([key, descriptor]) => {
      if (descriptor && key[0] !== '_') {
        try {
          const val = (this as any)[key];
          jsonObj[key] = val;
        } catch (error) {
          console.error(`Error calling getter ${key}`, error);
        }
      }
    });

  return jsonObj;
}

All 5 comments

What about those that don't want their getters called when serializing?

I realise that It could mean a BC-break in terms of exposing more data when serializing to JSON than in the current situation. In my opinion a getter is part of the public API of a class and in most situations it is a miss that a getter is not exposed.

If data should not be exposed, it should not be public.

TypeScript is JavaScript with types. There's no such thing as a "Typescript object", just a JavaScript object. See the design goals.

If data should not be exposed, it should not be public.

Being exposed and being in a situation where it is meaningful when serialized are two different things. Especially if there is not setter, therefore it is a read only property... Serializing in semantically meaningful and changing the default runtime behaviour of JavaScript for limited use cases... 馃槩

I handle it at runtime by using a general toJSON() function like this:

toJSON() {
  const proto = Object.getPrototypeOf(this);
  const jsonObj: any = Object.assign({}, this);

  Object.entries(Object.getOwnPropertyDescriptors(proto))
    .filter(([key, descriptor]) => typeof descriptor.get === 'function')
    .map(([key, descriptor]) => {
      if (descriptor && key[0] !== '_') {
        try {
          const val = (this as any)[key];
          jsonObj[key] = val;
        } catch (error) {
          console.error(`Error calling getter ${key}`, error);
        }
      }
    });

  return jsonObj;
}
Was this page helpful?
0 / 5 - 0 ratings

Related issues

kyasbal-1994 picture kyasbal-1994  路  3Comments

wmaurer picture wmaurer  路  3Comments

manekinekko picture manekinekko  路  3Comments

seanzer picture seanzer  路  3Comments

blendsdk picture blendsdk  路  3Comments