_.isEqual
Deep-compare two values to detect change or duplication
- py_dict
- other
- BOOLEAN
Sometimes you need to know whether two things in a workflow are the same, and the things aren't simple scalars. Same seed as last run? Same list of tags as the other branch produced? Same dictionary out of two different nodes? _.isEqual in the sfinktah/comfy-ovum pack is the deep-equality check for exactly that - it compares two objects all the way down and hands you a boolean. It's one of the genuinely usable members of the pack's ovum/underscore family.
The family is a set of auto-generated graph wrappers around underscore3, a Python port of Underscore.js, and the author's README warns the whole thing is a work in progress that's "not recommended for use in workflows." Take the warning in the spirit intended - it mostly applies to the exotic method nodes. A deep comparison is simple, and the code is short enough to trust.
What it does
The library implements isEqual as self.obj == other. That's it. Which sounds underwhelming until you remember that Python's == on lists and dicts is already deep and recursive - [1, [2, 3]] == [1, [2, 3]] is True, and dict comparison walks nested keys and values the same way. So for the data you actually pass around a Comfy graph - seeds, prompts, lists of strings, dicts built by dynamic nodes - this behaves like the JavaScript _.isEqual you might recognize, without pulling in a custom comparison routine.
Where it diverges from JavaScript is worth knowing: this is Python == semantics. 1 == True is True in Python because bool is a subclass of int, so comparing a numeric 1 against a boolean True will surprise you. If your data mixes types like that, treat the result with suspicion. Otherwise it's a faithful deep compare.
Inputs and outputs
py_dict- first object, type*, optional. The tooltip calls it the "expected object"; any JSON-serializable value works.other- second object, type*. This is the thing you're comparing against. Both are optional inputs, so you can drive either side from a widget or a wire.- Output - a single
BOOLEAN, properly typed, ready to feed an IfElse or switch.
Where it fits
The highest-value pattern is change detection: capture a value at the start of a run, compare it against a later value with _.isEqual, and gate a branch on "this actually changed." For instance, compare the current prompt or seed against a stored copy and skip re-generation when nothing moved. It's also handy for asserting two pipelines produced identical lists before you merge them.
One caveat on cost: the node deep-copies its input before checking (the whole underscore family does, to avoid mutating your data). Comparing two big nested structures means copying both of them. Fine for lists of strings and small dicts; don't point it at image tensors just to ask "are these the same frame."
Installing it
Install the pack via ComfyUI Manager (search comfy-ovum) or:
cd ComfyUI/custom_nodes
git clone https://github.com/sfinktah/comfy-ovum
Restart ComfyUI. underscore3 ships inside the repo, so there's nothing extra to install for this node; Manager handles the pack's requirements.txt for the rest.
Gotchas
- It's Python
==, not JavaScript===-1 == True,1 == 1.0are bothTrue. - Deep comparison means large structures cost a deep copy on each side. Keep it to small data.
- The tooltip says JSON is allowed for arrays/objects, which is how you'd enter a literal list or dict on the widget side - handy for comparing against a hardcoded expected value.
Inputs (2)
| Name | Type | Default | Description |
|---|---|---|---|
| py_dictopt | * | Primary input object (expected object). You can still pass any JSON-serializable value. Also accepts _.CHAIN to continue chaining. | |
| otheropt | * | other: JSON allowed for arrays/objects where applicable. |
Outputs (1)
| Name | Type | Description |
|---|---|---|
| BOOLEAN | BOOLEAN | — |