6

As a follow-up question to this: TypeScript override ToString()

Let's say we have a Person class and we're overriding toString like this:

class Person {
    constructor(
        public firstName: string,
        public lastName: string
    ) {}

    public toString(): string {
       return this.firstName + ' ' + this.lastName;
    }
} 

So, as mentioned in the original question, this works fine when doing console.log("" + myPerson);, however when examining an instance of Person in a debugger the toString seems to be ignored and the default display is shown. I tested this both against Google Chrome's debugger (hovering over a variable) and against VS Code's debugger (both hovering over a variable, and looking in the "locals" tab).

Is there any way to make one or both debuggers respect the toString override?

Note: It doesn't matter if I use the lambda version of toString, the results are the same.

wonea
  • 4,783
  • 17
  • 86
  • 139
tzachs
  • 4,331
  • 1
  • 28
  • 28
  • @wonea, please have a look at answer I posted. Not an exact solution but sharing my findings from the last attempt to get something similar working – Tarun Lalwani Jun 13 '18 at 05:29
  • For my needs I am taking advantage of the fact that objects in Chrome's DevTools when abbreviated (hover or collapsed in console) have the properties listed in the order they're found in code (or superclass if you have inheritance). This means putting the critical information (ie the "toString" type of information) as the first property defined in the class - this way you choose what you see. – DAG Mar 10 '20 at 19:55

4 Answers4

2

So your question is almost similar to below

How to change string representation of objects in Nodejs debug console view

The toString behaviour is not there as a part of normal v8 engine debugger API.

I had dived in deep for the link I posted earlier and found few interesting pointers

  • Compiling NodeJS from source and watching the debugger traffic, I realised there is a customFormatter concept

    setCustomObjectFormatterEnabled: function(enabled)

  • I was able to change some stuff in this JavaScript and send the evaluated toString values back with the v8 API call

  • The default VS code needs to be modified to change the behaviour and show a different text.

  • I never got a clean way to actually see what can be done and how to formulate a proper solution, so left it there itself.

So in short it is possible, but requires a decent effort to get it working

Tarun Lalwani
  • 142,312
  • 9
  • 204
  • 265
1

This might help: v1.69 release notes: toString() variable previews.

If a variable or property has a custom toString() method, it will be called to render the preview in the VARIABLES view and hovers. This is an improvement over generic object previews for complex types that have good string representations.

For example, VS Code's Range has a toString method that makes it easier to digest at a glance:

demo of debug variables view showing the toString() call

Mark
  • 143,421
  • 24
  • 428
  • 436
0

Can you provide an example what what you actually want to see at which place in the debugger?

If the question is about the variable scope (ie. Local and global variables available), the debugger will not call toString() as it would actually execute code within the debugged script which in turn may modify the state. Imagine an endless loop in toString().

  • An example from c# world: https://blogs.msdn.microsoft.com/soultech/2011/04/05/why-override-tostring-use-debuggerdisplayattribute-instead/, compare the first "quick watch" image with the second one. – tzachs Apr 05 '18 at 02:34
  • 2
    And as for your second comment, modifying state or doing endless loop in toString() is a terrible thing to do. In c# + VS world, the debugger is executing the toString and has been doing it for years (I imagine it does have some timeout in case the toString is taking too long though). – tzachs Apr 05 '18 at 02:36
0

I found out how to do it in Chrome DevTools (still don't have a solution for VSCode):

custom string in dev tools

For this to work we need to enable custom formatters in dev tools:

  • Open dev tools
  • Click on the hamburger menu (the 3 vertical dots) on the top right
  • Click on "settings"
  • Under "Console", make sure "Enable custom formatters" is checked.

With this done you can create a custom formatter in the code and then assign it to window["devtoolsFormatters"].

The full code for my example is here:

class Person {
    constructor(firstName, lastName) {
        this.firstName = firstName;
        this.lastName = lastName;
    }

    toString() {
        return `${this.firstName} ${this.lastName}`;
    }
}

Formatter = function (simpleFormatter) {
    this._simpleFormatter = simpleFormatter;
}

Formatter.prototype = {

    header: function (object) {
        if ((object instanceof Node))
            return null;

        var header = new JsonMLElement("span");
        let title = this._simpleFormatter.description(object);
        if (typeof object.toString !== "undefined") {
            title = `${object.toString()} [${this._simpleFormatter.description(object)}]`;
        }
        header.createTextChild(title);
        return header.toJsonML();
    },

    hasBody: function (object) {
        if (object instanceof Array)
            return false;
        return this._simpleFormatter.hasChildren(object);
    },

    body: function (object) {
        var body = new JsonMLElement("ol");
        body.setStyle("list-style-type:none; padding-left: 0px; margin-top: 0px; margin-bottom: 0px; margin-left: 12px");
        var children = this._simpleFormatter.children(object);
        for (var i = 0; i < children.length; ++i) {
            var child = children[i];
            var li = body.createChild("li");
            var objectTag;
            if (typeof child.value === "object")
                objectTag = li.createObjectTag(child.value);
            else
                objectTag = li.createChild("span");

            var nameSpan = objectTag.createChild("span");
            nameSpan.createTextChild(child.key + ": ");
            nameSpan.setStyle("color: rgb(136, 19, 145);");
            if (child.value instanceof Node) {
                var node = child.value;
                objectTag.createTextChild(node.nodeName.toLowerCase());
                if (node.id)
                    objectTag.createTextChild("#" + node.id)
                else
                    objectTag.createTextChild("." + node.className)
            }
            if (typeof child.value !== "object")
                objectTag.createTextChild("" + child.value);

        }
        return body.toJsonML();
    },

    _arrayFormatter: function (array) {
        var j = new JsonMLElement();
        j.createTextChild("[");
        for (var i = 0; i < array.length; ++i) {
            if (i != 0)
                j.createTextChild(", ")
            j.createObjectTag(array[i]);
        }
        j.createTextChild("]");
        return j;
    }
}

JsonMLElement = function (tagName) {
    this._attributes = {};
    this._jsonML = [tagName, this._attributes];
}


JsonMLElement.prototype = {

    createChild: function (tagName) {
        var c = new JsonMLElement(tagName);
        this._jsonML.push(c.toJsonML());
        return c;
    },

    createObjectTag: function (object) {
        var tag = this.createChild("object");
        tag.addAttribute("object", object);
        return tag;
    },

    setStyle: function (style) {
        this._attributes["style"] = style;
    },

    addAttribute: function (key, value) {
        this._attributes[key] = value;
    },

    createTextChild: function (text) {
        this._jsonML.push(text + "");
    },

    toJsonML: function () {
        return this._jsonML;
    }
}

function SimpleFormatter() {

}

SimpleFormatter.prototype = {

    description: function (object) {
        if ((typeof object === "object") && object)
            return object.constructor.name;
        return object;
    },

    hasChildren: function (object) {
        return (typeof object === "object");
    },

    children: function (object) {
        var result = [];
        for (var key in object)
            result.push({ key: key, value: object[key] });
        return result;
    }
}

window["devtoolsFormatters"] = [new Formatter(new SimpleFormatter())];

const p = new Person("Luke", "Skywalker")
console.log(p)

More details can be found in this link (my code is just a slightly modified version of one of the examples posted in that article).

tzachs
  • 4,331
  • 1
  • 28
  • 28