Skip to content

Add atomic modification API and JSON patch support with enhancements#71

Merged
IvanMurzak merged 17 commits into
mainfrom
feature/atomic-api
Apr 29, 2026
Merged

Add atomic modification API and JSON patch support with enhancements#71
IvanMurzak merged 17 commits into
mainfrom
feature/atomic-api

Conversation

@IvanMurzak
Copy link
Copy Markdown
Owner

This pull request introduces several major enhancements to ReflectorNet, focusing on atomic, path-based modification and advanced read-side navigation of object graphs. The README is significantly expanded to document these new features, and the core implementation is updated to support partial in-place updates of array/list elements and filtered views of complex objects. These changes make it much easier to precisely modify or inspect deeply nested data structures without affecting unrelated fields.

New Features and Enhancements:

Atomic Path-Based Modification & JSON Patch:

  • Added support for atomic, path-based modification of fields, array/list elements, and dictionary entries using a unified path syntax (e.g., celestialBodies/[0]/orbitRadius). Also introduced JSON Patch functionality, allowing multiple fields at different depths to be modified in a single call, following JSON Merge Patch semantics. [1] [2]

Partial In-Place Array/List Element Modification:

  • Implemented partial in-place modification for arrays and lists in ArrayReflectionConverter. Now, individual elements can be updated by path without replacing the entire collection, supporting both arrays and IList types.

View & Grep (Read-Side Navigation):

  • Introduced ViewQuery and ViewMatch classes, enabling filtered serialization and live object graph "grep" functionality. Users can now navigate to subtrees, filter by name regex or type, and extract only the data they need. [1] [2]

Improved Base Converter Logic:

  • Updated the base reflection converter logic to better distinguish between full replacements and partial updates, ensuring that SetValue is only called when appropriate. This prevents overwriting objects when only a subset of fields is being modified.

Documentation:

  • Expanded the README to thoroughly explain the new features, including usage examples, path syntax, JSON Patch rules, and the new view/grep capabilities. [1] [2] [3]

These improvements provide a much more flexible and robust API for both modifying and inspecting complex object graphs in .NET.

- Introduced `TryModifyAt` method for direct modification of fields, array elements, and dictionary entries without constructing full objects.
- Implemented path parsing logic to navigate to specific members using a string-based path format.
- Added `TryPatch` method to apply JSON Merge Patch documents, allowing multiple fields to be modified in a single call.
- Created new partial files: `Reflector.ModifyAt.cs` for atomic modifications and `Reflector.Patch.cs` for JSON patching.
- Enhanced error handling with detailed messages for navigation failures.
- Added tests to verify the functionality of the new modification methods and JSON patching capabilities.
Add atomic path-based modification API and JSON patch support
Add View and TryReadAt methods for object serialization enhancements
@IvanMurzak IvanMurzak self-assigned this Mar 3, 2026
@IvanMurzak IvanMurzak added the enhancement New feature or request label Mar 3, 2026
@github-actions
Copy link
Copy Markdown
Contributor

github-actions Bot commented Mar 3, 2026

Test Results

    2 files  ±  0      2 suites  ±0   8m 31s ⏱️ -23s
1 423 tests + 68  1 423 ✅ + 68  0 💤 ±0  0 ❌ ±0 
2 846 runs  +136  2 846 ✅ +136  0 💤 ±0  0 ❌ ±0 

Results for commit 2252f62. ± Comparison against base commit 50337a6.

♻️ This comment has been updated with latest results.

Add numerous unit tests to AtomicModifyTests.cs and ViewTests.cs covering edge cases for TryModifyAt, TryPatch, TryReadAt, Grep and View behaviors. New tests exercise null-intermediate paths, negative indices, dictionary key add/type-conversion, bracket notation misuse, read-only properties, mixed dictionary/array nesting, invalid JSON patches, deep array-based patches, null-at-root patches, hash-prefixed paths, circular-reference handling, property discovery, max-depth pruning, empty-path handling, List<T> element matching, and maxDepth=0 behavior. Also add small test helper types (ReadOnlyPropertyContainer, DictOfArraysContainer, ListContainer, CircularNode) and include logging assertions to verify diagnostic messages.
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

