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 @@
+