using System; using System.Reflection; using System.Text.Json; namespace Sandbox.Internal; /// /// An implementation of SerializedObject which uses reflection to fill out properties. This is only accessible from /// tools by default. We (probably) shouldn't trust the client with such things, as they can access /// class ReflectionSerializedObject : SerializedObject { internal object TargetObject; public override string TypeName => TargetObject.GetType().Name; public override string TypeTitle => TypeName; public override bool IsValid => (TargetObject as IValid)?.IsValid() ?? TargetObject is not null; public ReflectionSerializedObject( object target ) { TargetObject = target; BuildProperties(); } void BuildProperties() { PropertyList ??= new(); var type = TargetObject.GetType(); var properties = type.GetProperties( BindingFlags.Instance | BindingFlags.Public | BindingFlags.IgnoreCase | BindingFlags.FlattenHierarchy ) .Where( IsPropertyAcceptable ); foreach ( var prop in properties ) { PropertyList.Add( new TypeSerializedProperty( this, prop ) ); } } private bool IsPropertyAcceptable( PropertyInfo x ) { if ( !x.CanRead ) return false; if ( x.GetMethod.IsStatic ) return false; var info = DisplayInfo.ForMember( x ); //if ( x.GetIndexParameters().Length > 0 ) return false; //if ( x.PropertyType.IsByRefLike ) return false; return info.Browsable; } } file class TypeSerializedProperty : SerializedProperty { private ReflectionSerializedObject Owner; private PropertyInfo Prop; private DisplayInfo Info; public override SerializedObject Parent => Owner; public override string DisplayName => Info.Name; public override string Name => Prop.Name; public override string Description => Info.Description; public override System.Type PropertyType => Prop.PropertyType; public override string GroupName => Info.Group; public TypeSerializedProperty( ReflectionSerializedObject typeSerializedObject, PropertyInfo prop ) { Owner = typeSerializedObject; Prop = prop; Info = DisplayInfo.ForMember( prop ); } public override void SetValue( T value ) { try { // correct type if ( value == null || value.GetType().IsAssignableTo( Prop.PropertyType ) ) { NotePreChange(); Prop.SetValue( Owner.TargetObject, value ); } else if ( Prop.PropertyType.IsEnum ) { NotePreChange(); var changedValue = Enum.ToObject( Prop.PropertyType, value ); if ( changedValue is not null ) { Prop.SetValue( Owner.TargetObject, changedValue ); } } else if ( value is IConvertible ) { NotePreChange(); var changedValue = Convert.ChangeType( value, Prop.PropertyType ); if ( changedValue is not null ) { Prop.SetValue( Owner.TargetObject, changedValue ); } } else { return; } NoteChanged(); } catch ( System.Exception e ) { var l = new Logger( "ReflectionSerializedProperty" ); l.Warning( e, $"Error setting {PropertyType} to {value} ({value?.GetType()})" ); } } public override T GetValue( T defaultValue ) { var value = Prop.GetValue( Owner.TargetObject ); if ( Prop.PropertyType.IsAssignableTo( typeof( T ) ) ) return (T)value; if ( typeof( T ) == typeof( string ) ) return (T)(object)$"{value}"; if ( Prop.PropertyType == typeof( string ) ) { return JsonSerializer.Deserialize( (string)value ); } var jsonElement = JsonSerializer.SerializeToElement( value ); return jsonElement.Deserialize(); } /// public override IEnumerable GetAttributes() { return Prop.GetCustomAttributes() ?? base.GetAttributes(); } }