diff --git a/IPA.Loader/Config/Stores/Attributes.cs b/IPA.Loader/Config/Stores/Attributes.cs index 5c8e22b1..def1d292 100644 --- a/IPA.Loader/Config/Stores/Attributes.cs +++ b/IPA.Loader/Config/Stores/Attributes.cs @@ -21,6 +21,40 @@ namespace IPA.Config.Stores.Attributes [AttributeUsage(AttributeTargets.Field | AttributeTargets.Property, AllowMultiple = false, Inherited = true)] public sealed class NonNullableAttribute : Attribute { } + /// + /// Indicates that a given field or property in an object being wrapped by + /// should be serialized and deserialized using the provided converter instead of the default mechanism. + /// + [AttributeUsage(AttributeTargets.Field | AttributeTargets.Property, AllowMultiple = false, Inherited = true)] + public sealed class UseConverterAttribute : Attribute + { + /// + /// Gets the type of the converter to use. + /// + public Type ConverterType { get; private set; } + /// + /// Gets the target type of the converter if it is avaliable at instantiation time, otherwise + /// . + /// + public Type ConverterTargetType => ConverterType.BaseType.IsGenericType ? + ConverterType.BaseType.GetGenericArguments()[0] : + null; + + /// + /// Creates a new with a given . + /// + /// tpy type to assign to + public UseConverterAttribute(Type converterType) + { + ConverterType = converterType; + + var implInterface = ConverterType.GetInterfaces().Contains(typeof(IValueConverter)); + var inheritGeneric = ConverterType.BaseType.IsGenericType && + ConverterType.BaseType.GetGenericTypeDefinition() == typeof(ValueConverter<>); + if (!implInterface && !inheritGeneric) throw new ArgumentException("Type is not a value converter!"); + } + } + /// /// Specifies a name for the serialized field or property in an object being wrapped by /// that is different from the member name itself. diff --git a/IPA.Loader/Config/Stores/GeneratedStore.cs b/IPA.Loader/Config/Stores/GeneratedStore.cs index 478753b4..bcc6426c 100644 --- a/IPA.Loader/Config/Stores/GeneratedStore.cs +++ b/IPA.Loader/Config/Stores/GeneratedStore.cs @@ -245,6 +245,10 @@ namespace IPA.Config.Stores public bool IsField; public bool IsNullable; // signifies whether this is a Nullable + public bool IsGenericConverter; // used so we can call directly to the generic version if it is + public Type Converter; + public Type ConverterTarget; + // invalid for objects with IsNullabe false public Type NullableWrappedType => Nullable.GetUnderlyingType(Type); // invalid for objects with IsNullabe false @@ -281,28 +285,33 @@ namespace IPA.Config.Stores // TODO: support converters - bool ProcessAttributesFor(MemberInfo member, Type memberType, out string name, out bool allowNull, out bool isNullable) + static bool ProcessAttributesFor(ref SerializedMemberInfo member) { - var attrs = member.GetCustomAttributes(true); + var attrs = member.Member.GetCustomAttributes(true); var ignores = attrs.Select(o => o as IgnoreAttribute).NonNull(); if (ignores.Any()) // we ignore { - name = null; - allowNull = false; - isNullable = false; return false; } var nonNullables = attrs.Select(o => o as NonNullableAttribute).NonNull(); - name = member.Name; - isNullable = memberType.IsGenericType - && memberType.GetGenericTypeDefinition() == typeof(Nullable<>); - allowNull = !nonNullables.Any() && (!memberType.IsValueType || isNullable); + member.Name = member.Member.Name; + member.IsNullable = member.Type.IsGenericType + && member.Type.GetGenericTypeDefinition() == typeof(Nullable<>); + member.AllowNull = !nonNullables.Any() && (!member.Type.IsValueType || member.IsNullable); var nameAttr = attrs.Select(o => o as SerializedNameAttribute).NonNull().FirstOrDefault(); if (nameAttr != null) - name = nameAttr.Name; + member.Name = nameAttr.Name; + + var converterAttr = attrs.Select(o => o as UseConverterAttribute).NonNull().FirstOrDefault(); + if (converterAttr != null) + { + member.Converter = converterAttr.ConverterType; + member.ConverterTarget = converterAttr.ConverterTargetType; + member.IsGenericConverter = member.ConverterTarget != null; + } return true; } @@ -328,7 +337,7 @@ namespace IPA.Config.Stores Type = prop.PropertyType }; - if (!ProcessAttributesFor(smi.Member, smi.Type, out smi.Name, out smi.AllowNull, out smi.IsNullable)) continue; + if (!ProcessAttributesFor(ref smi)) continue; structure.Add(smi); } @@ -346,7 +355,7 @@ namespace IPA.Config.Stores Type = field.FieldType }; - if (!ProcessAttributesFor(smi.Member, smi.Type, out smi.Name, out smi.AllowNull, out smi.IsNullable)) continue; + if (!ProcessAttributesFor(ref smi)) continue; structure.Add(smi); } diff --git a/IPA.Loader/Config/Stores/ValueConverter.cs b/IPA.Loader/Config/Stores/ValueConverter.cs new file mode 100644 index 00000000..aec5dde6 --- /dev/null +++ b/IPA.Loader/Config/Stores/ValueConverter.cs @@ -0,0 +1,66 @@ +using IPA.Config.Data; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace IPA.Config.Stores +{ + /// + /// The base interface for a value converter for use by objects generated by + /// . + /// + /// + /// The object returned from , if fed into , + /// should return equivalent structures. Similarly, if the result of + /// is fed into , the resulting object should be equivalent to the one passed to + /// . + /// + public interface IValueConverter + { + /// + /// Converts the given object to a . + /// + /// the object to convert + /// a representation of as a structure + Value ToValue(object obj); + /// + /// Converts the given to the object type handled by this converter. + /// + /// the to deserialize + /// the deserialized object + object FromValue(Value value); + /// + /// Gets the type that this handles. + /// + Type Type { get; } + } + + /// + /// A strongly-typed . + /// + /// the type of object to handle + /// + public abstract class ValueConverter : IValueConverter + { + /// + /// Converts the given object to a . + /// + /// the object to convert + /// a representation of as a structure + /// + public abstract Value ToValue(T obj); + /// + /// Converts the given to the object type handled by this converter. + /// + /// the to deserialize + /// the deserialized object + /// + public abstract T FromValue(Value value); + + Value IValueConverter.ToValue(object obj) => ToValue((T)obj); + object IValueConverter.FromValue(Value value) => FromValue(value); + Type IValueConverter.Type => typeof(T); + } +} diff --git a/IPA.Loader/IPA.Loader.csproj b/IPA.Loader/IPA.Loader.csproj index 9c3e5602..315b9842 100644 --- a/IPA.Loader/IPA.Loader.csproj +++ b/IPA.Loader/IPA.Loader.csproj @@ -97,6 +97,7 @@ +