diff --git a/IGDB/IGDBApi.cs b/IGDB/IGDBApi.cs index ed84fb0..ba514e3 100644 --- a/IGDB/IGDBApi.cs +++ b/IGDB/IGDBApi.cs @@ -3,6 +3,7 @@ using System.IO; using System.Threading.Tasks; using IGDB.Models; +using IGDB.Serialization; using Newtonsoft.Json; using Newtonsoft.Json.Serialization; using RestEase; diff --git a/IGDB/Identity.cs b/IGDB/Identity.cs index ac3f74b..527aad1 100644 --- a/IGDB/Identity.cs +++ b/IGDB/Identity.cs @@ -49,5 +49,10 @@ public IdentitiesOrValues(object[] values) var list = values.Select(value => (T)value).ToArray(); Values = list; } + + public IdentitiesOrValues(T[] values) + { + Values = values; + } } } \ No newline at end of file diff --git a/IGDB/Models/ExternalGame.cs b/IGDB/Models/ExternalGame.cs index 8c3757e..f6347ab 100644 --- a/IGDB/Models/ExternalGame.cs +++ b/IGDB/Models/ExternalGame.cs @@ -12,7 +12,7 @@ public class ExternalGame : ITimestamps, IIdentifier, IHasChecksum public IdentityOrValue Game { get; set; } public long? Id { get; set; } - public ExternalGameMedia Media { get; set; } + public ExternalGameMedia? Media { get; set; } public string Name { get; set; } diff --git a/IGDB/Serialization/IdentityConverter.cs b/IGDB/Serialization/IdentityConverter.cs index 0e433a8..5258d97 100644 --- a/IGDB/Serialization/IdentityConverter.cs +++ b/IGDB/Serialization/IdentityConverter.cs @@ -1,17 +1,17 @@ using System; using System.Collections.Generic; using System.Linq; +using System.Reflection; using Newtonsoft.Json; -using Newtonsoft.Json.Linq; +using static IGDB.Serialization.LambdaActivator; -namespace IGDB +namespace IGDB.Serialization { public class IdentityConverter : JsonConverter { public override bool CanConvert(Type objectType) { - return IsAssignableToGenericType(objectType, typeof(IdentityOrValue<>)) || - IsAssignableToGenericType(objectType, typeof(IdentitiesOrValues<>)); + return IsIdentityOrValue(objectType) || IsIdentitiesOrValues(objectType); } public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer) @@ -24,7 +24,7 @@ public override object ReadJson(JsonReader reader, Type objectType, object exist var expandedType = objectType.GetGenericArguments()[0]; var value = reader.Value; - if (IsAssignableToGenericType(objectType, typeof(IdentitiesOrValues<>))) + if (IsIdentitiesOrValues(objectType)) { if (reader.TokenType != JsonToken.StartArray) { @@ -48,28 +48,33 @@ public override object ReadJson(JsonReader reader, Type objectType, object exist } } + var valuesActivator = GetValuesActivator(objectType); + var identitiesActivator = GetIdentitiesActivator(objectType); + // If any are objects, it means the IDs should be ignored if (values.All(v => v.GetType().IsAssignableFrom(typeof(long)))) { - return Activator.CreateInstance(objectType, values.Cast().ToArray()); + return identitiesActivator(values.Cast().ToArray()); } var objects = values.Where(v => !v.GetType().IsAssignableFrom(typeof(long))); var convertedValues = objects.ToArray(); - var ctor = objectType.GetConstructor(new[] { typeof(object[]) }); - return ctor.Invoke(new[] { convertedValues }); + return valuesActivator(new[] { convertedValues }); } - else if (IsAssignableToGenericType(objectType, typeof(IdentityOrValue<>))) + else if (IsIdentityOrValue(objectType)) { + var identityActivator = GetIdentityActivator(objectType); + var valueActivator = GetValueActivator(objectType); + if (reader.TokenType == JsonToken.StartObject) { // objects - return Activator.CreateInstance(objectType, serializer.Deserialize(reader, expandedType)); + return valueActivator(serializer.Deserialize(reader, expandedType)); } else if (reader.TokenType == JsonToken.Integer) { // int ids - return Activator.CreateInstance(objectType, (long)reader.Value); + return identityActivator((long)reader.Value); } } @@ -82,11 +87,11 @@ public override void WriteJson(JsonWriter writer, object value, JsonSerializer s { dynamic identity = value; - if (IsAssignableToGenericType(value.GetType(), typeof(IdentitiesOrValues<>))) + if (IsIdentitiesOrValues(value.GetType())) { serializer.Serialize(writer, identity.Ids ?? identity.Values ?? null); } - else if (IsAssignableToGenericType(value.GetType(), typeof(IdentityOrValue<>))) + else if (IsIdentityOrValue(value.GetType())) { serializer.Serialize(writer, identity.Id ?? identity.Value ?? null); } @@ -97,25 +102,81 @@ public override void WriteJson(JsonWriter writer, object value, JsonSerializer s } } - public static bool IsAssignableToGenericType(Type givenType, Type genericType) + private static readonly string IdentitiesOrValuesName = typeof(IdentitiesOrValues<>).Name; + private static readonly string IdentityOrValueName = typeof(IdentityOrValue<>).Name; + + public static bool IsIdentityOrValue(Type givenType) + { + return givenType.Name.Contains(IdentityOrValueName); + } + + public static bool IsIdentitiesOrValues(Type givenType) + { + return givenType.Name.Contains(IdentitiesOrValuesName); + } + + private static readonly IDictionary identitiesActivators + = new Dictionary(); + private static readonly IDictionary valuesActivators + = new Dictionary(); + private static readonly IDictionary identityActivators + = new Dictionary(); + private static readonly IDictionary valueActivators + = new Dictionary(); + + public static ObjectActivator GetIdentitiesActivator(Type objectType) + { + if (identitiesActivators.ContainsKey(objectType)) + { + return identitiesActivators[objectType]; + } + + ConstructorInfo ctor = objectType.GetConstructors().Skip(1).First(); + var activator = GetActivator(ctor); + identitiesActivators[objectType] = activator; + return activator; + } + + public static ObjectActivator GetValuesActivator(Type objectType) { - var interfaceTypes = givenType.GetInterfaces(); + if (valuesActivators.ContainsKey(objectType)) + { + return valuesActivators[objectType]; + } + + ConstructorInfo ctor = objectType.GetConstructors().Skip(2).First(); + var activator = GetActivator(ctor); + valuesActivators[objectType] = activator; + return activator; + } - foreach (var it in interfaceTypes) + public static ObjectActivator GetIdentityActivator(Type objectType) + { + if (identityActivators.ContainsKey(objectType)) { - if (it.IsGenericType && it.GetGenericTypeDefinition() == genericType) - return true; + return identityActivators[objectType]; } - if (givenType.IsGenericType && givenType.GetGenericTypeDefinition() == genericType) - return true; + ConstructorInfo ctor = objectType.GetConstructors().Skip(1).First(); + var activator = GetActivator(ctor); + identityActivators[objectType] = activator; + return activator; + } - Type baseType = givenType.BaseType; - if (baseType == null) return false; + public static ObjectActivator GetValueActivator(Type objectType) + { + if (valueActivators.ContainsKey(objectType)) + { + return valueActivators[objectType]; + } - return IsAssignableToGenericType(baseType, genericType); + ConstructorInfo ctor = objectType.GetConstructors().Skip(2).First(); + var activator = GetActivator(ctor); + valueActivators[objectType] = activator; + return activator; } } + } \ No newline at end of file diff --git a/IGDB/Serialization/LamdaActivator.cs b/IGDB/Serialization/LamdaActivator.cs new file mode 100644 index 0000000..fc5f9fe --- /dev/null +++ b/IGDB/Serialization/LamdaActivator.cs @@ -0,0 +1,47 @@ +using System; +using System.Linq.Expressions; +using System.Reflection; + +namespace IGDB.Serialization +{ + public delegate object ObjectActivator(params object[] args); + + /// + /// See: https://rogerjohansson.blog/2008/02/28/linq-expressions-creating-objects/ + /// + internal static class LambdaActivator + { + public static ObjectActivator GetActivator(ConstructorInfo ctor) + { + ParameterInfo[] paramsInfo = ctor.GetParameters(); + + ParameterExpression param = + Expression.Parameter(typeof(object[]), "args"); + + Expression[] argsExp = + new Expression[paramsInfo.Length]; + + for (int i = 0; i < paramsInfo.Length; i++) + { + Expression index = Expression.Constant(i); + Type paramType = paramsInfo[i].ParameterType; + + Expression paramAccessorExp = + Expression.ArrayIndex(param, index); + + Expression paramCastExp = + Expression.Convert(paramAccessorExp, paramType); + + argsExp[i] = paramCastExp; + } + + NewExpression newExp = Expression.New(ctor, argsExp); + + LambdaExpression lambda = + Expression.Lambda(typeof(ObjectActivator), newExp, param); + + ObjectActivator compiled = (ObjectActivator)lambda.Compile(); + return compiled; + } + } +} \ No newline at end of file diff --git a/IGDB/Serialization/UnixTimestampConverter.cs b/IGDB/Serialization/UnixTimestampConverter.cs index 3e3561f..37dabb0 100644 --- a/IGDB/Serialization/UnixTimestampConverter.cs +++ b/IGDB/Serialization/UnixTimestampConverter.cs @@ -2,7 +2,7 @@ using Newtonsoft.Json; using Newtonsoft.Json.Linq; -namespace IGDB +namespace IGDB.Serialization { public class UnixTimestampConverter : JsonConverter {