This PR expands ReflectorNet with path-based navigation/modification APIs (write + read), JSON Merge Patch-style multi-edit support, and read-side “view/grep” utilities for inspecting object graphs, plus documentation and tests to cover the new behavior.

Changes:

  • Added path-based write (TryModifyAt) and read (TryReadAt) navigation plus “View” (filtered serialization) and “Grep” (regex search over live graph).
  • Added JSON patch support (TryPatch) with optional $type hints and bracket-notation keys for arrays/dictionaries.
  • Enhanced converter behavior to support partial element updates for arrays/lists and refined base modify behavior for partial patches.

Reviewed changes

Copilot reviewed 12 out of 12 changed files in this pull request and generated 7 comments.

Show a summary per file
File Description
docs/plan/atomic-modify.md Design/plan document describing the new atomic modify + patch/navigation features and expected behaviors.
ReflectorNet/src/Reflector/Reflector.ModifyAt.cs Implements path-based atomic modification (TryModifyAt) plus shared path parsing helpers.
ReflectorNet/src/Reflector/Reflector.Navigate.cs Shared lookup helpers for member and bracketed element navigation used by modify-by-path.
ReflectorNet/src/Reflector/Reflector.ReadAt.cs Adds TryReadAt and a shared TryNavigateOneSegment helper used by read-side navigation.
ReflectorNet/src/Reflector/Reflector.Patch.cs Implements JSON Merge Patch-style updates via TryPatch / recursive patch engine.
ReflectorNet/src/Reflector/Reflector.View.cs Adds View (filtered serialization) and Grep (regex search) read-side utilities.
ReflectorNet/src/Model/ViewQuery.cs Introduces ViewQuery/ViewMatch models for view/grep functionality.
ReflectorNet/src/Converter/Reflection/Base/BaseReflectionConverter.Modify.cs Adjusts base modify logic to avoid calling SetValue for partial-patch scenarios.
ReflectorNet/src/Converter/Reflection/ArrayReflectionConverter.Modify.cs Adds partial in-place array/list element modification using [i]-named fields.
ReflectorNet.Tests/src/ReflectorTests/AtomicModifyTests.cs Comprehensive tests for TryModifyAt, TryPatch, and array partial element modification.
ReflectorNet.Tests/src/ReflectorTests/ViewTests.cs Tests for View/TypeFilter/NamePattern/MaxDepth, Grep, and TryReadAt behaviors.
README.md Documents the new APIs (TryModifyAt/TryPatch/View/TryReadAt/Grep) with examples and updated section numbering.

Comment thread ReflectorNet/src/Reflector/Reflector.Patch.cs
Comment thread ReflectorNet/src/Reflector/Reflector.Patch.cs
Comment thread ReflectorNet/src/Reflector/Reflector.Navigate.cs
Comment thread ReflectorNet/src/Reflector/Reflector.Navigate.cs
Comment thread ReflectorNet/src/Reflector/Reflector.ReadAt.cs
Comment thread ReflectorNet/src/Reflector/Reflector.View.cs
Comment thread ReflectorNet/src/Reflector/Reflector.Patch.cs
IvanMurzak added a commit to IvanMurzak/Unity-MCP that referenced this pull request Apr 29, 2026
The committed tests in this PR exercise the new atomic-modify and
view APIs (TryModifyAt, TryPatch, TryReadAt, View, Grep) introduced
by IvanMurzak/ReflectorNet#71 (HEAD 2252f623, branch feature/atomic-api).
Those APIs are not in the published com.IvanMurzak.ReflectorNet 5.0.0
NuGet, so building this PR against the released DLL fails compilation
on every Unity matrix job.

Replace the 5.0.0 DLL with a Release build of ReflectorNet at
feature/atomic-api SHA 2252f623 so CI compiles. The .csproj HintPath
locations are unchanged; only the binary content is updated.

This is a temporary cross-repo dependency stub. Once ReflectorNet#71
merges and a NuGet release ships with the atomic API surface, the
proper path is:

  1. Bump the Unity-MCP-Plugin NuGet folder name + .csproj HintPaths
     to the released ReflectorNet version (e.g. 5.1.0).
  2. Replace this dev DLL with the release artefact.

