Skip to content

Conversation

@dsarno
Copy link
Collaborator

@dsarno dsarno commented Jan 14, 2026

⏺ ## Fix manage_components set_property for object references

Problem

The manage_components tool's set_property action failed when setting Transform, Component, or ScriptableObject reference fields, with errors like:

  • "Unexpected token type 'EndObject' when deserializing UnityEngine.Object"
  • "Failed to convert value for property 'X' to type 'Transform'"

Solution

Enhanced UnityEngineObjectConverter.ReadJson to properly deserialize object references:

  • GUID support: {"guid": "abc123..."} loads assets by GUID
  • Path support: {"path": "Assets/..."} loads assets by path
  • Smart type conversion: When expecting Transform but receiving a GameObject instanceID, automatically extracts .transform. Same for other Component types via GetComponent().
  • Graceful error handling: Returns null with descriptive warnings instead of throwing exceptions

Also fixed ComponentOps.SetProperty to traverse the inheritance hierarchy when searching for private [SerializeField] fields (previously only searched the declared type).

Supported Input Formats

  • {"instanceID": 12345} - Scene objects (GameObjects, Components)
  • {"guid": "abc123..."} - Assets by GUID
  • {"path": "Assets/Foo.asset"} - Assets by path
  • "Assets/Foo.asset" - Assets by path (string)
  • "abc123..." - Assets by GUID (auto-detected)

Testing

  • All 262 Unity EditMode tests pass
  • Manually verified Transform and ScriptableObject field setting in live project

Summary by Sourcery

Improve deserialization and property setting for Unity object references in editor tooling.

Bug Fixes:

  • Allow UnityEngine.Object JSON deserialization to handle GUIDs, asset paths, and instance IDs with graceful fallbacks instead of throwing exceptions.
  • Correct component property setting to find private [SerializeField] fields defined in base classes, not just on the concrete component type.

Enhancements:

  • Add GUID auto-detection and warnings for failed asset loads when deserializing UnityEngine.Object values from JSON.
  • Support automatic conversion from GameObject instance IDs to expected Transform or Component types during JSON deserialization.
  • Improve non-editor behavior of UnityEngineObjectConverter by safely skipping unsupported tokens while retaining existing values.

Summary by CodeRabbit

  • New Features

    • GUID-first and path-based asset resolution during deserialization, plus GUID validation.
  • Bug Fixes

    • Corrected serialized-field lookup to find inherited private serialized fields across class hierarchies.
    • Improved type-mismatch handling with safe conversions (e.g., GameObject ↔ Transform/Component) and safer runtime behavior when editor APIs are unavailable.
    • Added richer diagnostic warnings for resolution failures and mismatches.

✏️ Tip: You can customize this high-level summary in your review settings.

@sourcery-ai
Copy link
Contributor

sourcery-ai bot commented Jan 14, 2026

Reviewer's Guide

Enhances Unity object deserialization for the manage_components set_property action so that UnityEngine.Object references (including Transform, Component, and ScriptableObject fields) can be set from various JSON formats, and fixes ComponentOps.SetProperty to correctly locate private [SerializeField] fields across an inheritance hierarchy.

Sequence diagram for manage_components SetProperty with enhanced Unity object resolution

sequenceDiagram
    actor Tool
    participant ComponentOps
    participant Serializer as JsonSerializer
    participant UnityObjConv as UnityEngineObjectConverter
    participant EditorUtil as EditorUtility
    participant AssetDB as AssetDatabase
    participant GO as GameObject
    participant Comp as Component
    participant Tr as Transform

    Tool->>ComponentOps: SetProperty(component, propertyName, valueToken, valueType, serializer)
    ComponentOps->>ComponentOps: FindSerializedFieldInHierarchy(component.GetType, propertyName)
    alt field found
        ComponentOps->>Serializer: Deserialize valueToken as field.FieldType
        Serializer->>UnityObjConv: ReadJson(reader, field.FieldType, existingValue, serializer)

        alt value is GUID or path
            UnityObjConv->>AssetDB: GUIDToAssetPath or LoadAssetAtPath
            AssetDB-->>UnityObjConv: asset or null
        else value has instanceID
            UnityObjConv->>EditorUtil: InstanceIDToObject(instanceId)
            EditorUtil-->>UnityObjConv: obj
            alt obj is GameObject and expected Transform
                UnityObjConv->>GO: access transform
                GO-->>UnityObjConv: Tr
            else obj is GameObject and expected Component subtype
                UnityObjConv->>GO: GetComponent(expectedType)
                GO-->>UnityObjConv: Comp or null
            end
        end

        UnityObjConv-->>Serializer: resolved UnityEngine.Object or null
        Serializer-->>ComponentOps: deserializedValue
        ComponentOps->>Component: set field to deserializedValue
        Component-->>ComponentOps: updated
        ComponentOps-->>Tool: return true
    else field not found
        ComponentOps-->>Tool: return false
    end
Loading

Updated class diagram for UnityEngineObjectConverter and ComponentOps behavior

classDiagram
    class UnityEngineObjectConverter {
        +ReadJson(reader JsonReader, objectType Type, existingValue UnityEngine_Object, serializer JsonSerializer) UnityEngine_Object
        -IsValidGuid(str string) bool
    }

    class ComponentOps {
        +SetProperty(component Component, propertyName string, valueToken JToken, valueType Type, serializer JsonSerializer) bool
        -FindSerializedFieldInHierarchy(type Type, fieldName string) FieldInfo
    }

    class UnityEngine_Object {
    }

    class Component {
    }

    class Transform {
    }

    class GameObject {
        +transform Transform
        +GetComponent(type Type) Component
    }

    class FieldInfo {
        +GetCustomAttribute~SerializeField~() SerializeField
    }

    class SerializeField {
    }

    Component <|-- Transform
    UnityEngine_Object <|-- Component
    UnityEngine_Object <|-- GameObject

    ComponentOps ..> FieldInfo : uses
    ComponentOps ..> SerializeField : checks

    UnityEngineObjectConverter ..> GameObject : resolves
    UnityEngineObjectConverter ..> Transform : may return
    UnityEngineObjectConverter ..> Component : may return
Loading

Flow diagram for UnityEngineObjectConverter.ReadJson deserialization logic

graph TD
    A[Start ReadJson<br/>tokenType, objectType, existingValue] --> B{UNITY_EDITOR?}
    B -- No --> C[Log warning<br/>UnityEngineObjectConverter cannot deserialize complex objects in non-Editor mode]
    C --> D{tokenType == StartObject?}
    D -- Yes --> E[JObject.Load skip object]
    D -- No --> F[reader.ReadAsString skip string]
    E --> G[Return existingValue]
    F --> G[Return existingValue]

    B -- Yes --> H{tokenType == Null?}
    H -- Yes --> I[Return null]
    H -- No --> J{tokenType == String?}

    J -- Yes --> K[Read strValue = reader.Value.ToString]
    K --> L{IsValidGuid strValue?}
    L -- Yes --> M[normalizedGuid = strValue without hyphens]
    M --> N[path = AssetDatabase.GUIDToAssetPath normalizedGuid]
    N --> O{path not empty?}
    O -- Yes --> P[asset = AssetDatabase.LoadAssetAtPath path, objectType]
    P --> Q{asset != null?}
    Q -- Yes --> R[Return asset]
    Q -- No --> S[Log warning<br/>Could not load asset with GUID]
    S --> T[Return null]
    O -- No --> S

    L -- No --> U[loadedAsset = AssetDatabase.LoadAssetAtPath strValue, objectType]
    U --> V{loadedAsset != null?}
    V -- Yes --> W[Return loadedAsset]
    V -- No --> X[Log warning<br/>Could not load asset at path]
    X --> W

    J -- No --> Y{tokenType == StartObject?}
    Y -- No --> Z[Log warning<br/>Unexpected token type when deserializing objectType]
    Z --> AA[Return null]

    Y -- Yes --> AB[jo = JObject.Load reader]
    AB --> AC{jo has guid string?}
    AC -- Yes --> AD[guid = jo.guid without hyphens]
    AD --> AE[path = AssetDatabase.GUIDToAssetPath guid]
    AE --> AF{path not empty?}
    AF -- Yes --> AG[asset = AssetDatabase.LoadAssetAtPath path, objectType]
    AG --> AH{asset != null?}
    AH -- Yes --> AI[Return asset]
    AH -- No --> AJ[Log warning<br/>Could not load asset with GUID]
    AJ --> AK[Return null]
    AF -- No --> AJ

    AC -- No --> AL{jo has instanceID int?}
    AL -- Yes --> AM[instanceId = jo.instanceID]
    AM --> AN[obj = EditorUtility.InstanceIDToObject instanceId]
    AN --> AO{obj != null?}
    AO -- No --> AP[objectName = jo.name or unknown]
    AP --> AQ[Log warning<br/>Could not resolve instance ID]
    AQ --> AR[Return null]

    AO -- Yes --> AS{objectType.IsAssignableFrom obj.GetType?}
    AS -- Yes --> AT[Return obj]
    AS -- No --> AU{objectType == Transform and obj is GameObject?}
    AU -- Yes --> AV[Return obj.transform]
    AU -- No --> AW{objectType is Component and obj is GameObject?}
    AW -- Yes --> AX[component = obj.GetComponent objectType]
    AX --> AY{component != null?}
    AY -- Yes --> AZ[Return component]
    AY -- No --> BA[Log warning<br/>GameObject missing component]
    BA --> BB[Return null]

    AW -- No --> BC[Log warning<br/>Type mismatch no automatic conversion]
    BC --> BD[Return null]

    AL -- No --> BE{jo has path string?}
    BE -- Yes --> BF[path = jo.path]
    BF --> BG[asset = AssetDatabase.LoadAssetAtPath path, objectType]
    BG --> BH{asset != null?}
    BH -- Yes --> BI[Return asset]
    BH -- No --> BJ[Log warning<br/>Could not load asset at path]
    BJ --> BK[Return null]

    BE -- No --> BL[Log warning<br/>JSON object missing instanceID, guid, or path]
    BL --> BM[Return null]
Loading

File-Level Changes

Change Details Files
Improve UnityEngine.Object JSON deserialization to support GUIDs, paths, instance IDs, and smarter type conversion with non-throwing error handling in editor.
  • In ReadJson, treat string tokens as either GUIDs (validated via a new IsValidGuid helper and resolved via AssetDatabase.GUIDToAssetPath) or asset paths, logging warnings and returning null when resolution fails instead of throwing.
  • Extend object-token handling to support GUID-based loading, instanceID lookup with additional conversion logic (GameObject to Transform/Component), and explicit path-based loading, with detailed warning messages for mismatches or unresolved references.
  • Replace the final JsonSerializationException with logged warnings and null returns for unsupported token types, and keep non-editor behavior as a best-effort no-op that logs a warning and skips unknown tokens.
  • Add a private IsValidGuid helper that normalizes potential GUID strings (removing hyphens) and verifies they contain exactly 32 hexadecimal characters.
MCPForUnity/Runtime/Serialization/UnityTypeConverters.cs
Make ComponentOps.SetProperty able to set inherited private [SerializeField] fields by walking the inheritance hierarchy.
  • Replace direct NonPublic GetField calls with use of a new FindSerializedFieldInHierarchy helper that walks up the type hierarchy to find matching private [SerializeField] fields by name or normalized name.
  • Implement FindSerializedFieldInHierarchy to iterate through base types with BindingFlags.NonPublic
Instance

Tips and commands

Interacting with Sourcery

  • Trigger a new review: Comment @sourcery-ai review on the pull request.
  • Continue discussions: Reply directly to Sourcery's review comments.
  • Generate a GitHub issue from a review comment: Ask Sourcery to create an
    issue from a review comment by replying to it. You can also reply to a
    review comment with @sourcery-ai issue to create an issue from it.
  • Generate a pull request title: Write @sourcery-ai anywhere in the pull
    request title to generate a title at any time. You can also comment
    @sourcery-ai title on the pull request to (re-)generate the title at any time.
  • Generate a pull request summary: Write @sourcery-ai summary anywhere in
    the pull request body to generate a PR summary at any time exactly where you
    want it. You can also comment @sourcery-ai summary on the pull request to
    (re-)generate the summary at any time.
  • Generate reviewer's guide: Comment @sourcery-ai guide on the pull
    request to (re-)generate the reviewer's guide at any time.
  • Resolve all Sourcery comments: Comment @sourcery-ai resolve on the
    pull request to resolve all Sourcery comments. Useful if you've already
    addressed all the comments and don't want to see them anymore.
  • Dismiss all Sourcery reviews: Comment @sourcery-ai dismiss on the pull
    request to dismiss all existing Sourcery reviews. Especially useful if you
    want to start fresh with a new review - don't forget to comment
    @sourcery-ai review to trigger a new review!

Customizing Your Experience

Access your dashboard to:

  • Enable or disable review features such as the Sourcery-generated pull request
    summary, the reviewer's guide, and others.
  • Change the review language.
  • Add, remove or edit custom review instructions.
  • Adjust other review settings.

Getting Help

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Jan 14, 2026

Note

Other AI code review bot(s) detected

CodeRabbit has detected other AI code review bot(s) in this pull request and will avoid duplicating their findings in the review comments. This may lead to a less comprehensive review.

📝 Walkthrough

Walkthrough

Replaces direct reflection of non-public serialized fields with inheritance-aware traversal in ComponentOps, and extends UnityEngineObjectConverter.ReadJson to resolve UnityEngine.Object values via GUID, InstanceID, and path with type-conversion fallbacks and additional warnings and editor/runtime-safe behavior.

Changes

Cohort / File(s) Summary
Hierarchical Serialized Field Lookup
MCPForUnity/Editor/Helpers/ComponentOps.cs
Adds FindSerializedFieldInHierarchy(Type, string) and updates SetProperty & GetAccessibleMembers to traverse base types and include private [SerializeField] fields (case-insensitive name matching, de-duplicated), replacing previous direct non-public reflection on the declaring type.
Enhanced Asset Resolution During Deserialization
MCPForUnity/Runtime/Serialization/UnityTypeConverters.cs
Extends UnityEngineObjectConverter.ReadJson to prefer GUID resolution, support path-based loading, improve InstanceID handling and type-conversion (GameObject→Transform/Component), add extensive warnings, safe runtime fallbacks, and introduces IsValidGuid helper.

Sequence Diagram(s)

sequenceDiagram
    participant JSON as JSON token
    participant Conv as UnityEngineObjectConverter
    participant AssetDB as AssetDatabase (Editor)
    participant EditorUtil as EditorUtility (Editor)
    participant Obj as UnityEngine.Object

    JSON->>Conv: ReadJson(token)
    alt token is string
        Conv->>Conv: IsValidGuid(string)?
        alt valid GUID
            Conv->>AssetDB: GUIDToAssetPath(guid)
            AssetDB-->>Conv: path or null
            Conv->>AssetDB: LoadAssetAtPath(path, type)
            AssetDB-->>Conv: Obj or null
        else not GUID
            Conv->>AssetDB: LoadAssetAtPath(path, type)
            AssetDB-->>Conv: Obj or null
        end
    else token is object
        alt has "guid"
            Conv->>AssetDB: GUIDToAssetPath & LoadAssetAtPath(type)
            AssetDB-->>Conv: Obj or null
        else has "instanceID"
            Conv->>EditorUtil: InstanceIDToObject(id)
            EditorUtil-->>Conv: Obj or null
            alt Obj found and type compatible
                Conv-->>JSON: Obj
            else Obj is GameObject & expected Transform
                Conv->>Obj: get transform
                Conv-->>JSON: Transform
            else expected Component & Obj is GameObject
                Conv->>Obj: GetComponent<T>()
                Obj-->>Conv: Component or null (warn)
            else type mismatch
                Conv-->>JSON: null (warn)
            end
        else has "path"
            Conv->>AssetDB: LoadAssetAtPath(path, type)
            AssetDB-->>Conv: Obj or null
        else
            Conv-->>JSON: null (warn missing fields)
        end
    end
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly related PRs

Poem

🐰 I hop through class branches, sniffing fields below,

I follow GUID trails where lost assets go,
From instance IDs to paths I pry,
I fetch transforms and components on the fly,
A little rabbit brings serialized things home.

🚥 Pre-merge checks | ✅ 2 | ❌ 1
❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 66.67% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'Fix manage_components set_property for object references' accurately describes the primary changes to the SetProperty method and UnityEngineObjectConverter to handle object reference deserialization and inheritance hierarchy traversal.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing touches
  • 📝 Generate docstrings

🧹 Recent nitpick comments
MCPForUnity/Runtime/Serialization/UnityTypeConverters.cs (1)

376-392: Consider extracting Transform before generic Component lookup.

The type conversion logic correctly handles Transform as a special case before generic Component lookup. However, since Transform is a Component, the condition on line 383 (typeof(Component).IsAssignableFrom(objectType)) would also match Transform. The current ordering is correct and works, but consider adding a comment noting that the Transform check must precede the generic Component check to avoid confusion during future maintenance.

MCPForUnity/Editor/Helpers/ComponentOps.cs (1)

285-309: [NonSerialized] attribute check is optional.

Unity's serialization respects [NonSerialized] to exclude fields from serialization. While the current implementation only checks for [SerializeField], adding a check to exclude fields with [NonSerialized] would align more closely with Unity's serialization behavior. However, this codebase contains no instances of fields marked with both attributes, making this a defensive improvement rather than a necessary fix for current use cases.


📜 Recent review details

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 1820722 and 3e30c68.

📒 Files selected for processing (2)
  • MCPForUnity/Editor/Helpers/ComponentOps.cs
  • MCPForUnity/Runtime/Serialization/UnityTypeConverters.cs
🧰 Additional context used
🧠 Learnings (3)
📚 Learning: 2025-12-29T15:23:11.613Z
Learnt from: msanatan
Repo: CoplayDev/unity-mcp PR: 491
File: MCPForUnity/Editor/Windows/EditorPrefs/EditorPrefsWindow.cs:78-115
Timestamp: 2025-12-29T15:23:11.613Z
Learning: In MCPForUnity, prefer relying on the established testing process to catch UI initialization issues instead of adding defensive null checks for UI elements in editor windows. This means during reviews, verify that tests cover UI initialization paths and that code avoids repetitive null-check boilerplate in Editor Windows. If a UI element can be null, ensure there is a well-tested fallback or that its initialization is guaranteed by design, rather than sprinkling null checks throughout editor code.

Applied to files:

  • MCPForUnity/Editor/Helpers/ComponentOps.cs
📚 Learning: 2025-10-13T13:27:23.040Z
Learnt from: msanatan
Repo: CoplayDev/unity-mcp PR: 316
File: TestProjects/UnityMCPTests/Assets/Tests/EditMode/Resources.meta:1-8
Timestamp: 2025-10-13T13:27:23.040Z
Learning: UnityMcpBridge is a legacy project kept for backwards compatibility; MCPForUnity is the only active Unity plugin project. GUID collisions between UnityMcpBridge and MCPForUnity are acceptable.

Applied to files:

  • MCPForUnity/Runtime/Serialization/UnityTypeConverters.cs
📚 Learning: 2025-09-03T16:00:55.839Z
Learnt from: dsarno
Repo: CoplayDev/unity-mcp PR: 0
File: :0-0
Timestamp: 2025-09-03T16:00:55.839Z
Learning: ComponentResolver in UnityMcpBridge/Editor/Tools/ManageGameObject.cs is a nested static class within ManageGameObject, not a sibling type. The `using static MCPForUnity.Editor.Tools.ManageGameObject;` import is required to access ComponentResolver methods directly without the outer class qualifier.

Applied to files:

  • MCPForUnity/Runtime/Serialization/UnityTypeConverters.cs
🧬 Code graph analysis (2)
MCPForUnity/Editor/Helpers/ComponentOps.cs (3)
MCPForUnity/Editor/Tools/ManageScriptableObject.cs (1)
  • Type (1517-1520)
MCPForUnity/Editor/Helpers/GameObjectLookup.cs (1)
  • Type (288-291)
MCPForUnity/Editor/Helpers/UnityTypeResolver.cs (3)
  • Type (103-108)
  • Type (113-118)
  • Type (123-128)
MCPForUnity/Runtime/Serialization/UnityTypeConverters.cs (2)
MCPForUnity/Editor/Helpers/ObjectResolver.cs (2)
  • UnityEngine (32-105)
  • UnityEngine (170-199)
MCPForUnity/Editor/Helpers/UnityTypeResolver.cs (3)
  • Type (103-108)
  • Type (113-118)
  • Type (123-128)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: Sourcery review
🔇 Additional comments (5)
MCPForUnity/Runtime/Serialization/UnityTypeConverters.cs (3)

321-343: LGTM! GUID-first resolution with proper normalization.

The string token handling correctly implements GUID detection with case normalization (ToLowerInvariant()) before path lookup, addresses the previous review feedback about GUID case sensitivity. The fallback to asset path loading with warning on failure provides good diagnostics.


426-431: LGTM! Correctly uses reader.Skip() for runtime fallback.

The runtime branch now properly uses reader.Skip() instead of the previous reader.ReadAsString(), which correctly advances past any token type without corrupting the reader state. This addresses the issue flagged in the previous review.


438-449: LGTM! Simple and effective GUID validation.

The manual hex validation is appropriate here since Guid.TryParse accepts formats (like {guid} with braces) that Unity's AssetDatabase does not use. This implementation correctly validates the 32-character hex format expected by Unity.

MCPForUnity/Editor/Helpers/ComponentOps.cs (2)

200-224: LGTM! Inheritance-aware serialized field lookup.

The change correctly addresses the limitation that Type.GetField() with BindingFlags.NonPublic only finds fields declared directly on that type, not inherited ones. The fallback to FindSerializedFieldInHierarchy after trying public members maintains backward compatibility while fixing the base class field issue. Good inline comments explaining the rationale.


256-272: LGTM! Proper inheritance traversal with duplicate prevention.

The implementation correctly uses DeclaredOnly to avoid getting the same field multiple times, and the HashSet prevents duplicates when a private serialized field might shadow a public member. The termination condition currentType != typeof(object) is appropriate.

✏️ Tip: You can disable this entire section by setting review_details to false in your review settings.


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Contributor

