To prevent a small YAML document from expanding into a massive object graph via aliases, implement a manual stack-based traversal to count nodes. This approach avoids call stack overflows and ensures that aliases and cyclic references are caught by hitting the specified limit.
Note: Aliases that point to the same node are counted every time they appear, which accurately reflects the real materialization cost.
// plain `{}` or `Object.create(null)`, but not Date / Uint8Array / etc.
function isContainer(o) {
if (Array.isArray(o)) return true
if (!o || typeof o !== 'object') return false
const proto = Object.getPrototypeOf(o)
return proto === Object.prototype || proto === null
}
function guardNodeCount(root, limit) {
let count = 0
const stack = [ root ]
while (stack.length) {
const node = stack.pop()
for (const key in node) {
if (++count > limit) throw new Error('Too many nodes')
const value = node[key]
if (isContainer(value)) stack.push(value)
}
}
}
const data = yaml.load(input)
guardNodeCount(data, 100000)
const json = JSON.stringify(data)