This commit is contained in:
@@ -0,0 +1,150 @@
|
||||
#nullable enable
|
||||
|
||||
using System;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
|
||||
namespace ShrinkNetwork
|
||||
{
|
||||
public sealed class ShrinkMessagePackNetworkSerializer : IShrinkNetworkSerializer
|
||||
{
|
||||
private readonly MethodInfo _serializeMethod;
|
||||
private readonly MethodInfo _deserializeMethod;
|
||||
private readonly object? _serializerOptions;
|
||||
|
||||
public ShrinkMessagePackNetworkSerializer()
|
||||
{
|
||||
var serializerType = Type.GetType("MessagePack.MessagePackSerializer, MessagePack");
|
||||
if (serializerType == null)
|
||||
{
|
||||
throw new InvalidOperationException(
|
||||
"MessagePack assembly was not found. Please install MessagePack-CSharp before using ShrinkMessagePackNetworkSerializer.");
|
||||
}
|
||||
|
||||
_serializerOptions = ResolveSerializerOptions(serializerType.Assembly);
|
||||
|
||||
var serializeMethod = serializerType
|
||||
.GetMethods(BindingFlags.Public | BindingFlags.Static)
|
||||
.FirstOrDefault(m =>
|
||||
{
|
||||
if (m.Name != "Serialize")
|
||||
return false;
|
||||
var parameters = m.GetParameters();
|
||||
return parameters.Length >= 2 &&
|
||||
parameters[0].ParameterType == typeof(Type) &&
|
||||
parameters[1].ParameterType == typeof(object);
|
||||
});
|
||||
|
||||
var deserializeMethod = serializerType
|
||||
.GetMethods(BindingFlags.Public | BindingFlags.Static)
|
||||
.FirstOrDefault(m =>
|
||||
{
|
||||
if (m.Name != "Deserialize")
|
||||
return false;
|
||||
var parameters = m.GetParameters();
|
||||
return parameters.Length >= 2 &&
|
||||
parameters[0].ParameterType == typeof(Type) &&
|
||||
(parameters[1].ParameterType == typeof(byte[]) ||
|
||||
parameters[1].ParameterType == typeof(ReadOnlyMemory<byte>));
|
||||
});
|
||||
|
||||
if (serializeMethod == null || deserializeMethod == null)
|
||||
throw new MissingMethodException("MessagePack serialize/deserialize API not found.");
|
||||
|
||||
_serializeMethod = serializeMethod;
|
||||
_deserializeMethod = deserializeMethod;
|
||||
}
|
||||
|
||||
public byte[] Serialize(object value)
|
||||
{
|
||||
if (value == null)
|
||||
return Array.Empty<byte>();
|
||||
|
||||
var parameters = BuildParameters(_serializeMethod, value.GetType(), value, _serializerOptions);
|
||||
return (byte[])_serializeMethod.Invoke(null, parameters)!;
|
||||
}
|
||||
|
||||
public object Deserialize(byte[] payload, Type type)
|
||||
{
|
||||
payload ??= Array.Empty<byte>();
|
||||
var parameters = BuildParameters(_deserializeMethod, type, payload, _serializerOptions);
|
||||
return _deserializeMethod.Invoke(null, parameters)
|
||||
?? throw new InvalidOperationException($"MessagePack returned null for type {type.FullName}.");
|
||||
}
|
||||
|
||||
public T Deserialize<T>(byte[] payload)
|
||||
{
|
||||
return (T)Deserialize(payload, typeof(T));
|
||||
}
|
||||
|
||||
private static object?[] BuildParameters(MethodInfo method, Type type, object valueOrBytes, object? serializerOptions)
|
||||
{
|
||||
var parameters = method.GetParameters();
|
||||
var args = new object?[parameters.Length];
|
||||
|
||||
if (parameters.Length > 0)
|
||||
args[0] = type;
|
||||
if (parameters.Length > 1)
|
||||
args[1] = ConvertPrimaryArgument(parameters[1].ParameterType, valueOrBytes);
|
||||
|
||||
for (var i = 2; i < parameters.Length; i++)
|
||||
{
|
||||
args[i] = ResolveAdditionalArgument(parameters[i], serializerOptions);
|
||||
}
|
||||
|
||||
return args;
|
||||
}
|
||||
|
||||
private static object? ResolveAdditionalArgument(ParameterInfo parameter, object? serializerOptions)
|
||||
{
|
||||
if (serializerOptions != null && parameter.ParameterType.IsInstanceOfType(serializerOptions))
|
||||
return serializerOptions;
|
||||
|
||||
return parameter.HasDefaultValue
|
||||
? parameter.DefaultValue
|
||||
: GetDefault(parameter.ParameterType);
|
||||
}
|
||||
|
||||
private static object ConvertPrimaryArgument(Type parameterType, object value)
|
||||
{
|
||||
if (parameterType == typeof(ReadOnlyMemory<byte>) && value is byte[] bytes)
|
||||
return new ReadOnlyMemory<byte>(bytes);
|
||||
|
||||
return value;
|
||||
}
|
||||
|
||||
private static object? ResolveSerializerOptions(Assembly serializerAssembly)
|
||||
{
|
||||
var contractlessResolverType = serializerAssembly.GetType("MessagePack.Resolvers.ContractlessStandardResolver");
|
||||
if (contractlessResolverType != null)
|
||||
{
|
||||
var optionsField = contractlessResolverType.GetField("Options",
|
||||
BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Static);
|
||||
var options = optionsField?.GetValue(null);
|
||||
if (options != null)
|
||||
return options;
|
||||
|
||||
var instanceField = contractlessResolverType.GetField("Instance",
|
||||
BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Static);
|
||||
var instance = instanceField?.GetValue(null);
|
||||
if (instance != null)
|
||||
{
|
||||
var optionsType = serializerAssembly.GetType("MessagePack.MessagePackSerializerOptions");
|
||||
var standardProperty = optionsType?.GetProperty("Standard", BindingFlags.Public | BindingFlags.Static);
|
||||
var standardOptions = standardProperty?.GetValue(null);
|
||||
var withResolverMethod = optionsType?.GetMethod("WithResolver", BindingFlags.Public | BindingFlags.Instance);
|
||||
var resolvedOptions = withResolverMethod?.Invoke(standardOptions, new[] { instance });
|
||||
if (resolvedOptions != null)
|
||||
return resolvedOptions;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private static object? GetDefault(Type type)
|
||||
{
|
||||
return type.IsValueType ? Activator.CreateInstance(type) : null;
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user