Browse Source

Cleaned up Serializers, removed SerializationFormat

voice-allocs
RogueException 7 years ago
parent
commit
e0ce1813a2
14 changed files with 184 additions and 123 deletions
  1. +0
    -9
      src/Discord.Net.Rest/DiscordRestApiClient.cs
  2. +2
    -1
      src/Discord.Net.Rest/DiscordRestClient.cs
  3. +4
    -2
      src/Discord.Net.Rest/Net/Queue/RequestQueueBucket.cs
  4. +0
    -0
      src/Discord.Net.Rest/Serialization/Json/Converters/EntityOrIdPropertyConverter.cs
  5. +0
    -0
      src/Discord.Net.Rest/Serialization/Json/Converters/ImagePropertyConverter.cs
  6. +0
    -0
      src/Discord.Net.Rest/Serialization/Json/Converters/Int53PropertyConverter.cs
  7. +0
    -0
      src/Discord.Net.Rest/Serialization/Json/Converters/OptionalPropertyConverter.cs
  8. +0
    -0
      src/Discord.Net.Rest/Serialization/Json/Converters/UInt53PropertyConverter.cs
  9. +23
    -0
      src/Discord.Net.Rest/Serialization/Json/DiscordJsonSerializer.cs
  10. +40
    -20
      src/Discord.Net.Serialization/ConverterCollection.cs
  11. +7
    -2
      src/Discord.Net.Serialization/Json/Converters/Object.cs
  12. +15
    -21
      src/Discord.Net.Serialization/Json/JsonSerializer.cs
  13. +0
    -55
      src/Discord.Net.Serialization/SerializationFormat.cs
  14. +93
    -13
      src/Discord.Net.Serialization/Serializer.cs

+ 0
- 9
src/Discord.Net.Rest/DiscordRestApiClient.cs View File

@@ -24,15 +24,6 @@ namespace Discord.API
{
internal class DiscordRestApiClient : IDisposable
{
static DiscordRestApiClient()
{
SerializationFormat.Json.AddConverter<Image, ImagePropertyConverter>();
SerializationFormat.Json.AddConverter<long, Int53PropertyConverter>((type, prop) => prop?.GetCustomAttribute<Int53Attribute>() != null);
SerializationFormat.Json.AddConverter<ulong, UInt53PropertyConverter>((type, prop) => prop?.GetCustomAttribute<Int53Attribute>() != null);
SerializationFormat.Json.AddGenericConverter(typeof(EntityOrId<>), typeof(EntityOrIdPropertyConverter<>));
SerializationFormat.Json.AddGenericConverter(typeof(Optional<>), typeof(OptionalPropertyConverter<>));
}

private static readonly ConcurrentDictionary<string, Func<BucketIds, string>> _bucketIdGenerators = new ConcurrentDictionary<string, Func<BucketIds, string>>();

public event Func<string, string, double, Task> SentRequest { add { _sentRequestEvent.Add(value); } remove { _sentRequestEvent.Remove(value); } }


+ 2
- 1
src/Discord.Net.Rest/DiscordRestClient.cs View File

@@ -1,4 +1,5 @@
using Discord.Serialization;
using Discord.Serialization.Json;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.IO;
@@ -16,7 +17,7 @@ namespace Discord.Rest
public DiscordRestClient() : this(new DiscordRestConfig()) { }
public DiscordRestClient(DiscordRestConfig config) : base(config)
{
_serializer = new Serializer(SerializationFormat.Json);
_serializer = DiscordJsonSerializer.Global.CreateScope();
_serializer.Error += ex =>
{
_restLogger.WarningAsync("Serializer Error", ex).GetAwaiter().GetResult();


+ 4
- 2
src/Discord.Net.Rest/Net/Queue/RequestQueueBucket.cs View File

@@ -1,4 +1,6 @@
using Discord.Serialization;
using Discord.Rest;
using Discord.Serialization;
using Discord.Serialization.Json;
using System;
#if DEBUG_LIMITS
using System.Diagnostics;
@@ -104,7 +106,7 @@ namespace Discord.Net.Queue
{
try
{
var error = Serializer.Json.Read<Error>(response.Data);
var error = DiscordJsonSerializer.Global.Read<Error>(response.Data);
code = error.Code;
reason = error.Message;
}


src/Discord.Net.Rest/Serialization/JsonConverters/EntityOrIdPropertyConverter.cs → src/Discord.Net.Rest/Serialization/Json/Converters/EntityOrIdPropertyConverter.cs View File


src/Discord.Net.Rest/Serialization/JsonConverters/ImagePropertyConverter.cs → src/Discord.Net.Rest/Serialization/Json/Converters/ImagePropertyConverter.cs View File


src/Discord.Net.Rest/Serialization/JsonConverters/Int53PropertyConverter.cs → src/Discord.Net.Rest/Serialization/Json/Converters/Int53PropertyConverter.cs View File


src/Discord.Net.Rest/Serialization/JsonConverters/OptionalPropertyConverter.cs → src/Discord.Net.Rest/Serialization/Json/Converters/OptionalPropertyConverter.cs View File


src/Discord.Net.Rest/Serialization/JsonConverters/UInt53PropertyConverter.cs → src/Discord.Net.Rest/Serialization/Json/Converters/UInt53PropertyConverter.cs View File


+ 23
- 0
src/Discord.Net.Rest/Serialization/Json/DiscordJsonSerializer.cs View File

@@ -0,0 +1,23 @@
using Discord.Serialization.Json.Converters;
using System;
using System.Reflection;

namespace Discord.Serialization.Json
{
internal class DiscordJsonSerializer : JsonSerializer
{
private static readonly Lazy<DiscordJsonSerializer> _singleton = new Lazy<DiscordJsonSerializer>();
public static new DiscordJsonSerializer Global => _singleton.Value;

public DiscordJsonSerializer()
{
AddConverter<API.Image, ImagePropertyConverter>();
AddConverter<long, Int53PropertyConverter>((type, prop) => prop?.GetCustomAttribute<Int53Attribute>() != null);
AddConverter<ulong, UInt53PropertyConverter>((type, prop) => prop?.GetCustomAttribute<Int53Attribute>() != null);
AddGenericConverter(typeof(API.EntityOrId<>), typeof(EntityOrIdPropertyConverter<>));
AddGenericConverter(typeof(Optional<>), typeof(OptionalPropertyConverter<>));
}
protected DiscordJsonSerializer(JsonSerializer parent) : base(parent) { }
public new DiscordJsonSerializer CreateScope() => new DiscordJsonSerializer(this);
}
}

+ 40
- 20
src/Discord.Net.Serialization/ConverterCollection.cs View File

@@ -1,11 +1,12 @@
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;

namespace Discord.Serialization
{
public class ConverterCollection
internal class ConverterCollection
{
private class ConverterTypeCollection
{
@@ -13,27 +14,30 @@ namespace Discord.Serialization
public List<(Func<TypeInfo, PropertyInfo, bool> Condition, Type ConverterType)> Conditionals = new List<(Func<TypeInfo, PropertyInfo, bool>, Type)>();
}

private static readonly MethodInfo _getConverterMethod
= typeof(ConverterCollection).GetTypeInfo().GetDeclaredMethod(nameof(Get));
private static readonly TypeInfo _serializerType = typeof(Serializer).GetTypeInfo();

private readonly Serializer _serializer;
private readonly ConcurrentDictionary<Type, object> _cache = new ConcurrentDictionary<Type, object>();
private readonly Dictionary<Type, ConverterTypeCollection> _types = new Dictionary<Type, ConverterTypeCollection>();
private readonly Dictionary<Type, ConverterTypeCollection> _mappedGenericTypes = new Dictionary<Type, ConverterTypeCollection>();
private readonly ConverterTypeCollection _genericTypes = new ConverterTypeCollection();

internal ConverterCollection() { }
internal ConverterCollection(Serializer serializer)
{
_serializer = serializer;
}

public void Add<TType, TConverter>()
public void Add(Type type, Type converterType)
{
if (!_types.TryGetValue(typeof(TType), out var converters))
_types.Add(typeof(TType), converters = new ConverterTypeCollection());
converters.DefaultConverterType = typeof(TConverter);
if (!_types.TryGetValue(type, out var converters))
_types.Add(type, converters = new ConverterTypeCollection());
converters.DefaultConverterType = converterType;
}
public void Add<TType, TConverter>(Func<TypeInfo, PropertyInfo, bool> condition)
public void Add(Type type, Type converterType, Func<TypeInfo, PropertyInfo, bool> condition)
{
if (!_types.TryGetValue(typeof(TType), out var converters))
_types.Add(typeof(TType), converters = new ConverterTypeCollection());
converters.Conditionals.Add((condition, typeof(TConverter)));
if (!_types.TryGetValue(type, out var converters))
_types.Add(type, converters = new ConverterTypeCollection());
converters.Conditionals.Add((condition, converterType));
}

public void AddGeneric(Type openConverterType)
@@ -63,12 +67,12 @@ namespace Discord.Serialization
converters.Conditionals.Add((condition, openConverterType));
}
public object Get<TType>(PropertyInfo propInfo = null)
public object Get(Type type, PropertyInfo propInfo = null)
{
if (!_cache.TryGetValue(typeof(TType), out var result))
if (!_cache.TryGetValue(type, out var result))
{
object converter = Create(typeof(TType), propInfo);
result = _cache.GetOrAdd(typeof(TType), converter);
object converter = Create(type, propInfo);
result = _cache.GetOrAdd(type, converter);
}
return result;
}
@@ -84,7 +88,7 @@ namespace Discord.Serialization
{
var innerType = typeInfo.GenericTypeArguments[0];
converterType = converterType.MakeGenericType(innerType);
object innerConverter = GetInnerConverter(innerType, propInfo);
object innerConverter = Get(innerType, propInfo);
return Activator.CreateInstance(converterType, innerConverter);
}
}
@@ -102,14 +106,30 @@ namespace Discord.Serialization
if (converterType != null)
{
converterType = converterType.MakeGenericType(type);
return Activator.CreateInstance(converterType);
var converterTypeInfo = converterType.GetTypeInfo();

var constructors = converterTypeInfo.DeclaredConstructors.ToArray();
if (constructors.Length == 0)
throw new SerializationException($"{converterType.Name} is missing a constructor");
if (constructors.Length != 1)
throw new SerializationException($"{converterType.Name} has multiple constructors");
var constructor = constructors[0];
var parameters = constructor.GetParameters();

if (parameters.Length == 0)
return constructor.Invoke(null);
else if (parameters.Length == 1)
{
var parameterType = parameters[0].ParameterType.GetTypeInfo();
if (_serializerType.IsAssignableFrom(parameterType))
return constructor.Invoke(new object[] { _serializer });
}
throw new SerializationException($"{converterType.Name} has an unsupported constructor");
}
}

throw new InvalidOperationException($"Unsupported model type: {type.Name}");
}
private object GetInnerConverter(Type type, PropertyInfo propInfo)
=> _getConverterMethod.MakeGenericMethod(type).Invoke(this, new object[] { propInfo });

private Type FindConverterType(Type type, Dictionary<Type, ConverterTypeCollection> collection, TypeInfo typeInfo, PropertyInfo propInfo)
{


+ 7
- 2
src/Discord.Net.Serialization/Json/Converters/Object.cs View File

@@ -5,8 +5,13 @@ namespace Discord.Serialization.Json.Converters
internal class ObjectPropertyConverter<T> : IJsonPropertyConverter<T>
where T : class, new()
{
private static readonly ModelMap<T> _map = SerializationFormat.Json.MapModel<T>();
private readonly ModelMap<T> _map;

public ObjectPropertyConverter(Serializer serializer)
{
_map = serializer.MapModel<T>();
}

public T Read(PropertyMap map, ref JsonReader reader, bool isTopLevel)
{
var model = new T();


src/Discord.Net.Serialization/Json/JsonFormat.cs → src/Discord.Net.Serialization/Json/JsonSerializer.cs View File

@@ -1,5 +1,4 @@
using Discord.Serialization.Json.Converters;
using System;
using System;
using System.Collections.Generic;
using System.Reflection;
using System.Text;
@@ -8,9 +7,12 @@ using System.Text.Json;

namespace Discord.Serialization.Json
{
public class JsonFormat : SerializationFormat
public class JsonSerializer : Serializer
{
public JsonFormat()
private static readonly Lazy<JsonSerializer> _singleton = new Lazy<JsonSerializer>();
public static JsonSerializer Global => _singleton.Value;

public JsonSerializer()
{
AddConverter<sbyte, Converters.Int8PropertyConverter>();
AddConverter<short, Converters.Int16PropertyConverter>();
@@ -41,42 +43,34 @@ namespace Discord.Serialization.Json
AddGenericConverter(typeof(Converters.EnumPropertyConverter<>), (type, prop) => type.IsEnum);
AddGenericConverter(typeof(Converters.ObjectPropertyConverter<>), (type, prop) => type.IsClass);
}
protected JsonSerializer(JsonSerializer parent) : base(parent) { }
public JsonSerializer CreateScope() => new JsonSerializer(this);

public void AddConverter<TValue, TConverter>()
where TConverter : class, IJsonPropertyConverter<TValue>
=> _converters.Add<TValue, TConverter>();
=> AddConverter(typeof(TValue), typeof(TConverter));
public void AddConverter<TValue, TConverter>(Func<TypeInfo, PropertyInfo, bool> condition)
where TConverter : class, IJsonPropertyConverter<TValue>
=> _converters.Add<TValue, TConverter>(condition);

public void AddGenericConverter(Type converter)
=> _converters.AddGeneric(converter);
public void AddGenericConverter(Type converter, Func<TypeInfo, PropertyInfo, bool> condition)
=> _converters.AddGeneric(converter, condition);
public void AddGenericConverter(Type value, Type converter)
=> _converters.AddGeneric(value, converter);
public void AddGenericConverter(Type value, Type converter, Func<TypeInfo, PropertyInfo, bool> condition)
=> _converters.AddGeneric(value, converter, condition);
=> AddConverter(typeof(TValue), typeof(TConverter), condition);

protected override PropertyMap CreatePropertyMap<TModel, TValue>(PropertyInfo propInfo)
{
var converter = (IJsonPropertyConverter<TValue>)_converters.Get<TValue>(propInfo);
var converter = (IJsonPropertyConverter<TValue>)GetConverter(typeof(TValue), propInfo);
return new JsonPropertyMap<TModel, TValue>(propInfo, converter);
}

protected internal override TModel Read<TModel>(Serializer serializer, ReadOnlyBuffer<byte> data)
public override TModel Read<TModel>(ReadOnlyBuffer<byte> data)
{
var reader = new JsonReader(data.Span, SymbolTable.InvariantUtf8);
if (!reader.Read())
return null;
var converter = _converters.Get<TModel>() as IJsonPropertyConverter<TModel>;
var converter = GetConverter(typeof(TModel)) as IJsonPropertyConverter<TModel>;
return converter.Read(null, ref reader, false);
}

protected internal override void Write<TModel>(Serializer serializer, ArrayFormatter stream, TModel model)
public override void Write<TModel>(ArrayFormatter stream, TModel model)
{
var writer = new JsonWriter(stream);
var converter = _converters.Get<TModel>() as IJsonPropertyConverter<TModel>;
var converter = GetConverter(typeof(TModel)) as IJsonPropertyConverter<TModel>;
converter.Write(null, ref writer, model, false);
}
}

+ 0
- 55
src/Discord.Net.Serialization/SerializationFormat.cs View File

@@ -1,55 +0,0 @@
using Discord.Serialization.Json;
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Text.Formatting;

namespace Discord.Serialization
{
public abstract class SerializationFormat
{
private static readonly MethodInfo _getConverterMethod
= typeof(SerializationFormat).GetTypeInfo().GetDeclaredMethod(nameof(CreatePropertyMap));

private static readonly Lazy<JsonFormat> _json = new Lazy<JsonFormat>(() => new JsonFormat());
public static JsonFormat Json => _json.Value;

protected readonly ConcurrentDictionary<Type, object> _maps = new ConcurrentDictionary<Type, object>();
protected readonly ConverterCollection _converters = new ConverterCollection();

protected internal ModelMap<TModel> MapModel<TModel>()
where TModel : class, new()
{
return _maps.GetOrAdd(typeof(TModel), _ =>
{
var type = typeof(TModel).GetTypeInfo();
var propInfos = type.DeclaredProperties
.Where(x => x.CanRead && x.CanWrite)
.ToArray();

var properties = new List<PropertyMap>();
for (int i = 0; i < propInfos.Length; i++)
{
var propMap = MapProperty<TModel>(propInfos[i]);
properties.Add(propMap);
}
return new ModelMap<TModel>(properties);
}) as ModelMap<TModel>;
}

private PropertyMap MapProperty<TModel>(PropertyInfo propInfo)
where TModel : class, new()
=> _getConverterMethod.MakeGenericMethod(typeof(TModel), propInfo.PropertyType).Invoke(this, new object[] { propInfo }) as PropertyMap;


protected internal abstract TModel Read<TModel>(Serializer serializer, ReadOnlyBuffer<byte> data)
where TModel : class, new();
protected internal abstract void Write<TModel>(Serializer serializer, ArrayFormatter stream, TModel model)
where TModel : class, new();

protected abstract PropertyMap CreatePropertyMap<TModel, TValue>(PropertyInfo propInfo)
where TModel : class, new();
}
}

+ 93
- 13
src/Discord.Net.Serialization/Serializer.cs View File

@@ -1,27 +1,107 @@
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Text.Formatting;

namespace Discord.Serialization
{
public class Serializer
public abstract class Serializer
{
private readonly static Lazy<Serializer> _json = new Lazy<Serializer>(() => new Serializer(SerializationFormat.Json));
public static Serializer Json => _json.Value;

public event Action<Exception> Error; //TODO: Impl

private readonly SerializationFormat _format;
private static readonly MethodInfo _createPropertyMapMethod
= typeof(Serializer).GetTypeInfo().GetDeclaredMethod(nameof(CreatePropertyMap));

private readonly ConcurrentDictionary<Type, object> _maps;
private readonly ConverterCollection _converters;

public bool IsScoped { get; }

protected Serializer()
{
_maps = new ConcurrentDictionary<Type, object>();
_converters = new ConverterCollection(this);
IsScoped = false;
}
protected Serializer(Serializer parent)
{
_maps = parent._maps;
_converters = parent._converters;
IsScoped = true;
}

protected object GetConverter(Type type, PropertyInfo propInfo = null)
=> _converters.Get(type, propInfo);

public void AddConverter(Type type, Type converter)
{
CheckScoped();
_converters.Add(type, converter);
}
public void AddConverter(Type type, Type converter, Func<TypeInfo, PropertyInfo, bool> condition)
{
CheckScoped();
_converters.Add(type, converter, condition);
}

public void AddGenericConverter(Type converter)
{
CheckScoped();
_converters.AddGeneric(converter);
}
public void AddGenericConverter(Type converter, Func<TypeInfo, PropertyInfo, bool> condition)
{
CheckScoped();
_converters.AddGeneric(converter, condition);
}
public void AddGenericConverter(Type value, Type converter)
{
CheckScoped();
_converters.AddGeneric(value, converter);
}
public void AddGenericConverter(Type value, Type converter, Func<TypeInfo, PropertyInfo, bool> condition)
{
CheckScoped();
_converters.AddGeneric(value, converter, condition);
}

public Serializer(SerializationFormat format)
protected internal ModelMap<TModel> MapModel<TModel>()
where TModel : class, new()
{
_format = format;
return _maps.GetOrAdd(typeof(TModel), _ =>
{
var type = typeof(TModel).GetTypeInfo();
var propInfos = type.DeclaredProperties
.Where(x => x.CanRead && x.CanWrite)
.ToArray();

var properties = new List<PropertyMap>();
for (int i = 0; i < propInfos.Length; i++)
{
var propMap = MapProperty<TModel>(propInfos[i]);
properties.Add(propMap);
}
return new ModelMap<TModel>(properties);
}) as ModelMap<TModel>;
}

public T Read<T>(ReadOnlyBuffer<byte> data)
where T : class, new()
=> _format.Read<T>(this, data);
public void Write<T>(ArrayFormatter data, T obj)
where T : class, new()
=> _format.Write(this, data, obj);
private PropertyMap MapProperty<TModel>(PropertyInfo propInfo)
where TModel : class, new()
=> _createPropertyMapMethod.MakeGenericMethod(typeof(TModel), propInfo.PropertyType).Invoke(this, new object[] { propInfo }) as PropertyMap;
protected abstract PropertyMap CreatePropertyMap<TModel, TValue>(PropertyInfo propInfo)
where TModel : class, new();

public abstract TModel Read<TModel>(ReadOnlyBuffer<byte> data)
where TModel : class, new();
public abstract void Write<TModel>(ArrayFormatter stream, TModel model)
where TModel : class, new();

private void CheckScoped()
{
if (IsScoped)
throw new InvalidOperationException("Scoped serializers are read-only");
}
}
}

Loading…
Cancel
Save