DLL provenance:
  - Source: ReflectorNet/ReflectorNet/bin/Release/netstandard2.1/ReflectorNet.dll
  - Built from: feature/atomic-api @ 2252f623495ff9355f022ca68990807b5280b429
  - md5: cdf5e92be0f2e5084d5b8ea16b57b4de
  - Size: 227328 bytes (was 206336 bytes for released 5.0.0)
@IvanMurzak IvanMurzak merged commit 67f777c into main Apr 29, 2026
2 checks passed
@IvanMurzak IvanMurzak deleted the feature/atomic-api branch April 29, 2026 09:31
IvanMurzak added a commit to IvanMurzak/Unity-MCP that referenced this pull request Apr 29, 2026
* test: cover new ReflectorNet atomic API in Unity EditMode

Add Unity EditMode tests under Packages/com.ivanmurzak.unity.mcp/Tests/Editor/AtomicApi
covering the atomic-modify / atomic-read / view / grep API surface introduced by
IvanMurzak/ReflectorNet PR #71:

- TryModifyAt: root field, array element, dictionary string/int key, partial
  SerializedMember patch, out-of-range index error, missing-member error.
- TryPatch: multi-field top-level patch, nested array+dictionary patch,
  JsonElement overload, "$type" polymorphic replacement (Animal -> Dog),
  null-value erasure (RFC 7396), invalid-JSON failure path.
- TryReadAt: read-write symmetry against the same paths exercised by
  TryModifyAt, plus invalid-path detailed error.
- View / Grep: NameRegex + TypeFilter on a graph with circular references,
  MaxDepth pruning, flat ViewMatch list, null-input + invalid-regex paths.

Tests use small POCO fixtures defined in the test asmdef (CelestialBody,
StarSystemPoco, Animal/Dog, CyclicNode) — they intentionally do NOT consume
ReflectorNet.Tests.Model.SolarSystem because that fixture lives in
ReflectorNet's own test assembly which is not shipped to Unity.

Validated against ReflectorNet feature/atomic-api branch at
SHA 2252f623495ff9355f022ca68990807b5280b429 (locally built and dropped into
Assets/Plugins/NuGet/com.IvanMurzak.ReflectorNet.5.0.0/ReflectorNet.dll for
the test gate; the DLL itself is NOT committed — the committed tests target
whatever ReflectorNet version is the active dependency at PR-merge time).

Local Unity EditMode test gate: 815/815 passed in 3m35.8s.

* test: replace ReflectorNet.dll with build from feature/atomic-api

The committed tests in this PR exercise the new atomic-modify and
view APIs (TryModifyAt, TryPatch, TryReadAt, View, Grep) introduced
by IvanMurzak/ReflectorNet#71 (HEAD 2252f623, branch feature/atomic-api).
Those APIs are not in the published com.IvanMurzak.ReflectorNet 5.0.0
NuGet, so building this PR against the released DLL fails compilation
on every Unity matrix job.

Replace the 5.0.0 DLL with a Release build of ReflectorNet at
feature/atomic-api SHA 2252f623 so CI compiles. The .csproj HintPath
locations are unchanged; only the binary content is updated.

This is a temporary cross-repo dependency stub. Once ReflectorNet#71
merges and a NuGet release ships with the atomic API surface, the
proper path is:

  1. Bump the Unity-MCP-Plugin NuGet folder name + .csproj HintPaths
     to the released ReflectorNet version (e.g. 5.1.0).
  2. Replace this dev DLL with the release artefact.

DLL provenance:
  - Source: ReflectorNet/ReflectorNet/bin/Release/netstandard2.1/ReflectorNet.dll
  - Built from: feature/atomic-api @ 2252f623495ff9355f022ca68990807b5280b429
  - md5: cdf5e92be0f2e5084d5b8ea16b57b4de
  - Size: 227328 bytes (was 206336 bytes for released 5.0.0)

* test: sync ReflectorNet DLL into Unity-Tests projects

Each Unity-Tests/<version>/ has its own committed copy of
Assets/Plugins/NuGet/com.IvanMurzak.ReflectorNet.5.0.0/ReflectorNet.dll.
The previous commit only updated the Unity-MCP-Plugin/ copy, so CI
EditMode runs (which use Unity-Tests/<version>/ as the project path)
compiled against the old shipped 5.0.0 NuGet DLL and failed with
"Reflector does not contain a definition for TryModifyAt / View / Grep".

