diff --git a/src/MessagePack/Formatters/PrimitiveObjectFormatter.cs b/src/MessagePack/Formatters/PrimitiveObjectFormatter.cs index 553251de..eefa464a 100644 --- a/src/MessagePack/Formatters/PrimitiveObjectFormatter.cs +++ b/src/MessagePack/Formatters/PrimitiveObjectFormatter.cs @@ -32,6 +32,18 @@ namespace MessagePack.Formatters } + public static bool IsSupportedType(Type type, TypeInfo typeInfo, object value) + { + if (value == null) return true; + if (typeToJumpCode.ContainsKey(type)) return true; + if (typeInfo.IsEnum) return true; + + if (value is System.Collections.IDictionary) return true; + if (value is System.Collections.ICollection) return true; + + return false; + } + public int Serialize(ref byte[] bytes, int offset, object value, IFormatterResolver formatterResolver) { if (value == null) diff --git a/src/MessagePack/Resolvers/DynamicObjectResolver.cs b/src/MessagePack/Resolvers/DynamicObjectResolver.cs index a03cbec0..6e68e96f 100644 --- a/src/MessagePack/Resolvers/DynamicObjectResolver.cs +++ b/src/MessagePack/Resolvers/DynamicObjectResolver.cs @@ -110,6 +110,11 @@ namespace MessagePack.Resolvers static FormatterCache() { + if (typeof(T) == typeof(object)) + { + return; + } + var ti = typeof(T).GetTypeInfo(); if (ti.IsNullable()) { diff --git a/src/MessagePack/Resolvers/StandardResolver.cs b/src/MessagePack/Resolvers/StandardResolver.cs index bd489e1f..37baa0d9 100644 --- a/src/MessagePack/Resolvers/StandardResolver.cs +++ b/src/MessagePack/Resolvers/StandardResolver.cs @@ -1,4 +1,11 @@ using MessagePack.Formatters; +using System.Linq; +using MessagePack.Internal; +using System; +using System.Reflection; +using System.Collections.Generic; +using MessagePack.Resolvers; +using System.Linq.Expressions; namespace MessagePack.Resolvers { @@ -65,8 +72,26 @@ namespace MessagePack.Resolvers static readonly IFormatterResolver[] resolvers = new[] { - StandardResolver.Instance, + BuiltinResolver.Instance, // Try Builtin + + AttributeFormatterResolver.Instance, // Try use [MessagePackFormatter] + +#if !NETSTANDARD1_4 + MessagePack.Unity.UnityResolver.Instance, +#endif + +#if !ENABLE_IL2CPP + + DynamicEnumResolver.Instance, // Try Enum + DynamicGenericResolver.Instance, // Try Array, Tuple, Collection + DynamicUnionResolver.Instance, // Try Union(Interface) + DynamicObjectResolver.Instance, // Try Object +#endif + DynamicContractlessObjectResolver.Instance, + + // finally, try primitive -> dynamic contractless + DynamicObjectFallbackResolver.Instance }; ContractlessStandardResolver() @@ -96,4 +121,110 @@ namespace MessagePack.Resolvers } } } +} + +namespace MessagePack.Internal +{ + /// + /// In `object`, when serializing resolve by concrete type and when deserializing use primitive. + /// + internal class DynamicObjectFallbackResolver : IFormatterResolver + { + public static IFormatterResolver Instance = new DynamicObjectFallbackResolver(); + + DynamicObjectFallbackResolver() + { + + } + + public IMessagePackFormatter GetFormatter() + { + return FormatterCache.formatter; + } + + static class FormatterCache + { + public static readonly IMessagePackFormatter formatter; + + static FormatterCache() + { + formatter = (typeof(T) == typeof(object)) + ? (IMessagePackFormatter)(object)DynamicObjectFallbackResolverFormatter.Instance + : null; + } + } + } + + internal class DynamicObjectFallbackResolverFormatter : IMessagePackFormatter + { + internal delegate int SerializeMethod(object dynamicContractlessFormatter, ref byte[] bytes, int offset, object value, IFormatterResolver formatterResolver); + + internal static readonly IMessagePackFormatter Instance = new DynamicObjectFallbackResolverFormatter(); + + static readonly System.Collections.Generic.Dictionary> serializers = new Dictionary>(); + + DynamicObjectFallbackResolverFormatter() + { + + } + + public int Serialize(ref byte[] bytes, int offset, object value, IFormatterResolver formatterResolver) + { + if (value == null) + { + return MessagePackBinary.WriteNil(ref bytes, offset); + } + + var type = value.GetType(); + var ti = type.GetTypeInfo(); + + if (PrimitiveObjectFormatter.IsSupportedType(type, ti, value)) + { + return PrimitiveObjectFormatter.Instance.Serialize(ref bytes, offset, value, formatterResolver); + } + + KeyValuePair formatterAndDelegate; + lock (serializers) + { + if (!serializers.TryGetValue(type, out formatterAndDelegate)) + { + var formatter = DynamicContractlessObjectResolver.Instance.GetFormatterDynamic(type); + if (formatter == null) + { + throw new FormatterNotRegisteredException(type.FullName + " is not registered in this resolver. resolver:" + typeof(DynamicContractlessObjectResolver).Name); + } + + var formatterType = typeof(IMessagePackFormatter<>).MakeGenericType(type); + var param0 = Expression.Parameter(typeof(object), "formatter"); + var param1 = Expression.Parameter(typeof(byte[]).MakeByRefType(), "bytes"); + var param2 = Expression.Parameter(typeof(int), "offset"); + var param3 = Expression.Parameter(typeof(object), "value"); + var param4 = Expression.Parameter(typeof(IFormatterResolver), "formatterResolver"); + + var serializeMethodInfo = formatterType.GetRuntimeMethod("Serialize", new[] { typeof(byte[]).MakeByRefType(), typeof(int), type, typeof(IFormatterResolver) }); + + var body = Expression.Call( + Expression.Convert(param0, formatterType), + serializeMethodInfo, + param1, + param2, + ti.IsValueType ? Expression.Unbox(param3, type) : Expression.Convert(param3, type), + param4); + + var lambda = Expression.Lambda(body, param0, param1, param2, param3, param4).Compile(); + + formatterAndDelegate = new KeyValuePair(formatter, lambda); + + serializers[type] = formatterAndDelegate; + } + } + + return formatterAndDelegate.Value(formatterAndDelegate.Key, ref bytes, offset, value, formatterResolver); + } + + public object Deserialize(byte[] bytes, int offset, IFormatterResolver formatterResolver, out int readSize) + { + return PrimitiveObjectFormatter.Instance.Deserialize(bytes, offset, formatterResolver, out readSize); + } + } } \ No newline at end of file diff --git a/tests/MessagePack.Tests/ContractlessStandardResolverTest.cs b/tests/MessagePack.Tests/ContractlessStandardResolverTest.cs new file mode 100644 index 00000000..dee8e406 --- /dev/null +++ b/tests/MessagePack.Tests/ContractlessStandardResolverTest.cs @@ -0,0 +1,51 @@ +using System; +using System.Collections; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using Xunit; + +namespace MessagePack.Tests +{ + public class ContractlessStandardResolverTest + { + public class Address + { + public string Street { get; set; } + } + + public class Person + { + public string Name { get; set; } + public object[] /*Address*/ Addresses { get; set; } + } + + + [Fact] + public void SimpleTest() + { + var p = new Person + { + Name = "John", + Addresses = new[] + { + new Address { Street = "St." }, + new Address { Street = "Ave." } + } + }; + + var result = MessagePack.MessagePackSerializer.Serialize(p, MessagePack.Resolvers.ContractlessStandardResolver.Instance); + + MessagePackSerializer.ToJson(result).Is(@"{""Name"":""John"",""Addresses"":[{""Street"":""St.""},{""Street"":""Ave.""}]}"); + + var p2 = MessagePack.MessagePackSerializer.Deserialize(result, MessagePack.Resolvers.ContractlessStandardResolver.Instance); + p2.Name.Is("John"); + var addresses = p2.Addresses as IList; + var d1 = addresses[0] as IDictionary; + var d2 = addresses[1] as IDictionary; + (d1["Street"] as string).Is("St."); + (d2["Street"] as string).Is("Ave."); + } + } +} diff --git a/tests/MessagePack.Tests/MessagePack.Tests.csproj b/tests/MessagePack.Tests/MessagePack.Tests.csproj index 34cf1e8f..fa6fafc1 100644 --- a/tests/MessagePack.Tests/MessagePack.Tests.csproj +++ b/tests/MessagePack.Tests/MessagePack.Tests.csproj @@ -105,6 +105,7 @@ +