Add atomic modification API and JSON patch support with enhancements#71
Merged
Conversation
- 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
Contributor
… for enhanced member resolution
…ll inputs and invalid patterns
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.
Contributor
There was a problem hiding this comment.
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$typehints 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. |
3 tasks
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
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.
This was referenced Apr 29, 2026
3 tasks
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).
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
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:
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:
ArrayReflectionConverter. Now, individual elements can be updated by path without replacing the entire collection, supporting both arrays andIListtypes.View & Grep (Read-Side Navigation):
ViewQueryandViewMatchclasses, 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:
SetValueis only called when appropriate. This prevents overwriting objects when only a subset of fields is being modified.Documentation:
These improvements provide a much more flexible and robust API for both modifying and inspecting complex object graphs in .NET.