Built ReflectorNet from source via the infra cli (`uv run --directory
.scripts python cli.py build-plugins`) and propagated the resulting
netstandard2.1 DLL to all six Unity NuGet drop folders. The build is
deterministic for this source: the produced DLL hashes byte-identical
to the one already committed at Unity-MCP-Plugin/.../ReflectorNet.dll
in commit ed4977d, so only the 5 Unity-Tests/<version>/ copies needed
to change.

Verified locally: every ReflectorNet.dll across the six Unity project
trees now hashes to 564de27
(227 328 bytes).

* chore(deps): bump com.IvanMurzak.ReflectorNet to 5.1.0

ReflectorNet 5.1.0 was just released to NuGet
(https://www.nuget.org/packages/com.IvanMurzak.ReflectorNet/5.1.0),
introducing the atomic API surface (TryModifyAt, TryPatch, TryReadAt,
View, Grep) that the EditMode tests under Tests/Editor/AtomicApi/
depend on.

Update path:

  Unity-MCP-Plugin/ + every Unity-Tests/<unity-version>/:
    Assets/Plugins/NuGet/com.IvanMurzak.ReflectorNet.5.0.0/  ->
    Assets/Plugins/NuGet/com.IvanMurzak.ReflectorNet.5.1.0/
    (directory + sibling .meta + inner .dll.meta renamed via git mv;
     the inner ReflectorNet.dll content swapped to the released NuGet
     5.1.0 bytes — blob dfd4a43)

  NuGetConfig.cs:
    Added com.IvanMurzak.ReflectorNet@5.1.0 as a top-level pinned
    package right after McpPlugin. Without this, the resolver would
    fall back to McpPlugin 6.1.0's transitive 5.0.0 nuspec on dev
    machines (CI is unaffected — IsCi() short-circuits the resolver).

Verified locally: every ReflectorNet.dll across the six Unity project
trees now hashes byte-identical to the NuGet 5.1.0 lib/netstandard2.1
payload.
IvanMurzak added a commit that referenced this pull request Apr 29, 2026
…ups) (#76)

Address 7 unresolved review threads from copilot-pull-request-reviewer
on PR #71. The Try* methods (TryPatch, TryModifyAt, TryReadAt, View) all
document "errors accumulated in the optional Logs object; nothing is
thrown", but several reflection call sites could let exceptions escape
on write-only properties, throwing getters/setters, missing converters,
or null patches against non-nullable value types.

Reflector.Patch.cs:
- TryPatchInternal: guard the JSON-null patch path against non-nullable
  value-type targets (Nullable<T> still permits null) — previously the
  null would propagate to a SetValue that throws ArgumentException.
- TryPatchInternal: wrap CreateInstance in try/catch — Reflector.DefaultValue
  documents that it throws ArgumentException when no converter supports the
  type.
- TryPatchMember: add CanRead check on properties; wrap field/property
  GetValue and SetValue in try/catch with logged failures.

Reflector.Navigate.cs:
- TryLookupMember: add CanRead check on properties; wrap field/property
  GetValue and SetValue in try/catch.
- TryLookupMember: switch the "Available fields/properties" error list
  from raw GetFields/GetProperties to GetSerializableFields/Properties so
  the message matches what Serialize/TryModify actually operate on
  (matches the existing convention in TryNavigateOneSegment and
  BaseReflectionConverter). Drop `static` on TryLookupMember to access
  the instance helpers — its only caller (TryModifyAtMember) is already
  an instance method.

Reflector.ReadAt.cs:
- TryNavigateOneSegment: add CanRead check on properties; wrap field/
  property GetValue in try/catch with logged failures.

Reflector.View.cs:
- View: wrap the Serialize call in try/catch(ArgumentException) — Serialize
  is documented to throw when both obj and fallbackObjType are null or no
  converter exists for the resolved type. Mirrors the existing
  TryCompilePattern try/catch convention in the same file.

Tests: 1423/1423 pass on net8.0 and net9.0 (no test changes — these fixes
plug exception leaks that the existing suite did not exercise).
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

enhancement New feature or request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants