3

I have the following class structure:

export abstract class PersonBase {
    public toJSON(): string {
        let obj = Object.assign(this);
        let keys = Object.keys(this.constructor.prototype);
        obj.toJSON = undefined;
        return JSON.stringify(obj, keys);
    }
}

export class Person extends PersonBase {

    private readonly _firstName: string;
    private readonly _lastName: string;

    public constructor(firstName: string, lastName: string) {
        this._firstName = firstName;
        this._lastName = lastName;
    }

    public get first_name(): string {
        return this._firstName;
    }

    public get last_name(): string {
        return this._lastName;
    }
}

export class DetailPerson extends Person {

    private _address: string;

    public constructor(firstName: string, lastName: string) {
        super(firstName, lastName);
    }

    public get address(): string {
        return this._address;
    }
     
    public set address(addy: string) {
        this._address = addy;
    }
}

I am trying to get toJSON to output all the getters (excluding private properties) from the full object hierarchy.

So if I have a DetailPerson instance and I call the toJSON method, I want to see the following output:

{
   "address": "Some Address",
   "first_name": "My first name",
   "last_name": "My last name"
}

I used one of the solutions from this Q&A but it doesn't solve my particular use case - I am not getting all the getters in the output.

What do I need to change here to get the result I am looking for?

5
  • Have you defined the method toJsonString on both the Person and DetailPerson classes, as shown in the post you linked? I don't see them defined in the code you posted. Commented Jun 1, 2017 at 19:21
  • Please provide a minimal reproducible example showing the exact issue you are trying to solve. Commented Jun 1, 2017 at 19:27
  • Amendments made. Commented Jun 1, 2017 at 20:03
  • @Daz Maybe you'd like to edit this question again? As far as I can tell, the toJSON method added to PersonBase doesn't return the getters from DetailPerson. Also, the previous phrasing of the question made it more clear that you want to omit "private" properties (i.e. you only want getters). Commented Jun 2, 2017 at 4:06
  • Additional edits made. Commented Jun 2, 2017 at 18:39

1 Answer 1

5

The link you provided uses Object.keys which leaves out properties on the prototype.

You could use for...in instead of Object.keys:

public toJSON(): string {
    let obj: any = {};

    for (let key in this) {
        if (key[0] !== '_') {
            obj[key] = this[key];
        }
    }

    return JSON.stringify(obj);
}

Edit: This is my attempt to return only getters, recursively, without assuming that non-getters start with underscores. I'm sure there are gotchas I missed (circular references, issues with certain types), but it's a good start:

abstract class PersonBase {
  public toJSON(): string {
    return JSON.stringify(this._onlyGetters(this));
  }

  private _onlyGetters(obj: any): any {
    // Gotchas: types for which typeof returns "object"
    if (obj === null || obj instanceof Array || obj instanceof Date) {
      return obj;
    }

    let onlyGetters: any = {};

    // Iterate over each property for this object and its prototypes. We'll get each
    // property only once regardless of how many times it exists on parent prototypes.
    for (let key in obj) {
      let proto = obj;

      // Check getOwnPropertyDescriptor to see if the property is a getter. It will only
      // return the descriptor for properties on this object (not prototypes), so we have
      // to walk the prototype chain.
      while (proto) {
        let descriptor = Object.getOwnPropertyDescriptor(proto, key);

        if (descriptor && descriptor.get) {
          // Access the getter on the original object (not proto), because while the getter
          // may be defined on proto, we want the property it gets to be the one from the
          // lowest level
          let val = obj[key];

          if (typeof val === 'object') {
            onlyGetters[key] = this._onlyGetters(val);
          } else {
            onlyGetters[key] = val;
          }

          proto = null;
        } else {
          proto = Object.getPrototypeOf(proto);
        }
      }
    }

    return onlyGetters;
  }
}
Sign up to request clarification or add additional context in comments.

11 Comments

Thank you... ideally i was hoping for a solution that was more generic, something that didn't make assumptions about how i name private properties vs getters\setters. In the case my private property is named firstName and the getter\setter is First_Name or any other variant this won't work. I should have been clearer with my question, apologies.
I edited my response. Let me know if that works for you.
What would it take to modify this so that it would correctly serialize a get property that was in-fact another object I had defined? In the example above say the DetailPerson has a Pet object property with its own set of get\set properties?
@Daz I made an update, but can't test at the moment. Can you tell me if it works?
Unfortunately, both return just { } in my case, i.e. an empty JSON object.
|

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.