Browse Source

Added serialization support for submodels

voice-allocs
RogueException 7 years ago
parent
commit
a1c17856b9
5 changed files with 133 additions and 72 deletions
  1. +2
    -2
      src/Discord.Net.Rest/DiscordRestApiClient.cs
  2. +68
    -37
      src/Discord.Net.Serialization/ConverterCollection.cs
  3. +45
    -0
      src/Discord.Net.Serialization/Json/Converters/Object.cs
  4. +17
    -32
      src/Discord.Net.Serialization/Json/JsonFormat.cs
  5. +1
    -1
      src/Discord.Net.Serialization/SerializationFormat.cs

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

@@ -27,8 +27,8 @@ namespace Discord.API
static DiscordRestApiClient()
{
SerializationFormat.Json.AddConverter<Image, ImagePropertyConverter>();
SerializationFormat.Json.AddConverter<long, Int53PropertyConverter>(info => info.GetCustomAttribute<Int53Attribute>() != null);
SerializationFormat.Json.AddConverter<ulong, UInt53PropertyConverter>(info => info.GetCustomAttribute<Int53Attribute>() != null);
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<>));
}


+ 68
- 37
src/Discord.Net.Serialization/ConverterCollection.cs View File

@@ -10,85 +10,116 @@ namespace Discord.Serialization
private class ConverterTypeCollection
{
public Type DefaultConverterType;
public List<(Func<PropertyInfo, bool> Condition, Type ConverterType)> Conditionals = new List<(Func<PropertyInfo, bool>, Type)>();
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 readonly ConcurrentDictionary<Type, object> _maps = new ConcurrentDictionary<Type, object>();
private readonly ConcurrentDictionary<Type, ConverterTypeCollection> _types = new ConcurrentDictionary<Type, ConverterTypeCollection>();
private readonly ConcurrentDictionary<Type, ConverterTypeCollection> _genericTypes = new ConcurrentDictionary<Type, ConverterTypeCollection>();
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() { }

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

public void AddGeneric(Type openConverterType)
{
if (openConverterType.IsConstructedGenericType) throw new InvalidOperationException($"{nameof(openConverterType)} must be an open generic");
_genericTypes.DefaultConverterType = openConverterType;
}
public void AddGeneric(Type openConverterType, Func<TypeInfo, PropertyInfo, bool> condition)
{
if (openConverterType.IsConstructedGenericType) throw new InvalidOperationException($"{nameof(openConverterType)} must be an open generic");
_genericTypes.Conditionals.Add((condition, openConverterType));
}
public void AddGeneric(Type openType, Type openConverterType)
{
if (openType.IsConstructedGenericType) throw new InvalidOperationException($"{nameof(openType)} must be an open generic");
if (openConverterType.IsConstructedGenericType) throw new InvalidOperationException($"{nameof(openConverterType)} must be an open generic");
var converters = _genericTypes.GetOrAdd(openType, _ => new ConverterTypeCollection());
if (!_mappedGenericTypes.TryGetValue(openType, out var converters))
_mappedGenericTypes.Add(openType, converters = new ConverterTypeCollection());
converters.DefaultConverterType = openConverterType;
}
public void AddGeneric(Type openType, Type openConverterType, Func<PropertyInfo, bool> condition)
public void AddGeneric(Type openType, Type openConverterType, Func<TypeInfo, PropertyInfo, bool> condition)
{
if (openType.IsConstructedGenericType) throw new InvalidOperationException($"{nameof(openType)} must be an open generic");
if (openConverterType.IsConstructedGenericType) throw new InvalidOperationException($"{nameof(openConverterType)} must be an open generic");
var converters = _genericTypes.GetOrAdd(openType, _ => new ConverterTypeCollection());
if (!_mappedGenericTypes.TryGetValue(openType, out var converters))
_mappedGenericTypes.Add(openType, converters = new ConverterTypeCollection());
converters.Conditionals.Add((condition, openConverterType));
}
public object Get<TType>(PropertyInfo propInfo)
public object Get<TType>(PropertyInfo propInfo = null)
{
var typeInfo = typeof(TType).GetTypeInfo();

//Generic converters
if (typeInfo.IsGenericType)
return _cache.GetOrAdd(typeof(TType), _ =>
{
var converterType = FindConverterType(typeInfo.GetGenericTypeDefinition(), _genericTypes, propInfo);
if (converterType != null)
TypeInfo typeInfo = typeof(TType).GetTypeInfo();

//Mapped generic converters (List<T> -> CollectionPropertyConverter<T>)
if (typeInfo.IsGenericType)
{
var innerType = typeInfo.GenericTypeArguments[0];
converterType = converterType.MakeGenericType(innerType);
object innerConverter = GetInnerConverter(innerType, propInfo);
return Activator.CreateInstance(converterType, innerConverter);
var converterType = FindConverterType(typeInfo.GetGenericTypeDefinition(), _mappedGenericTypes, typeInfo, propInfo);
if (converterType != null)
{
var innerType = typeInfo.GenericTypeArguments[0];
converterType = converterType.MakeGenericType(innerType);
object innerConverter = GetInnerConverter(innerType, propInfo);
return Activator.CreateInstance(converterType, innerConverter);
}
}
}

//Normal converters
{
var converterType = FindConverterType(typeof(TType), _types, propInfo);
if (converterType != null)
return Activator.CreateInstance(converterType);
}
//Normal converters (bool -> BooleanPropertyConverter)
{
var converterType = FindConverterType(typeof(TType), _types, typeInfo, propInfo);
if (converterType != null)
return Activator.CreateInstance(converterType);
}

//Generic converters (Model -> ObjectPropertyConverter<Model>)
{
var converterType = FindConverterType(_genericTypes, typeInfo, propInfo);
if (converterType != null)
{
converterType = converterType.MakeGenericType(typeof(TType));
return Activator.CreateInstance(converterType);
}
}

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

private Type FindConverterType(Type type, ConcurrentDictionary<Type, ConverterTypeCollection> collection, PropertyInfo propInfo)
private Type FindConverterType(Type type, Dictionary<Type, ConverterTypeCollection> collection, TypeInfo typeInfo, PropertyInfo propInfo)
{
if (collection.TryGetValue(type, out var converters))
return FindConverterType(converters, typeInfo, propInfo);
return null;
}
private Type FindConverterType(ConverterTypeCollection converters, TypeInfo typeInfo, PropertyInfo propInfo)
{
for (int i = 0; i < converters.Conditionals.Count; i++)
{
for (int i = 0; i < converters.Conditionals.Count; i++)
{
if (converters.Conditionals[i].Condition(propInfo))
return converters.Conditionals[i].ConverterType;
}
if (converters.DefaultConverterType != null)
return converters.DefaultConverterType;
if (converters.Conditionals[i].Condition(typeInfo, propInfo))
return converters.Conditionals[i].ConverterType;
}
if (converters.DefaultConverterType != null)
return converters.DefaultConverterType;
return null;
}
}


+ 45
- 0
src/Discord.Net.Serialization/Json/Converters/Object.cs View File

@@ -0,0 +1,45 @@
using System.Text.Json;

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>();
public T Read(PropertyMap map, JsonReader reader, bool isTopLevel)
{
var model = new T();

if ((isTopLevel && !reader.Read()) || reader.TokenType != JsonTokenType.StartObject)
throw new SerializationException("Bad input, expected StartObject");
while (reader.Read())
{
if (reader.TokenType == JsonTokenType.EndObject)
return model;
if (reader.TokenType != JsonTokenType.PropertyName)
throw new SerializationException("Bad input, expected PropertyName");

string key = reader.ParseString();
if (_map.PropertiesByKey.TryGetValue(key, out var property))
(property as IJsonPropertyMap<T>).Read(model, reader);
else
reader.Skip(); //Unknown property, skip

if (!reader.Read())
throw new SerializationException("Bad input, expected Value");
}
throw new SerializationException("Bad input, expected EndObject");
}
public void Write(PropertyMap map, JsonWriter writer, T value, bool isTopLevel)
{
if (isTopLevel)
writer.WriteObjectStart(map.Key);
else
writer.WriteObjectStart();
for (int i = 0; i < _map.Properties.Length; i++)
(_map.Properties[i] as IJsonPropertyMap<T>).Write(value, writer);
writer.WriteObjectEnd();
}
}
}

