Skip to content

How do I implement MyObject.inspect(depth, inspectOpts)? #8442

Description

@Havvy

I want to implement inspect for my own object. I want to mostly follow the semantics of inspect for non-custom implementations, but just by going by the documentation alone, I have no possible way of doing so. What conventions are there? How do I recursively call inspect while respecting depth? Do I need to handle the case of depth being null?

The example ignores the parameters passed to it completely currently. Maybe if we had an example of a Box object that inspects as "Box<InnerInspectValue>" so e.g. inspect({value: "a", inspect: ... }) returns "Box<'a'>" but the value can be anything (including over objects with custom inspect impls).

Activity

  1. added
    questionIssues asking questions about Node.js.
    utilIssues and PRs related to the built-in util module.
    on Sep 8, 2016
  2. Havvy commented on Sep 8, 2016

    @Havvy
    ContributorAuthor

    cc @addaleax since you've been doing some work w.r.t. util.inspect recently.

  3. addaleax commented on Sep 8, 2016

    @addaleax
    Member

    Hi @Havvy – I agree, the examples don’t really go into that aspect. I probably have time to look into that later, but it would be really cool if you could start out with basically showing a full example with the output you’d like to see (and leave out the things you’re unsure about)?

  4. Havvy commented on Sep 9, 2016

    @Havvy
    ContributorAuthor

    Here's an initial attempt. Feel free to warp completely. I'm not all that good at documentation. I'm assuming that the inspect will never be called such that you need to handle not returning anything. The main goal here is to tell me the main conventions and logic the I should be thinking about when implementing things, especially recursive things. I went with an example of a Box because it's the simplest thing I could think of that embodied all of the major issues you should run into when implementing this function.


    When implementing inspect on your object, to properly respect the recursion limit, you must use the depth value properly. When you want to include the inspected value of another object, call util.inspect with your inspect options but change the depth to ___ unless depth is null.

    If you want to support custom colors (and no colors), you can style your text by doing ....

    To support the breakLength value, if the size of your returned string is greater than the breakLength value, then you should break the return value into multiple lines. This is best done by...1. You don't need to modify breakLength when passing it to your object.

    If your object is an arraylike, remember to check the maxArrayLength value.

    And to combine all of these, we look at implementing inspect on a Box object. We want the box object to display its "value" property surrounded by "Box<>"2. For example, a box of an empty object would display as "Box<{}>" while a box of a longer object would show up like the following:

    Box<
      {
        foo: "A long string."
      }
    >
    

    The implementation of this would look like:

    function Box(value) {
        this.value = value;
    }
    
    Box.prototype[util.inspect.custom] = function (depth, inspectArgs) {
         // impl goes here with comments explaining nuances.
    }

    Footnotes:

    1. I can't actually think of a good general solution here.
    2. This should be colored cyan (a.k.a. special) in the docs.
  5. added
    good first issueIssues that are suitable for first-time contributors.
    on Sep 9, 2016
  6. addaleax commented on Sep 9, 2016

    @addaleax
    Member

    Hmm. How close would this example get to what you want to show?

    const util = require('util');
    
    class Box {
      constructor(value) {
        this.value = value;
      }
    
      [util.inspect.custom](depth, options) {
        const newOptions = Object.assign({}, options, {
          depth: options.depth - depth
        });
    
        return options.stylize('Box', 'special') + '<' +
            util.inspect(this.value, newOptions) + '>';
      }
    }
    
    util.inspect.defaultOptions.colors = true;
    
    console.log(new Box({}))
    console.log(new Box({
      aaaaaaaaaaaaaaaaa: 1,
      aaaaaaaaaaaaaaaab: 2,
      aaaaaaaaaaaaaaaae: 3
    }))
    console.log(new Box({a:{b:{c:{d:{}}}}}));

    Output:

    Box<{}>
    Box<{ aaaaaaaaaaaaaaaaa: 1,
      aaaaaaaaaaaaaaaab: 2,
      aaaaaaaaaaaaaaaae: 3 }>
    Box<{ a: [Object] }>

    I agree, there are some parts, like checking maxArrayLength and friends, that are not that easy to implement, but your suggestions sounds pretty solid to me.

  7. Havvy commented on Sep 10, 2016

    @Havvy
    ContributorAuthor

    It's close.

    It doesn't seem to be respecting depth properly.

    const box = new Box({x: 5});
    console.log(util.inspect(box));
    console.log(util.inspect({wrap: box}));
    console.log(util.inspect({wrap: {wrap: box} });
    console.log(util.inspect({wrap: {wrap: {wrap: box} } }));
    console.log(util.inspect({wrap: {wrap: {wrap: {wrap: box} } } }));
    
    // Expected Outputs
    // Box<{ x: 5 }>
    // { wrap: Box<{ x: 5 }> }
    // { wrap: { wrap: { Box<[Object]> } }
    // { wrap: { wrap: { wrap: [Box] } } }
    // { wrap: { wrap: { wrap: [Object] } } }
    
    // Actual Outputs
    // Box<{ x: 5 }>
    // { wrap: Box<{ x: 5 }> }
    // { wrap: { wrap: Box<{ x: 5 }> } }
    // { wrap: { wrap: { wrap: Box<{ x: 5 }> } } }
    // { wrap: { wrap: { wrap: [Object] } } }

    Experimenting with util.inspect, it shows the short form (e.g. "[Object]") if the depth is less than zero.

    This line depth: options.depth - depth actually sets the depth to 0, and of course, you're not checking depth to see if you should not recurse downwards or not. As such, let box = new Box(5); for (var ix = 0; ix < 10; ix += 1) { box = new Box(box); } util.inspect(box) will show all 10 or so levels of boxing instead of stopping earlier. Also, null - 1 evaluates to -1, so we need to specifically check for that. (Why was null chosen as the value instead of Infinity?)

    So, taking those into consideration, here's a better version:

    class Box {
      constructor(value) {
        this.value = value;
      }
    
      inspect(depth, options) {
        if (depth < 0) {
          return options.stylize('[Box]', 'special');
        }
    
        const newOptions = Object.assign({}, options, {
          depth: options.depth === null ? null : options.depth - 1
        });
    
        return options.stylize('Box', 'special') + '<' +
            util.inspect(this.value, newOptions) + '>';
      }
    }
  8. Havvy commented on Sep 28, 2016

    @Havvy
    ContributorAuthor

    And here's code dealing with whitespace. (I haven't actually tested this code yet)

    class Box {
      constructor(value) {
        this.value = value;
      }
    
      inspect(depth, options) {
        if (depth < 0) {
          return options.stylize('[Box]', 'special');
        }
    
        const newOptions = Object.assign({}, options, {
          depth: options.depth === null ? null : options.depth - 1
        });
    
        // Four space padding because that's the size of "Box<".
        const padding = "    ";
        const inner = util.inspect(this.value, newOptions).replace(/\n/g, "\n" + padding);
        return options.stylize('Box', 'special') + '<' + inner + '>';
      }
    }
  9. addaleax commented on Sep 30, 2016

    @addaleax
    Member

    @Havvy I guess that looks pretty okay, and trying it out seems to work just fine – do you want to open a PR against doc/api/util.md with this example?

  10. added
    docIssues and PRs related to Node.js documentation.
    and removed
    docIssues and PRs related to Node.js documentation.
    on Dec 1, 2016
  11. addaleax commented on Apr 30, 2017

    @addaleax
    Member

    I’m going to close this as #8875 has landed and there hasn’t been any new discussion since then. If you feel like there’s more to this issue, feel free to re-open, or open a new issue or pull request. :)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    docIssues and PRs related to Node.js documentation.good first issueIssues that are suitable for first-time contributors.questionIssues asking questions about Node.js.utilIssues and PRs related to the built-in util module.

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions