Skip to main content
Glama
UnityGenericReflectionConverter.cs7.44 kB
/* ┌──────────────────────────────────────────────────────────────────┐ │ Author: Ivan Murzak (https://github.com/IvanMurzak) │ │ Repository: GitHub (https://github.com/IvanMurzak/Unity-MCP) │ │ Copyright (c) 2025 Ivan Murzak │ │ Licensed under the Apache License, Version 2.0. │ │ See the LICENSE file in the project root for more information. │ └──────────────────────────────────────────────────────────────────┘ */ #nullable enable using System; using System.Collections.Generic; using System.Linq; using System.Reflection; using com.IvanMurzak.ReflectorNet; using com.IvanMurzak.ReflectorNet.Converter; using com.IvanMurzak.ReflectorNet.Model; using com.IvanMurzak.ReflectorNet.Utils; using com.IvanMurzak.Unity.MCP.Runtime.Extensions; using Microsoft.Extensions.Logging; using UnityEngine; using ILogger = Microsoft.Extensions.Logging.ILogger; namespace com.IvanMurzak.Unity.MCP.Reflection.Converter { public partial class UnityGenericReflectionConverter<T> : GenericReflectionConverter<T> { public override IEnumerable<FieldInfo>? GetSerializableFields(Reflector reflector, Type objType, BindingFlags flags, ILogger? logger = null) => objType.GetFields(flags) .Where(field => field.GetCustomAttribute<ObsoleteAttribute>() == null) .Where(field => field.IsPublic || field.IsPrivate && field.GetCustomAttribute<SerializeField>() != null); public override object? Deserialize( Reflector reflector, SerializedMember data, Type? fallbackType = null, string? fallbackName = null, int depth = 0, Logs? logs = null, ILogger? logger = null, DeserializationContext? context = null) { var type = fallbackType ?? typeof(T); if (typeof(UnityEngine.Object).IsAssignableFrom(type)) { return data.valueJsonElement .ToAssetObjectRef( reflector: reflector, suppressException: true, depth: depth, logs: logs, logger: logger) .FindAssetObject(type); } return base.Deserialize( reflector: reflector, data: data, fallbackType: fallbackType, fallbackName: fallbackName, depth: depth, logs: logs, logger: logger); } protected override bool SetValue( Reflector reflector, ref object? obj, Type type, System.Text.Json.JsonElement? value, int depth = 0, Logs? logs = null, ILogger? logger = null) { var originalObj = obj; var result = base.SetValue( reflector: reflector, obj: ref obj, type: type, value: value, depth: depth, logs: logs, logger: logger); // If obj became null but we had an object, and the value didn't explicitly say null, restore it. // This handles cases where TryPopulate is called with an existing object but no valueJsonElement. if (obj == null && originalObj != null) { var isExplicitNull = value.HasValue && value.Value.ValueKind == System.Text.Json.JsonValueKind.Null; if (!isExplicitNull) { obj = originalObj; } } return result; } protected override bool TryPopulateField( Reflector reflector, ref object? obj, Type objType, SerializedMember fieldValue, int depth = 0, Logs? logs = null, BindingFlags flags = BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic, ILogger? logger = null) { var padding = StringUtils.GetPadding(depth); if (obj == null) { logger?.LogError("{padding}obj is null in TryPopulateField for {field}", padding, fieldValue.name); logs?.Error($"obj is null in TryPopulateField for {fieldValue.name}", depth); return false; } var field = objType.GetField(fieldValue.name, flags); if (field == null) { logger?.LogError("{padding}Field {field} not found on {type}", padding, fieldValue.name, objType.GetTypeId()); logs?.Error($"Field {fieldValue.name} not found on {objType.GetTypeId()}", depth); return false; } try { var value = reflector.Deserialize(fieldValue, field.FieldType, depth: depth + 1, logs: logs, logger: logger); field.SetValue(obj, value); return true; } catch (Exception e) { logger?.LogError(e, "{padding}Failed to set field {field}", padding, fieldValue.name); logs?.Error($"Failed to set field {fieldValue.name}: {e.Message}", depth); return false; } } protected override bool TryPopulateProperty( Reflector reflector, ref object? obj, Type objType, SerializedMember member, int depth = 0, Logs? logs = null, BindingFlags flags = BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic, ILogger? logger = null) { var padding = StringUtils.GetPadding(depth); if (obj == null) { logger?.LogError("{padding}obj is null in TryPopulateProperty for '{property}'", padding, member.name); logs?.Error($"obj is null in TryPopulateProperty for '{member.name}'", depth); return false; } var property = objType.GetProperty(member.name, flags); if (property == null || !property.CanWrite) { logger?.LogError("{padding}Property '{property}' not found or not writable on '{type}'", padding, member.name, objType.GetTypeId()); logs?.Error($"Property '{member.name}' not found or not writable on {objType.GetTypeId()}", depth); return false; } try { var value = reflector.Deserialize(member, property.PropertyType, depth: depth + 1, logs: logs, logger: logger); property.SetValue(obj, value); return true; } catch (Exception e) { logger?.LogError(e, "{padding}Failed to set property '{property}'", padding, member.name); logs?.Error($"Failed to set property '{member.name}': {e.Message}", depth); return false; } } } }

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/IvanMurzak/Unity-MCP'

If you have feedback or need assistance with the MCP directory API, please join our Discord server