+ 17
- 32
src/Discord.Net.Serialization/Json/JsonFormat.cs View File

@@ -1,4 +1,5 @@
using System;
using Discord.Serialization.Json.Converters;
using System;
using System.Collections.Generic;
using System.Reflection;
using System.Text;
@@ -37,20 +38,26 @@ namespace Discord.Serialization.Json
AddGenericConverter(typeof(List<>), typeof(Converters.ListPropertyConverter<>));
AddGenericConverter(typeof(Nullable<>), typeof(Converters.NullablePropertyConverter<>));

AddGenericConverter(typeof(Converters.ObjectPropertyConverter<>), (type, prop) => type.IsClass);

//AddEnumConverter<Converters.EnumPropertyConverter>();
}

public void AddConverter<TValue, TConverter>()
where TConverter : class, IJsonPropertyConverter<TValue>
=> _converters.Add<TValue, TConverter>();
public void AddConverter<TValue, TConverter>(Func<PropertyInfo, bool> condition)
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<PropertyInfo, bool> condition)
=> _converters.AddGeneric(value, converter);
public void AddGenericConverter(Type value, Type converter, Func<TypeInfo, PropertyInfo, bool> condition)
=> _converters.AddGeneric(value, converter, condition);

protected override PropertyMap CreatePropertyMap<TModel, TValue>(PropertyInfo propInfo)
{
@@ -61,39 +68,17 @@ namespace Discord.Serialization.Json
protected internal override TModel Read<TModel>(Serializer serializer, ReadOnlyBuffer<byte> data)
{
var reader = new JsonReader(data.Span, SymbolTable.InvariantUtf8);
var map = MapModel<TModel>();
var model = new TModel();

if (!reader.Read() || reader.TokenType != JsonTokenType.StartObject)
throw new InvalidOperationException("Bad input, expected StartObject");
while (reader.Read())
{
if (reader.TokenType == JsonTokenType.EndObject)
return model;
if (reader.TokenType != JsonTokenType.PropertyName)
throw new InvalidOperationException("Bad input, expected PropertyName");

string key = reader.ParseString();
if (map.PropertiesByKey.TryGetValue(key, out var property))
(property as IJsonPropertyMap<TModel>).Read(model, reader);
else
reader.Skip(); //Unknown property, skip

if (!reader.Read())
throw new InvalidOperationException("Bad input, expected Value");
}
throw new InvalidOperationException("Bad input, expected EndObject");
if (!reader.Read())
return null;
var converter = _converters.Get<TModel>() as IJsonPropertyConverter<TModel>;
return converter.Read(null, reader, false);
}

protected internal override void Write<TModel>(Serializer serializer, ArrayFormatter stream, TModel model)
{
var writer = new JsonWriter(stream);
var map = MapModel<TModel>();

writer.WriteObjectStart();
for (int i = 0; i < map.Properties.Length; i++)
(map.Properties[i] as IJsonPropertyMap<TModel>).Write(model, writer);
writer.WriteObjectEnd();
var converter = _converters.Get<TModel>() as IJsonPropertyConverter<TModel>;
converter.Write(null, writer, model, false);
}
}
}

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

@@ -19,7 +19,7 @@ namespace Discord.Serialization
protected readonly ConcurrentDictionary<Type, object> _maps = new ConcurrentDictionary<Type, object>();
protected readonly ConverterCollection _converters = new ConverterCollection();

protected ModelMap<TModel> MapModel<TModel>()
protected internal ModelMap<TModel> MapModel<TModel>()
where TModel : class, new()
{
return _maps.GetOrAdd(typeof(TModel), _ =>


Loading…
Cancel
Save