Skip to content

visitor: deepcopy literal value so each search returns a fresh object (fixes #318) - #375

Closed
qianbkk wants to merge 1 commit into
jmespath:developfrom
qianbkk:fix-issue-318-literal-mutation
Closed

qianbkk wants to merge 1 commit into
jmespath:developfrom
qianbkk:fix-issue-318-literal-mutation

Conversation

@qianbkk

@qianbkk qianbkk commented Sep 23, 2026

Copy link
Copy Markdown

Fixes #318.

What

Parser caches the parsed AST per expression, so the literal node's already-decoded Python value is shared across every call to search(). Returning that object directly meant that two searches of the same literal expression ([], {}, [1, 2], ...) would hand back the same list/dict object -- any in-place mutation by the caller would leak into every subsequent call, including unrelated searches on completely different inputs.

visit_literal now wraps the cached value with copy.deepcopy on every visit. Immutable literals (str / int / float / bool / None) are unaffected; mutable JSON literals become independent per call.

Repro (before)

import jmespath

a = jmespath.search('`[]`', {})
a.extend([1, 2, 3])

b = jmespath.search('`[]`', {})  # expects [], got [1, 2, 3]
b.extend([9, 8, 7])

print(a)  # [1, 2, 3, 9, 8, 7] -- leaked from b

After: a and b are independent lists.

Diff

 def visit_literal(self, node, value):
-    return node['value']
+    return copy.deepcopy(node['value'])

Tests

Four regression tests in tests/test_search.py (new TestLiteralFreshness class):

  • test_list_literal_is_independent_per_search
  • test_object_literal_is_independent_per_search
  • test_nested_list_literal_is_independent (covers [[]] etc.)
  • test_string_literal_remains_a_string (control)

Full suite: 995 passed, 1 skipped (excluding tests/legacy/ which is data-only). No existing tests changed.

Performance

copy.deepcopy is only invoked for literal nodes in expressions that use them, and only mutable JSON literals allocate new containers. The common case -- read-only results, immutable literals, expressions that don't use literals at all -- has the same cost as before.

AI disclosure

Prepared with AI assistance (kilo assistant); the diagnosis, fix, and tests were developed and verified locally before submission.

…fixes jmespath#318)

Parser caches the parsed AST per expression, so the literal node's
already-decoded Python value is shared across every call. Returning it
directly meant that two searches of `[]` or `{}` would hand back
the same list/dict object, and any in-place mutation by the caller
would leak into every subsequent call -- sometimes into completely
unrelated search calls on a different input.

visit_literal now copies the decoded value with copy.deepcopy on every
visit. Immutable literals (str/int/float/bool/None) are unaffected;
mutable JSON literals become independent per call. Performance is
unchanged for the common case where the result is read-only.

Four regression tests in tests/test_search.py cover list, object, and
nested list literals, plus a control that string literals still come
back as plain strings.
Copilot AI lite review requested due to automatic review settings September 23, 2026 20:59

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot review overview

🟡 Changes recommended

Avoid unnecessary immutable-scalar copying and clarify the misleading comment before approval.

Get a fresh assessment by requesting another Copilot review.

Review effort: Lite
Findings: 1 Low severity

Open (1)
What changed in this PR

Fixes shared mutable literal results by returning independent values for each search.

Changes:

  • Deep-copy cached literal values during visitation.
  • Add regression tests for list, object, nested, and string literals.
File Summary Review notes
jmespath/​visitor.py Returns fresh literal values per visit. Moderate (1 vote): avoid deep-copying immutable scalars. Nit (2 votes): clarify the implementation comment.
tests/​test_search.py Adds literal freshness regression coverage. No findings.

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

Comment thread jmespath/visitor.py
Comment on lines +233 to +234
# into every other call. Re-decode JSON literals on every visit so
# callers get a fresh, independent value.
@qianbkk qianbkk closed this Sep 24, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Unexpected mutations when using list or object literals in expressions

2 participants