@sourcery-ai sourcery-ai bot left a comment

Choose a reason for hiding this comment

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

Hey - I've found 1 issue, and left some high level feedback:

  • The GUID detection/handling logic is duplicated between the string and JObject paths in UnityEngineObjectConverter.ReadJson; consider extracting a shared helper that takes a string GUID and target type and returns the loaded asset (or null) to keep behavior consistent and easier to maintain.
  • FindSerializedFieldInHierarchy currently iterates all non-public instance fields on each type and compares the names; you could simplify and reduce overhead by using currentType.GetField(fieldName, BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.IgnoreCase | BindingFlags.DeclaredOnly) and then checking for [SerializeField] on the single returned field.
Prompt for AI Agents
Please address the comments from this code review:

## Overall Comments
- The GUID detection/handling logic is duplicated between the string and JObject paths in UnityEngineObjectConverter.ReadJson; consider extracting a shared helper that takes a string GUID and target type and returns the loaded asset (or null) to keep behavior consistent and easier to maintain.
- FindSerializedFieldInHierarchy currently iterates all non-public instance fields on each type and compares the names; you could simplify and reduce overhead by using currentType.GetField(fieldName, BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.IgnoreCase | BindingFlags.DeclaredOnly) and then checking for [SerializeField] on the single returned field.

## Individual Comments

### Comment 1
<location> `MCPForUnity/Runtime/Serialization/UnityTypeConverters.cs:326` </location>
<code_context>
+                // Check if it looks like a GUID (32 hex chars, optionally with hyphens)
+                if (IsValidGuid(strValue))
+                {
+                    string path = UnityEditor.AssetDatabase.GUIDToAssetPath(strValue.Replace("-", ""));
+                    if (!string.IsNullOrEmpty(path))
+                    {
</code_context>

<issue_to_address>
**suggestion:** Consider normalizing GUID case before passing to GUIDToAssetPath.

Unity asset GUIDs are usually lowercase hex. Here you strip hyphens but keep the original casing; if callers pass uppercase/mixed‑case GUIDs, `GUIDToAssetPath` may not resolve them. Consider normalizing with `strValue.Replace("-", "").ToLowerInvariant()` before calling `GUIDToAssetPath` to handle case variations robustly.

```suggestion
                    var normalizedGuid = strValue.Replace("-", "").ToLowerInvariant();
                    string path = UnityEditor.AssetDatabase.GUIDToAssetPath(normalizedGuid);
```
</issue_to_address>

Sourcery is free for open source - if you like our reviews please consider sharing them ✨
Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 0

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
MCPForUnity/Editor/Helpers/ComponentOps.cs (1)

256-263: Inconsistency: GetAccessibleMembers doesn't list inherited [SerializeField] fields that SetProperty can set.

SetProperty uses FindSerializedFieldInHierarchy to walk the inheritance chain and set inherited [SerializeField] fields, but GetAccessibleMembers calls GetFields(BindingFlags.NonPublic | BindingFlags.Instance) without DeclaredOnly or hierarchy traversal. In .NET, private fields are not inherited by a single GetFields call—the method will only find fields declared directly on the target type. This means SetProperty can set inherited serialized fields that won't appear in the list returned by GetAccessibleMembers, breaking the API contract.

Update GetAccessibleMembers to walk the inheritance hierarchy when collecting private [SerializeField] fields, similar to FindSerializedFieldInHierarchy.

🧹 Nitpick comments (1)
MCPForUnity/Runtime/Serialization/UnityTypeConverters.cs (1)

436-450: Efficient GUID validation.

The manual hex validation avoids allocating a Guid struct and correctly handles both hyphenated and non-hyphenated formats.

Alternatively, System.Guid.TryParse(str, out _) could be used if readability is preferred over the minor allocation.

📜 Review details

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between f4a8b05 and 327d15f.

📒 Files selected for processing (2)
  • MCPForUnity/Editor/Helpers/ComponentOps.cs
  • MCPForUnity/Runtime/Serialization/UnityTypeConverters.cs
🧰 Additional context used
🧠 Learnings (3)
📚 Learning: 2025-12-29T15:23:11.613Z
Learnt from: msanatan
Repo: CoplayDev/unity-mcp PR: 491
File: MCPForUnity/Editor/Windows/EditorPrefs/EditorPrefsWindow.cs:78-115
Timestamp: 2025-12-29T15:23:11.613Z
Learning: In MCPForUnity, prefer relying on the established testing process to catch UI initialization issues instead of adding defensive null checks for UI elements in editor windows. This means during reviews, verify that tests cover UI initialization paths and that code avoids repetitive null-check boilerplate in Editor Windows. If a UI element can be null, ensure there is a well-tested fallback or that its initialization is guaranteed by design, rather than sprinkling null checks throughout editor code.

Applied to files:

  • MCPForUnity/Editor/Helpers/ComponentOps.cs
📚 Learning: 2025-10-13T13:27:23.040Z
Learnt from: msanatan
Repo: CoplayDev/unity-mcp PR: 316
File: TestProjects/UnityMCPTests/Assets/Tests/EditMode/Resources.meta:1-8
Timestamp: 2025-10-13T13:27:23.040Z
Learning: UnityMcpBridge is a legacy project kept for backwards compatibility; MCPForUnity is the only active Unity plugin project. GUID collisions between UnityMcpBridge and MCPForUnity are acceptable.

Applied to files:

  • MCPForUnity/Runtime/Serialization/UnityTypeConverters.cs
📚 Learning: 2025-09-03T16:00:55.839Z
Learnt from: dsarno
Repo: CoplayDev/unity-mcp PR: 0
File: :0-0
Timestamp: 2025-09-03T16:00:55.839Z
Learning: ComponentResolver in UnityMcpBridge/Editor/Tools/ManageGameObject.cs is a nested static class within ManageGameObject, not a sibling type. The `using static MCPForUnity.Editor.Tools.ManageGameObject;` import is required to access ComponentResolver methods directly without the outer class qualifier.

Applied to files:

  • MCPForUnity/Runtime/Serialization/UnityTypeConverters.cs
🧬 Code graph analysis (1)
MCPForUnity/Runtime/Serialization/UnityTypeConverters.cs (2)
MCPForUnity/Editor/Helpers/ObjectResolver.cs (3)
  • UnityEngine (32-105)
  • UnityEngine (170-199)
  • GameObject (110-129)
MCPForUnity/Editor/Helpers/UnityTypeResolver.cs (3)
  • Type (103-108)
  • Type (113-118)
  • Type (123-128)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: Sourcery review
🔇 Additional comments (7)
MCPForUnity/Editor/Helpers/ComponentOps.cs (2)

200-224: LGTM - Proper fix for inherited serialized fields.

The switch to FindSerializedFieldInHierarchy correctly addresses the issue where Type.GetField() with NonPublic only returns fields declared directly on that type, missing inherited private [SerializeField] fields.


271-300: Well-structured hierarchy traversal.

The implementation correctly uses DeclaredOnly to fetch only fields declared on each type in the chain, then manually walks up via BaseType. The case-insensitive comparison and [SerializeField] check ensure only the right fields are matched.

MCPForUnity/Runtime/Serialization/UnityTypeConverters.cs (5)

321-342: Good GUID auto-detection with path fallback.

The string handling correctly distinguishes between GUID strings (32 hex chars) and asset paths, with appropriate warnings when resolution fails.


348-361: LGTM - GUID-based asset resolution.

Correctly extracts and normalizes the GUID, then resolves via AssetDatabase.


363-402: Well-designed smart type conversion.

The cascading type resolution logic handles common Unity patterns well:

  • Direct type match via IsAssignableFrom
  • Transform extraction from GameObject
  • Component fetching from GameObject via GetComponent

Error messages include context (object name, instance ID) which aids debugging.


404-419: LGTM - Path resolution with helpful diagnostics.

The path-based loading and the unrecognized format warning (which includes the object content) provide good debugging information.


421-433: Good defensive handling for edge cases.

The unexpected token handling and non-editor fallback properly skip tokens to maintain reader state integrity, preventing cascading deserialization errors.

✏️ Tip: You can disable this entire section by setting review_details to false in your review settings.

@dsarno dsarno force-pushed the fix/object-reference-deserialization branch from 327d15f to 1820722 Compare January 14, 2026 05:50
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🤖 Fix all issues with AI agents
In `@MCPForUnity/Runtime/Serialization/UnityTypeConverters.cs`:
- Around line 426-433: The non-Editor deserialization branch in
UnityTypeConverters (UnityEngineObjectConverter) misuses reader.ReadAsString()
when reader.TokenType == JsonToken.String, which tries to read the next token
and can corrupt the reader; replace that specific call with reader.Skip() (or
simply call reader.Skip() for any non-StartObject token) so the current token is
advanced correctly, keep the JObject.Load(reader) for StartObject, and then
return existingValue as before.
♻️ Duplicate comments (2)
MCPForUnity/Runtime/Serialization/UnityTypeConverters.cs (2)

324-334: GUID case normalization already flagged.

The GUID handling strips hyphens but doesn't normalize to lowercase. Unity's GUIDToAssetPath may not resolve mixed-case GUIDs. This was noted in a previous review.


350-361: Same GUID normalization issue applies here.

Line 352 should also normalize to lowercase for consistency with the fix suggested for line 326.

📜 Review details

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 327d15f and 1820722.

📒 Files selected for processing (2)
  • MCPForUnity/Editor/Helpers/ComponentOps.cs
  • MCPForUnity/Runtime/Serialization/UnityTypeConverters.cs
🚧 Files skipped from review as they are similar to previous changes (1)
  • MCPForUnity/Editor/Helpers/ComponentOps.cs
🧰 Additional context used
🧠 Learnings (2)
📚 Learning: 2025-10-13T13:27:23.040Z
Learnt from: msanatan
Repo: CoplayDev/unity-mcp PR: 316
File: TestProjects/UnityMCPTests/Assets/Tests/EditMode/Resources.meta:1-8
Timestamp: 2025-10-13T13:27:23.040Z
Learning: UnityMcpBridge is a legacy project kept for backwards compatibility; MCPForUnity is the only active Unity plugin project. GUID collisions between UnityMcpBridge and MCPForUnity are acceptable.

Applied to files:

  • MCPForUnity/Runtime/Serialization/UnityTypeConverters.cs
📚 Learning: 2025-09-03T16:00:55.839Z
Learnt from: dsarno
Repo: CoplayDev/unity-mcp PR: 0
File: :0-0
Timestamp: 2025-09-03T16:00:55.839Z
Learning: ComponentResolver in UnityMcpBridge/Editor/Tools/ManageGameObject.cs is a nested static class within ManageGameObject, not a sibling type. The `using static MCPForUnity.Editor.Tools.ManageGameObject;` import is required to access ComponentResolver methods directly without the outer class qualifier.

Applied to files:

  • MCPForUnity/Runtime/Serialization/UnityTypeConverters.cs
🧬 Code graph analysis (1)
MCPForUnity/Runtime/Serialization/UnityTypeConverters.cs (1)
MCPForUnity/Editor/Helpers/ObjectResolver.cs (3)
  • UnityEngine (32-105)
  • UnityEngine (170-199)
  • GameObject (110-129)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: Sourcery review
🔇 Additional comments (3)
MCPForUnity/Runtime/Serialization/UnityTypeConverters.cs (3)

368-401: Well-designed type conversion hierarchy.

The cascading type checks handle common Unity patterns elegantly:

  • Direct type assignment
  • GameObject → Transform extraction
  • GameObject → Component via GetComponent

The detailed warning messages with instance IDs and names will aid debugging.


404-419: LGTM!

Path-based resolution and the unrecognized format fallback are correctly implemented. Including the JSON object in the warning message aids debugging.


436-450: LGTM!

The GUID validation correctly handles 32 hex characters with or without hyphens, and properly accepts both upper and lowercase hex digits for flexible input matching.

✏️ Tip: You can disable this entire section by setting review_details to false in your review settings.

Fixes deserialization of Transform, Component, and ScriptableObject
references in manage_components set_property action.

Changes to UnityEngineObjectConverter.ReadJson:
- Add GUID support: {"guid": "abc123..."} loads assets by GUID
- Add path support: {"path": "Assets/..."} loads assets by path
- Smart type conversion for instanceID:
  - When expecting Transform but got GameObject, auto-extract .transform
  - When expecting Component but got GameObject, auto-call GetComponent()
- Return null with warnings instead of throwing exceptions on failures
- Auto-detect GUID strings (32 hex chars) vs asset paths

Changes to ComponentOps.SetProperty:
- Add FindSerializedFieldInHierarchy() to traverse inheritance chain
- Fixes issue where private [SerializeField] fields from base classes
  were not found (Type.GetField with NonPublic only searches declared type)

Supported input formats for object references:
- {"instanceID": 12345} - Scene objects (GameObjects, Components)
- {"guid": "abc123..."} - Assets by GUID
- {"path": "Assets/Foo.asset"} - Assets by path
- "Assets/Foo.asset" - Assets by path (string)
- "abc123..." - Assets by GUID (string, auto-detected)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
@dsarno dsarno force-pushed the fix/object-reference-deserialization branch from 1820722 to 3e30c68 Compare January 14, 2026 06:09
@dsarno dsarno merged commit b874922 into CoplayDev:main Jan 14, 2026
2 checks passed
@dsarno dsarno deleted the fix/object-reference-deserialization branch January 15, 2026 16:04
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.

1 participant