2

Let's say I have this class.

class Attribute {
  constructor(name) {
    this.name = name;
  }
}

And I create an instance and log it to the console for debugging purposes.

const test = new Attribute('Large');
console.log(test);

How can I get it to output a specially formatted string, like {Attribute} Large? I'm primarily concerned with Chrome support, but Node and other browsers would be nice, too.

  • 1
    override Attribute.prototype.toString – Jonas Wilms Apr 14 '17 at 17:42
  • 3
    @Jonasw Doesn't really work unless you do `console.log(test.toString())`. – Derek 朕會功夫 Apr 14 '17 at 17:43
  • 1
    Probably use a custom logging function. For NodeJS, I would recommend using `util.format` to format the `process.stdout` to create a custom logger. You can override `console.log` too, but it is not a good way to deal with stuff. – Ozil Apr 14 '17 at 17:50
  • Does this answer your question? [Is it possible to override JavaScript's toString() function to provide meaningful output for debugging?](https://stackoverflow.com/questions/6307514/is-it-possible-to-override-javascripts-tostring-function-to-provide-meaningfu) – ggorlen Feb 04 '22 at 01:47

3 Answers3

8

A sane solution would probably be to use a custom logging function that stringifies Attribute values.

However, if you have a vendetta against people who need to maintain your code in the future, one solution that meets the technical requirements of this question is to have your class extend a type that console.log automatically serializes to string, like RegExp. When you console.log a RegExp instance, Chrome (and probably other environments) automatically serializes it to its bare /.../ expression. Simply overwrite what string it should serialize to by supplying a custom toString function, and you've got exactly what you asked for.

class Attribute extends RegExp {
  constructor(name) {
    super();
    this.name = name;
  }
  toString() {
    return this.name
  }
}
var test = new Attribute('Large');
console.log(test);

This is roughly equivalent to answering the question "How do I stop my house from flooding?" with "Put your house on a giant raft" instead of "Put some caulk in your basement" or "Move somewhere else." Some side effects will include:

  • Your objects will inherit regular expression properties like global and exec

  • Testing if your object is instanceof RegExp will of course return true, which might cause your object to be valid inputs for routines that only expect to operate on regular expression objects

Using further dark magic, you can solve the these issues by doing

Object.setPrototypeOf(Attribute.prototype, Object.prototype);

immediately after your class definition, which will ensure that your this object will simply be run through the RexExp constructor (thereby flagging it for stringified log output) but not inherit from RexExp.prototype. By mixing class and prototype syntax together, you also ensure that your code is confusing and everyone is actively afraid of it.

apsillers
  • 112,806
  • 17
  • 235
  • 239
  • This answer is technically correct -- the best kind of correct. –  Apr 14 '17 at 18:09
  • 1
    Are there magic properties that Chrome refers to when deciding whether to cast the object to string or not? – Derek 朕會功夫 Apr 14 '17 at 18:12
  • @Derek朕會功夫 As far as I know, there is no way to indicate (e.g., by overloading some property in JavaScript) that `console.log` should stringify its output. Chrome seems to have a fixed set of conditions that trigger this (e.g., is the value being logged a primitive? is it a `RegExp`?) – apsillers Apr 14 '17 at 18:17
  • I guess another way of doing it without extending from `RegExp` is to modify `console.log` (https://jsfiddle.net/DerekL/dLf8rq52/). – Derek 朕會功夫 Apr 14 '17 at 18:23
  • Thanks for being the only person to actually answer the question. It is an ugly hack, but it's really helpful whenever you need to do some quick debugging and can't change the logging code. Unfortunately, while it does work in Chrome and Firefox, it doesn't work in Node, so I guess I'm still out of luck. – Antimony Oct 22 '17 at 04:15
5

apsiller's answer works great in Chrome and Firefox, but unfortunately, it doesn't work in Node. However, after some experimentation and research, I found a solution that does work in Node.

It turns out that Node's implementation of console.log has a built-in customization point, util.inspect.custom, so all you have to do is define a method with that symbol that returns whatever you want to print.

class Attribute {
  constructor(name) {
    this.name = name;
  }
  [require('util').inspect.custom](){
    return `{Attribute} ${this.name}`;
  }
}
Antimony
  • 37,781
  • 10
  • 100
  • 107
  • 1
    Wow, I'm actually really happy you found a way to port my nightmare of an answer to Node as well. (And I'm happy that Node seems to have a more sensible way of doing this than inheriting from `RegExp`!) Thanks for your excellent research! – apsillers May 14 '19 at 20:07
3
class Attribute {
  constructor(name) {
    this.name = name;
  }
  toString(){//simply set the to String method?
    return "{Attribute} "+this.name;
  }
}

As console.log does not call to String you either do:

const test = new Attribute('Large');
console.log(test+"");

or you create your own logging function

Jonas Wilms
  • 132,000
  • 20
  • 149
  • 151