// Another way is to use const serializables = Symbol(); const serializables = new WeakMap(); type Context = | ClassAccessorDecoratorContext | ClassGetterDecoratorContext | ClassFieldDecoratorContext ; function serialize(_target: any, context: Context): void { if (context.static || context.private) { throw new Error("Can only serialize public instance members.") } if (typeof context.name !== "string") { throw new Error("Can only serialize string properties."); } // if Symbol used: const propNames = (context.metadata[serializables] as string[] | undefined) ??= []; let propNames = serializables.get(context.metadata || {}); if (propNames === undefined) { serializables.set(context.metadata || {}, propNames = []); } propNames.push(context.name); } function jsonify(instance: object): string { // @ts-ignore const metadata = instance.constructor[Symbol.metadata]; // if Symbol used: const propNames = metadata?.[serializables] as string[] | undefined; const propNames = metadata && serializables.get(metadata); if (!propNames) { throw new Error("No members marked with @serialize."); } const pairStrings = propNames.map((key: string) => { const strKey = JSON.stringify(key); const strValue = JSON.stringify((instance as any)[key]); return `${strKey}: ${strValue}`; }); return `{ ${pairStrings.join(", ")} }`; } class Person { firstName: string; lastName: string; @serialize age: number @serialize get fullName() { return `${this.firstName} ${this.lastName}`; } toJSON() { return jsonify(this) } constructor(firstName: string, lastName: string, age: number) { this.firstName = firstName this.lastName = lastName this.age = age } } const person = new Person('Alice', 'Sofia', 4) console.log(person.toJSON())