You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何友好打印含嵌套Set的Graph对象?解决JSON.stringify栈溢出问题

如何友好打印带循环引用的图对象

问题背景

现有如下图结构代码:

class Node {
  constructor(value) {
    this.value = value;
    this.adjacents = new Set();
  }

  addAdjacent(node) {
    this.adjacents.add(node);
  }
}

class Graph {
  constructor(directed = false) {
    this.nodes = new Map();
    this.directed = directed;
  }

  addVertex(value) {
    const node = this.nodes.has(value);
    if (node) {
      return this.nodes.get(value);
    }

    const vertex = new Node(value);
    this.nodes.set(value, vertex);
    return vertex;
  }

  addEdge(src, dest) {
    let srcNode = this.nodes.get(src);
    if (!srcNode) {
      srcNode = this.addVertex(src);
    }

    let destNode = this.nodes.get(dest);
    if (!destNode) {
      destNode = this.addVertex(dest);
    }

    srcNode.addAdjacent(destNode);
    if (this.directed === false) {
      destNode.addAdjacent(srcNode);
    }
  }
}

const g1 = new Graph();
g1.addVertex("a");
g1.addVertex("b");
g1.addEdge("a", "c");

直接用console.log(g1)时,邻接节点信息无法展开查看:

Graph {
  nodes: Map(3) {
    'a' => Node { value: 'a', adjacents: [Set] },
    'b' => Node { value: 'b', adjacents: Set(0) {} },
    'c' => Node { value: 'c', adjacents: [Set] }
  },
  directed: false
}

尝试用JSON.stringify的replacer转换时,因为节点间循环引用(a和c互相引用),触发栈溢出:

console.log(
  JSON.stringify(
    Object.fromEntries(g1.nodes),
    (_key, value) =>
      value.adjacents instanceof Set ? [...value.adjacents] : value,
    2
  )
);

错误信息:

JSON.stringify(
       ^

RangeError: Maximum call stack size exceeded
    at JSON.stringify (<anonymous>)

解决方案

方法1:给Node类添加toJSON方法

通过自定义toJSON,让序列化时只输出邻接节点的value,避免循环引用:

class Node {
  constructor(value) {
    this.value = value;
    this.adjacents = new Set();
  }

  addAdjacent(node) {
    this.adjacents.add(node);
  }

  // 自定义序列化逻辑
  toJSON() {
    return {
      value: this.value,
      adjacents: [...this.adjacents].map(node => node.value)
    };
  }
}

之后直接序列化即可得到清晰输出:

console.log(JSON.stringify(Object.fromEntries(g1.nodes), null, 2));

输出结果:

{
  "a": {
    "value": "a",
    "adjacents": [
      "c"
    ]
  },
  "b": {
    "value": "b",
    "adjacents": []
  },
  "c": {
    "value": "c",
    "adjacents": [
      "a"
    ]
  }
}

方法2:改进replacer函数,跟踪已访问节点

如果不想修改类定义,可以在replacer里用WeakSet跟踪已处理的节点,避免循环递归:

const visited = new WeakSet();
console.log(
  JSON.stringify(Object.fromEntries(g1.nodes), (key, value) => {
    if (value instanceof Node) {
      if (visited.has(value)) {
        // 已访问过的节点,只返回value避免循环
        return value.value;
      }
      visited.add(value);
      return {
        value: value.value,
        adjacents: [...value.adjacents].map(n => n.value)
      };
    }
    return value;
  }, 2)
);

同样能得到和方法1一致的友好输出。

方法3:手动遍历生成打印对象

直接遍历图的节点,构建一个只包含关键信息的普通对象,再打印:

const printObj = {};
for (const [key, node] of g1.nodes) {
  printObj[key] = {
    value: node.value,
    adjacents: [...node.adjacents].map(n => n.value)
  };
}
console.log(printObj);

控制台输出:

{
  a: { value: 'a', adjacents: [ 'c' ] },
  b: { value: 'b', adjacents: [] },
  c: { value: 'c', adjacents: [ 'a' ] }
}

内容的提问来源于stack exchange,提问作者Cody

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.19 21:48:24