Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Fix numpy view py/id desync when base is shared (#449) - #612

Merged
Theelx merged 2 commits into
jsonpickle:mainfrom
apoorvdarshan:fix-449-numpy-view-py-id-desync
Jul 21, 2026
Merged

Fix numpy view py/id desync when base is shared (#449)#612
Theelx merged 2 commits into
jsonpickle:mainfrom
apoorvdarshan:fix-449-numpy-view-py-id-desync

Conversation

@apoorvdarshan

Copy link
Copy Markdown
Contributor
  • If this PR changes any non-documentation portion of the codebase, I have updated the CHANGES.rst file for this change.
    • In the CHANGES.rst update, I added a link to the PR number that this will be (e.g. (+611)) and (if applicable) added a link to the issue that this fixes (e.g. (#608)).
  • If this PR fixes a bug or adds a feature, I have added appropriate tests.
  • I have run the tests with pytest and run formatting with ruff format.
  • If generative AI of any kind was used in creating this PR, I have followed the these guidelines:
    • I ensured that this PR is not entirely written by any generative AI model.
    • I ensured that any portions of the code written by any generative AI model are free from copyrighted code.
    • I ensured that I included a Co-authored-by or Assisted-by tag in the footer containing the name of all generative AI models used.
    • I ensured that all code in this PR submitted verbatim from generative AI output is limited to creating/fixing tests/documentation and/or fixing bugs in extensions, and does not impact the core code.
    • I understand that if I used AI and the above checkmarks are not all true when I submit this PR, my PR will be closed.
  • I understand that if this checklist is deleted from the PR body, my PR will be closed.

Summary

Fixes #449. Round-tripping a non-contiguous NumPy view whose base is shared with another view raised:

ValueError: strides is incompatible with shape of requested array and size of buffer

The maintainer confirmed an off-by-one in the encode step (a py/id emitted as N+1 instead of N), so the base-array reference resolved to the wrong object during restore — a 0-dim array wrapping an unpickler._Proxy, as reported in the issue thread.

Root cause

When a view is stored by reference to a base array, NumpyNDArrayHandlerView.flatten also emits a decorative values entry, documented as "not used in restore since base is present, but include values for human-readability". That decoration went through self.context.flatten(obj.tolist(), reset=False), which registers a py/id reference in the pickler's bookkeeping.

Restore, however, ignores values entirely when a base is present (it rebuilds the array from buffer/offset/strides) and therefore never registers a matching reference. The encode and decode id counters then drift apart by one for every referenced array carrying a decorative values. A later view stored as {"py/id": N} to a shared base then resolves to the wrong object during restore, producing the strides error.

This is easy to trigger with e.g. sklearn's cv_results_ (the issue's repro): a masked array registers an unmirrored reference and the splitN_test_score views share a single base.

Fix

Emit the decorative values without allocating a py/id slot, by snapshotting and restoring the pickler's reference bookkeeping (_objs / _flattened) around the call. This keeps the encode and decode id counters in lockstep while preserving the human-readable values in the output. The change is confined to the numpy extension (jsonpickle/ext/numpy.py); no core code is touched.

Tests / Verification

  • Added tests/numpy_test.py::test_shared_base_after_referenced_object, a minimal regression that reproduces the exact ValueError on the pristine code and passes with the fix (masked array + two non-contiguous views sharing a base).
  • The original sklearn cv_results_ repro from the issue now round-trips correctly with all arrays equal.
  • pytest tests/numpy_test.py tests/sklearn_test.py tests/jsonpickle_test.py — all green (221 passed).
  • Verified object-dtype views, 2D non-contiguous views, and unpicklable=False still round-trip correctly.
  • ruff check, ruff format --check, and isort --profile=black are clean; mypy --strict introduces no new errors in the changed file.

Assisted-by: Claude (Anthropic)

Disclosure: prepared with AI assistance; reviewed and verified locally.

@Theelx

Theelx commented Jul 21, 2026

Copy link
Copy Markdown
Contributor

Thank you for this! Sorry for not getting to it earlier. For the AI footer in the template, it was meant to be added to the commit itself. After you amend the commit to include that, I'd be happy to merge!

@Theelx Theelx left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Looks good once the comment about commit footers is addressed!

@Theelx

Theelx commented Jul 21, 2026

Copy link
Copy Markdown
Contributor

Oh also, I forgot this initially, but could you remove the backticks from the values inside the docstrings? We don't use markdown when rendering the docstrings so they're an unnecessary remnant of the AI that just clogs up the character count.

Round-tripping a non-contiguous numpy view whose base is shared with
another view could raise:

    ValueError: strides is incompatible with shape of requested array
    and size of buffer

When a view is stored by reference to a base array,
NumpyNDArrayHandlerView also emits a decorative "values" entry "for
human-readability". Restore ignores that entry entirely when a base is
present, but flattening it went through self.context.flatten(), which
registered an extra py/id reference that the restore step never mirrored.

This desynchronized the encode/decode reference counters, so a later view
stored as a py/id reference to a shared base resolved to the wrong object
(a 0-dim array wrapping a _Proxy), producing the strides error.

Emit the decorative values without allocating a py/id slot by snapshotting
and restoring the pickler's reference bookkeeping around the call, keeping
the encode and decode id counters in lockstep.

Adds a regression test and a CHANGES.rst entry.

Fixes jsonpickle#449.

Assisted-by: Claude (Anthropic)
@apoorvdarshan
apoorvdarshan force-pushed the fix-449-numpy-view-py-id-desync branch from 2322a11 to 13eb183 Compare July 21, 2026 17:14
@Theelx
Theelx merged commit f1649f6 into jsonpickle:main Jul 21, 2026
5 checks passed
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.

Error with strides deserializing complex NumPy array

2 participants