diff --git a/src/Discord.Net.Rest/DiscordRestApiClient.cs b/src/Discord.Net.Rest/DiscordRestApiClient.cs index c708df258..ab677c12b 100644 --- a/src/Discord.Net.Rest/DiscordRestApiClient.cs +++ b/src/Discord.Net.Rest/DiscordRestApiClient.cs @@ -198,7 +198,11 @@ namespace Discord.API var request = new JsonRestRequest(RestClient, method, endpoint, SerializeJson(data, payload), options); await SendInternalAsync(method, endpoint, request).ConfigureAwait(false); } - finally { _formatters.Enqueue(data); } + finally + { + data.Clear(); + _formatters.Enqueue(data); + } } internal Task SendMultipartAsync(string method, Expression> endpointExpr, IReadOnlyDictionary multipartArgs, BucketIds ids, @@ -247,7 +251,11 @@ namespace Discord.API var request = new JsonRestRequest(RestClient, method, endpoint, SerializeJson(data, payload), options); return DeserializeJson(await SendInternalAsync(method, endpoint, request).ConfigureAwait(false)); } - finally { _formatters.Enqueue(data); } + finally + { + data.Clear(); + _formatters.Enqueue(data); + } } internal Task SendMultipartAsync(string method, Expression> endpointExpr, IReadOnlyDictionary multipartArgs, BucketIds ids, diff --git a/src/Discord.Net.Rest/DiscordRestClient.cs b/src/Discord.Net.Rest/DiscordRestClient.cs index 8c4e7cafa..b6420dbe8 100644 --- a/src/Discord.Net.Rest/DiscordRestClient.cs +++ b/src/Discord.Net.Rest/DiscordRestClient.cs @@ -17,7 +17,7 @@ namespace Discord.Rest public DiscordRestClient() : this(new DiscordRestConfig()) { } public DiscordRestClient(DiscordRestConfig config) : base(config) { - _serializer = DiscordJsonSerializer.Global.CreateScope(); + _serializer = DiscordRestJsonSerializer.Global.CreateScope(); _serializer.Error += ex => { _restLogger.WarningAsync("Serializer Error", ex).GetAwaiter().GetResult(); diff --git a/src/Discord.Net.Rest/Net/Queue/RequestQueueBucket.cs b/src/Discord.Net.Rest/Net/Queue/RequestQueueBucket.cs index bfd47796e..d4abfd595 100644 --- a/src/Discord.Net.Rest/Net/Queue/RequestQueueBucket.cs +++ b/src/Discord.Net.Rest/Net/Queue/RequestQueueBucket.cs @@ -106,7 +106,7 @@ namespace Discord.Net.Queue { try { - var error = DiscordJsonSerializer.Global.Read(response.Data); + var error = DiscordRestJsonSerializer.Global.Read(response.Data); code = error.Code; reason = error.Message; } diff --git a/src/Discord.Net.Rest/Serialization/Json/Converters/EntityOrIdPropertyConverter.cs b/src/Discord.Net.Rest/Serialization/Json/Converters/EntityOrIdPropertyConverter.cs index 640de4016..836c2ce9d 100644 --- a/src/Discord.Net.Rest/Serialization/Json/Converters/EntityOrIdPropertyConverter.cs +++ b/src/Discord.Net.Rest/Serialization/Json/Converters/EntityOrIdPropertyConverter.cs @@ -3,16 +3,16 @@ using System.Text.Json; namespace Discord.Serialization.Json.Converters { - internal class EntityOrIdPropertyConverter : IJsonPropertyConverter> + internal class EntityOrIdPropertyConverter : JsonPropertyConverter> { - private readonly IJsonPropertyConverter _innerConverter; + private readonly JsonPropertyConverter _innerConverter; - public EntityOrIdPropertyConverter(IJsonPropertyConverter innerConverter) + public EntityOrIdPropertyConverter(JsonPropertyConverter innerConverter) { _innerConverter = innerConverter; } - public EntityOrId Read(PropertyMap map, object model, ref JsonReader reader, bool isTopLevel) + public override EntityOrId Read(PropertyMap map, object model, ref JsonReader reader, bool isTopLevel) { if (isTopLevel) reader.Read(); @@ -21,14 +21,14 @@ namespace Discord.Serialization.Json.Converters return new EntityOrId(_innerConverter.Read(map, model, ref reader, false)); } - public void Write(PropertyMap map, object model, ref JsonWriter writer, EntityOrId value, bool isTopLevel) + public override void Write(PropertyMap map, object model, ref JsonWriter writer, EntityOrId value, string key) { if (value.Object != null) - _innerConverter.Write(map, model, ref writer, value.Object, isTopLevel); + _innerConverter.Write(map, model, ref writer, value.Object, key); else { - if (isTopLevel) - writer.WriteAttribute(map.Key, value.Id); + if (key != null) + writer.WriteAttribute(key, value.Id); else writer.WriteValue(value.Id); } diff --git a/src/Discord.Net.Rest/Serialization/Json/Converters/ImagePropertyConverter.cs b/src/Discord.Net.Rest/Serialization/Json/Converters/ImagePropertyConverter.cs index a5687b16d..4852fe4d8 100644 --- a/src/Discord.Net.Rest/Serialization/Json/Converters/ImagePropertyConverter.cs +++ b/src/Discord.Net.Rest/Serialization/Json/Converters/ImagePropertyConverter.cs @@ -3,9 +3,9 @@ using System.Text.Json; namespace Discord.Serialization.Json.Converters { - internal class ImagePropertyConverter : IJsonPropertyConverter + internal class ImagePropertyConverter : JsonPropertyConverter { - public API.Image Read(PropertyMap map, object model, ref JsonReader reader, bool isTopLevel) + public override API.Image Read(PropertyMap map, object model, ref JsonReader reader, bool isTopLevel) { if (isTopLevel) reader.Read(); @@ -13,7 +13,7 @@ namespace Discord.Serialization.Json.Converters throw new SerializationException("Bad input, expected String"); return new API.Image(reader.ParseString()); } - public void Write(PropertyMap map, object model, ref JsonWriter writer, API.Image value, bool isTopLevel) + public override void Write(PropertyMap map, object model, ref JsonWriter writer, API.Image value, string key) { string str; if (value.Stream != null) @@ -43,8 +43,8 @@ namespace Discord.Serialization.Json.Converters else str = value.Hash; - if (isTopLevel) - writer.WriteAttribute(map.Key, str); + if (key != null) + writer.WriteAttribute(key, str); else writer.WriteValue(str); } diff --git a/src/Discord.Net.Rest/Serialization/Json/Converters/Int53PropertyConverter.cs b/src/Discord.Net.Rest/Serialization/Json/Converters/Int53PropertyConverter.cs index 3b9fe89a6..f79cc64ce 100644 --- a/src/Discord.Net.Rest/Serialization/Json/Converters/Int53PropertyConverter.cs +++ b/src/Discord.Net.Rest/Serialization/Json/Converters/Int53PropertyConverter.cs @@ -2,9 +2,9 @@ namespace Discord.Serialization.Json.Converters { - internal class Int53PropertyConverter : IJsonPropertyConverter + internal class Int53PropertyConverter : JsonPropertyConverter { - public long Read(PropertyMap map, object model, ref JsonReader reader, bool isTopLevel) + public override long Read(PropertyMap map, object model, ref JsonReader reader, bool isTopLevel) { if (isTopLevel) reader.Read(); @@ -12,10 +12,10 @@ namespace Discord.Serialization.Json.Converters throw new SerializationException("Bad input, expected Number"); return reader.ParseInt64(); } - public void Write(PropertyMap map, object model, ref JsonWriter writer, long value, bool isTopLevel) + public override void Write(PropertyMap map, object model, ref JsonWriter writer, long value, string key) { - if (isTopLevel) - writer.WriteAttribute(map.Key, value); + if (key != null) + writer.WriteAttribute(key, value); else writer.WriteValue(value.ToString()); } diff --git a/src/Discord.Net.Rest/Serialization/Json/Converters/OptionalPropertyConverter.cs b/src/Discord.Net.Rest/Serialization/Json/Converters/OptionalPropertyConverter.cs index 4a5218a34..30ee1777f 100644 --- a/src/Discord.Net.Rest/Serialization/Json/Converters/OptionalPropertyConverter.cs +++ b/src/Discord.Net.Rest/Serialization/Json/Converters/OptionalPropertyConverter.cs @@ -2,22 +2,22 @@ namespace Discord.Serialization.Json.Converters { - internal class OptionalPropertyConverter : IJsonPropertyConverter> + internal class OptionalPropertyConverter : JsonPropertyConverter> { - private readonly IJsonPropertyConverter _innerConverter; + private readonly JsonPropertyConverter _innerConverter; - public OptionalPropertyConverter(IJsonPropertyConverter innerConverter) + public OptionalPropertyConverter(JsonPropertyConverter innerConverter) { _innerConverter = innerConverter; } - public Optional Read(PropertyMap map, object model, ref JsonReader reader, bool isTopLevel) + public override Optional Read(PropertyMap map, object model, ref JsonReader reader, bool isTopLevel) => new Optional(_innerConverter.Read(map, model, ref reader, isTopLevel)); - public void Write(PropertyMap map, object model, ref JsonWriter writer, Optional value, bool isTopLevel) + public override void Write(PropertyMap map, object model, ref JsonWriter writer, Optional value, string key) { if (value.IsSpecified) - _innerConverter.Write(map, model, ref writer, value.Value, isTopLevel); + _innerConverter.Write(map, model, ref writer, value.Value, key); } } } diff --git a/src/Discord.Net.Rest/Serialization/Json/Converters/UInt53PropertyConverter.cs b/src/Discord.Net.Rest/Serialization/Json/Converters/UInt53PropertyConverter.cs index 9664fc3a2..b69554bed 100644 --- a/src/Discord.Net.Rest/Serialization/Json/Converters/UInt53PropertyConverter.cs +++ b/src/Discord.Net.Rest/Serialization/Json/Converters/UInt53PropertyConverter.cs @@ -2,9 +2,9 @@ namespace Discord.Serialization.Json.Converters { - internal class UInt53PropertyConverter : IJsonPropertyConverter + internal class UInt53PropertyConverter : JsonPropertyConverter { - public ulong Read(PropertyMap map, object model, ref JsonReader reader, bool isTopLevel) + public override ulong Read(PropertyMap map, object model, ref JsonReader reader, bool isTopLevel) { if (isTopLevel) reader.Read(); @@ -12,12 +12,12 @@ namespace Discord.Serialization.Json.Converters throw new SerializationException("Bad input, expected Number"); return reader.ParseUInt64(); } - public void Write(PropertyMap map, object model, ref JsonWriter writer, ulong value, bool isTopLevel) + public override void Write(PropertyMap map, object model, ref JsonWriter writer, ulong value, string key) { - if (isTopLevel) - writer.WriteAttribute(map.Key, value); + if (key != null) + writer.WriteAttribute(key, value); else - writer.WriteValue(value.ToString()); + writer.WriteValue(value); } } } diff --git a/src/Discord.Net.Rest/Serialization/Json/DiscordJsonSerializer.cs b/src/Discord.Net.Rest/Serialization/Json/DiscordRestJsonSerializer.cs similarity index 50% rename from src/Discord.Net.Rest/Serialization/Json/DiscordJsonSerializer.cs rename to src/Discord.Net.Rest/Serialization/Json/DiscordRestJsonSerializer.cs index 673836e7e..17ad7638a 100644 --- a/src/Discord.Net.Rest/Serialization/Json/DiscordJsonSerializer.cs +++ b/src/Discord.Net.Rest/Serialization/Json/DiscordRestJsonSerializer.cs @@ -4,12 +4,15 @@ using System.Reflection; namespace Discord.Serialization.Json { - public class DiscordJsonSerializer : JsonSerializer + public class DiscordRestJsonSerializer : JsonSerializer { - private static readonly Lazy _singleton = new Lazy(); - public static new DiscordJsonSerializer Global => _singleton.Value; + private static readonly Lazy _singleton = new Lazy(); + public static DiscordRestJsonSerializer Global => _singleton.Value; - public DiscordJsonSerializer() + public DiscordRestJsonSerializer() + : this((JsonSerializer)null) { } + public DiscordRestJsonSerializer(JsonSerializer parent) + : base(parent ?? DefaultJsonSerializer.Global) { AddConverter(); AddConverter((type, prop) => prop?.GetCustomAttribute() != null); @@ -18,7 +21,9 @@ namespace Discord.Serialization.Json AddGenericConverter(typeof(API.EntityOrId<>), typeof(EntityOrIdPropertyConverter<>)); AddGenericConverter(typeof(Optional<>), typeof(OptionalPropertyConverter<>)); } - protected DiscordJsonSerializer(JsonSerializer parent) : base(parent) { } - public new DiscordJsonSerializer CreateScope() => new DiscordJsonSerializer(this); + + private DiscordRestJsonSerializer(DiscordRestJsonSerializer parent) + : base(parent) { } + public DiscordRestJsonSerializer CreateScope() => new DiscordRestJsonSerializer(this); } } diff --git a/src/Discord.Net.Rpc/DiscordRpcApiClient.cs b/src/Discord.Net.Rpc/DiscordRpcApiClient.cs index 5ff89f71e..6c99e2fe3 100644 --- a/src/Discord.Net.Rpc/DiscordRpcApiClient.cs +++ b/src/Discord.Net.Rpc/DiscordRpcApiClient.cs @@ -42,11 +42,11 @@ namespace Discord.API } public Task SetResultAsync(ReadOnlyBuffer data) { - return Promise.TrySetResultAsync(DiscordJsonSerializer.Global.Read(data)); + return Promise.TrySetResultAsync(DiscordRestJsonSerializer.Global.Read(data)); } public Task SetExceptionAsync(ReadOnlyBuffer data) { - var error = DiscordJsonSerializer.Global.Read(data); + var error = DiscordRestJsonSerializer.Global.Read(data); return Promise.TrySetExceptionAsync(new RpcException(error.Code, error.Message)); } } @@ -225,26 +225,23 @@ namespace Discord.API private async Task SendRpcAsyncInternal(string cmd, object payload, Optional evt, RequestOptions options) where TResponse : class, new() { - if (_formatters.TryDequeue(out var data1)) - data1 = new ArrayFormatter(128, SymbolTable.InvariantUtf8); - if (_formatters.TryDequeue(out var data2)) - data2 = new ArrayFormatter(128, SymbolTable.InvariantUtf8); + if (_formatters.TryDequeue(out var data)) + data = new ArrayFormatter(128, SymbolTable.InvariantUtf8); try { var guid = Guid.NewGuid(); - var frame = new API.Rpc.RpcFrame { Cmd = cmd, Event = evt, Args = SerializeJson(data1, payload), Nonce = guid }; + var frame = new API.Rpc.RpcFrame { Cmd = cmd, Event = evt, Args = payload, Nonce = guid }; var requestTracker = new RpcRequest(options); _requests[guid] = requestTracker; - await RequestQueue.SendAsync(new WebSocketRequest(_webSocketClient, null, SerializeJson(data2, frame), true, options)).ConfigureAwait(false); + await RequestQueue.SendAsync(new WebSocketRequest(_webSocketClient, null, SerializeJson(data, frame), true, options)).ConfigureAwait(false); await _sentRpcMessageEvent.InvokeAsync(cmd).ConfigureAwait(false); return await requestTracker.Promise.Task.ConfigureAwait(false); } finally { - _formatters.Enqueue(data1); - _formatters.Enqueue(data2); + _formatters.Enqueue(data); } } diff --git a/src/Discord.Net.Rpc/DiscordRpcClient.cs b/src/Discord.Net.Rpc/DiscordRpcClient.cs index b208e4f40..6708c8803 100644 --- a/src/Discord.Net.Rpc/DiscordRpcClient.cs +++ b/src/Discord.Net.Rpc/DiscordRpcClient.cs @@ -38,7 +38,7 @@ namespace Discord.Rpc _authorizeLock = new SemaphoreSlim(1, 1); _rpcLogger = LogManager.CreateLogger("RPC"); - _serializer = DiscordJsonSerializer.Global.CreateScope(); + _serializer = DiscordRpcJsonSerializer.Global.CreateScope(); _serializer.Error += ex => { _rpcLogger.WarningAsync("Serializer Error", ex).GetAwaiter().GetResult(); diff --git a/src/Discord.Net.Rpc/Serialization/Json/DiscordRpcJsonSerializer.cs b/src/Discord.Net.Rpc/Serialization/Json/DiscordRpcJsonSerializer.cs new file mode 100644 index 000000000..06bb7340b --- /dev/null +++ b/src/Discord.Net.Rpc/Serialization/Json/DiscordRpcJsonSerializer.cs @@ -0,0 +1,21 @@ +using System; + +namespace Discord.Serialization.Json +{ + public class DiscordRpcJsonSerializer : JsonSerializer + { + private static readonly Lazy _singleton = new Lazy(); + public static DiscordRpcJsonSerializer Global => _singleton.Value; + + public DiscordRpcJsonSerializer() + : this((JsonSerializer)null) { } + public DiscordRpcJsonSerializer(JsonSerializer parent) + : base(parent ?? DiscordRestJsonSerializer.Global) + { + } + + private DiscordRpcJsonSerializer(DiscordRpcJsonSerializer parent) + : base(parent) { } + public DiscordRpcJsonSerializer CreateScope() => new DiscordRpcJsonSerializer(this); + } +} diff --git a/src/Discord.Net.Serialization/ConverterCollection.cs b/src/Discord.Net.Serialization/ConverterCollection.cs index c1a0efa81..bac21e5e0 100644 --- a/src/Discord.Net.Serialization/ConverterCollection.cs +++ b/src/Discord.Net.Serialization/ConverterCollection.cs @@ -8,28 +8,46 @@ namespace Discord.Serialization { internal class ConverterCollection { + private class GenericConverterType + { + public Type Type; + public Func InnerTypeSelector; + + public GenericConverterType(Type type, Func innerTypeSelector) + { + Type = type; + InnerTypeSelector = innerTypeSelector ?? (x => x); + } + } private class ConverterTypeCollection { public Type DefaultConverterType; - public List<(Func Condition, Type ConverterType)> Conditionals = new List<(Func, Type)>(); + public List<(Func Condition, Type ConverterType)> Conditionals + = new List<(Func, Type)>(); + } + private class GenericConverterTypeCollection + { + public GenericConverterType DefaultConverterType; + public List<(Func Condition, GenericConverterType ConverterType)> Conditionals + = new List<(Func, GenericConverterType)>(); } private static readonly TypeInfo _serializerType = typeof(Serializer).GetTypeInfo(); - private readonly Serializer _serializer; + private readonly ConverterCollection _parent; private readonly ConcurrentDictionary _cache; private readonly Dictionary _types; - private readonly Dictionary _mappedGenericTypes; - private readonly ConverterTypeCollection _genericTypes; + private readonly Dictionary _mappedGenericTypes; + private readonly GenericConverterTypeCollection _genericTypes; private readonly ConcurrentDictionary> _selectorGroups; - internal ConverterCollection(Serializer serializer) + internal ConverterCollection(ConverterCollection parent = null) { - _serializer = serializer; + _parent = parent; _cache = new ConcurrentDictionary(); _types = new Dictionary(); - _mappedGenericTypes = new Dictionary(); - _genericTypes = new ConverterTypeCollection(); + _mappedGenericTypes = new Dictionary(); + _genericTypes = new GenericConverterTypeCollection(); _selectorGroups = new ConcurrentDictionary>(); } @@ -46,63 +64,80 @@ namespace Discord.Serialization converters.Conditionals.Add((condition, converterType)); } - public void AddGeneric(Type openConverterType) + public void AddGeneric(Type openConverterType, Func innerTypeSelector = null) { if (openConverterType.IsConstructedGenericType) throw new InvalidOperationException($"{nameof(openConverterType)} must be an open generic"); - _genericTypes.DefaultConverterType = openConverterType; + _genericTypes.DefaultConverterType = new GenericConverterType(openConverterType, innerTypeSelector); } - public void AddGeneric(Type openConverterType, Func condition) + public void AddGeneric(Type openConverterType, Func condition, Func innerTypeSelector = null) { if (openConverterType.IsConstructedGenericType) throw new InvalidOperationException($"{nameof(openConverterType)} must be an open generic"); - _genericTypes.Conditionals.Add((condition, openConverterType)); + _genericTypes.Conditionals.Add((condition, new GenericConverterType(openConverterType, innerTypeSelector))); } - public void AddGeneric(Type openType, Type openConverterType) + public void AddGeneric(Type openType, Type openConverterType, Func innerTypeSelector = null) { 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"); if (!_mappedGenericTypes.TryGetValue(openType, out var converters)) - _mappedGenericTypes.Add(openType, converters = new ConverterTypeCollection()); - converters.DefaultConverterType = openConverterType; + _mappedGenericTypes.Add(openType, converters = new GenericConverterTypeCollection()); + converters.DefaultConverterType = new GenericConverterType(openConverterType, innerTypeSelector); } - public void AddGeneric(Type openType, Type openConverterType, Func condition) + public void AddGeneric(Type openType, Type openConverterType, Func condition, Func innerTypeSelector = null) { 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"); if (!_mappedGenericTypes.TryGetValue(openType, out var converters)) - _mappedGenericTypes.Add(openType, converters = new ConverterTypeCollection()); - converters.Conditionals.Add((condition, openConverterType)); + _mappedGenericTypes.Add(openType, converters = new GenericConverterTypeCollection()); + converters.Conditionals.Add((condition, new GenericConverterType(openConverterType, innerTypeSelector))); } - public void AddSelector(string groupKey, Type keyType, object keyValue, Type converterType) + public void AddSelector(Serializer serializer, string groupKey, Type keyType, object keyValue, Type converterType) { - var group = GetSelectorGroup(keyType, groupKey); - group.AddDynamicConverter(keyValue, converterType); + var group = CreateSelectorGroup(keyType, groupKey); + group.AddDynamicConverter(keyValue, BuildConverter(converterType, serializer)); } - public object Get(Type type, PropertyInfo propInfo = null) + public object Get(Serializer serializer, Type type, PropertyInfo propInfo = null, bool throwOnNotFound = true) { + //Check parent + object converter = _parent?.Get(serializer, type, propInfo, false); + if (converter != null) + return converter; + if (propInfo == null) //Can only cache top-level due to attribute influences { if (_cache.TryGetValue(type, out var result)) return result; - return _cache.GetOrAdd(type, Create(type, propInfo)); + converter = Create(serializer, type, propInfo, throwOnNotFound); + if (converter != null) + return _cache.GetOrAdd(type, converter); + return null; } - return Create(type, propInfo); + return Create(serializer, type, propInfo, throwOnNotFound); } - private object Create(Type type, PropertyInfo propInfo) + private object Create(Serializer serializer, Type type, PropertyInfo propInfo, bool throwOnNotFound) { TypeInfo typeInfo = type.GetTypeInfo(); //Mapped generic converters (List -> CollectionPropertyConverter) if (typeInfo.IsGenericType) { - var converterType = FindConverterType(typeInfo.GetGenericTypeDefinition(), _mappedGenericTypes, typeInfo, propInfo); - if (converterType != null) + var openGenericType = typeInfo.GetGenericTypeDefinition(); + + Type innerType = null; + if (openGenericType == typeof(Dictionary<,>) || openGenericType == typeof(IReadOnlyDictionary<,>)) //TODO: We can only assume key type for JSON + innerType = typeInfo.GenericTypeArguments[1]; //TValue + else if (openGenericType.GetTypeInfo().GenericTypeParameters.Length == 1) + innerType = typeInfo.GenericTypeArguments[0]; + + if (innerType != null) { - var innerType = typeInfo.GenericTypeArguments[0]; - converterType = converterType.MakeGenericType(innerType); - object innerConverter = Get(innerType, propInfo); - return Activator.CreateInstance(converterType, innerConverter); + var converterType = FindGenericConverterType(openGenericType, innerType, _mappedGenericTypes, typeInfo, propInfo); + if (converterType != null) + { + object innerConverter = serializer.GetConverter(innerType, propInfo); + return BuildConverter(converterType, serializer, innerConverter); + } } } @@ -110,38 +145,52 @@ namespace Discord.Serialization { var converterType = FindConverterType(type, _types, typeInfo, propInfo); if (converterType != null) - return Activator.CreateInstance(converterType); + return BuildConverter(converterType, serializer); } - + //Generic converters (Model -> ObjectPropertyConverter) { - var converterType = FindConverterType(_genericTypes, typeInfo, propInfo); + var converterType = FindGenericConverterType(type, _genericTypes, typeInfo, propInfo); if (converterType != null) { - converterType = converterType.MakeGenericType(type); - var converterTypeInfo = converterType.GetTypeInfo(); - - var constructors = converterTypeInfo.DeclaredConstructors.Where(x => !x.IsStatic).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) + if (type.IsArray) //We cant feed arrays through the mapped generic logic, emulate here { - var parameterType = parameters[0].ParameterType.GetTypeInfo(); - if (_serializerType.IsAssignableFrom(parameterType)) - return constructor.Invoke(new object[] { _serializer }); + object innerConverter = serializer.GetConverter(type.GetElementType(), propInfo); + return BuildConverter(converterType, serializer, innerConverter); } - throw new SerializationException($"{converterType.Name} has an unsupported constructor"); + else + return BuildConverter(converterType, serializer); } } - throw new InvalidOperationException($"Unsupported model type: {type.Name}"); + if (throwOnNotFound) + throw new InvalidOperationException($"Unsupported model type: {type.Name}"); + return null; + } + private object BuildConverter(Type converterType, Serializer serializer, object innerConverter = null) + { + var converterTypeInfo = converterType.GetTypeInfo(); + + var constructors = converterTypeInfo.DeclaredConstructors.Where(x => !x.IsStatic).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(); + + var args = new object[parameters.Length]; + for (int i = 0; i < args.Length; i++) + { + var paramType = parameters[i].ParameterType; + if (i == args.Length - 1 && innerConverter != null) + args[i] = innerConverter; + else if (_serializerType.IsAssignableFrom(paramType.GetTypeInfo())) + args[i] = serializer; + else + throw new SerializationException($"{converterType.Name} has an unsupported constructor"); + } + return constructor.Invoke(args); } private Type FindConverterType(Type type, Dictionary collection, TypeInfo typeInfo, PropertyInfo propInfo) @@ -150,6 +199,12 @@ namespace Discord.Serialization return FindConverterType(converters, typeInfo, propInfo); return null; } + private Type FindGenericConverterType(Type type, Type innerType, Dictionary collection, TypeInfo typeInfo, PropertyInfo propInfo) + { + if (collection.TryGetValue(type, out var converters)) + return FindGenericConverterType(innerType, converters, typeInfo, propInfo); + return null; + } private Type FindConverterType(ConverterTypeCollection converters, TypeInfo typeInfo, PropertyInfo propInfo) { for (int i = 0; i < converters.Conditionals.Count; i++) @@ -161,17 +216,45 @@ namespace Discord.Serialization return converters.DefaultConverterType; return null; } + private Type FindGenericConverterType(Type innerType, GenericConverterTypeCollection converters, TypeInfo typeInfo, PropertyInfo propInfo) + { + for (int i = 0; i < converters.Conditionals.Count; i++) + { + if (converters.Conditionals[i].Condition(typeInfo, propInfo)) + { + var converterType = converters.Conditionals[i].ConverterType; + return converterType.Type.MakeGenericType(converterType.InnerTypeSelector(innerType)); + } + } + if (converters.DefaultConverterType != null) + { + var converterType = converters.DefaultConverterType; + return converterType.Type.MakeGenericType(converterType.InnerTypeSelector(innerType)); + } + return null; + } public ISelectorGroup GetSelectorGroup(Type keyType, string groupKey) { - var keyGroup = GetSelectorKeyGroup(keyType); + var selectorGroup = _parent?.GetSelectorGroup(keyType, groupKey); + if (selectorGroup != null) + return selectorGroup; + + if (_selectorGroups.TryGetValue(keyType, out var keyGroup) && + keyGroup.TryGetValue(groupKey, out selectorGroup)) + return selectorGroup; + return null; + } + public ISelectorGroup CreateSelectorGroup(Type keyType, string groupKey) + { + var keyGroup = CreateSelectorKeyGroup(keyType); if (keyGroup.TryGetValue(groupKey, out var selectorGroup)) return selectorGroup; return CreateSelectorGroup(keyType, keyGroup, groupKey); } private ISelectorGroup CreateSelectorGroup(Type keyType, ConcurrentDictionary keyGroup, string groupKey) => keyGroup.GetOrAdd(groupKey, Activator.CreateInstance(typeof(SelectorGroup<>).MakeGenericType(keyType)) as ISelectorGroup); - private ConcurrentDictionary GetSelectorKeyGroup(Type keyType) + private ConcurrentDictionary CreateSelectorKeyGroup(Type keyType) => _selectorGroups.GetOrAdd(keyType, _ => new ConcurrentDictionary()); } } diff --git a/src/Discord.Net.Serialization/Json/Converters/Collections.cs b/src/Discord.Net.Serialization/Json/Converters/Collections.cs deleted file mode 100644 index 9dcf3b373..000000000 --- a/src/Discord.Net.Serialization/Json/Converters/Collections.cs +++ /dev/null @@ -1,37 +0,0 @@ -using System.Collections.Generic; -using System.Text.Json; - -namespace Discord.Serialization.Json.Converters -{ - public class ListPropertyConverter : IJsonPropertyConverter> - { - private readonly IJsonPropertyConverter _innerConverter; - - public ListPropertyConverter(IJsonPropertyConverter innerConverter) - { - _innerConverter = innerConverter; - } - - public List Read(PropertyMap map, object model, ref JsonReader reader, bool isTopLevel) - { - if ((isTopLevel && !reader.Read()) || reader.TokenType != JsonTokenType.StartArray) - throw new SerializationException("Bad input, expected StartArray"); - - var list = new List(); - while (reader.Read() && reader.TokenType != JsonTokenType.EndArray) - list.Add(_innerConverter.Read(map, model, ref reader, false)); - return list; - } - - public void Write(PropertyMap map, object model, ref JsonWriter writer, List value, bool isTopLevel) - { - if (isTopLevel) - writer.WriteArrayStart(map.Key); - else - writer.WriteArrayStart(); - for (int i = 0; i < value.Count; i++) - _innerConverter.Write(map, model, ref writer, value[i], false); - writer.WriteArrayEnd(); - } - } -} diff --git a/src/Discord.Net.Serialization/Json/Converters/Dictionary.cs b/src/Discord.Net.Serialization/Json/Converters/Dictionary.cs new file mode 100644 index 000000000..714f755fa --- /dev/null +++ b/src/Discord.Net.Serialization/Json/Converters/Dictionary.cs @@ -0,0 +1,46 @@ +using System.Collections.Generic; +using System.Text.Json; + +namespace Discord.Serialization.Json.Converters +{ + public class DictionaryPropertyConverter : JsonPropertyConverter> + { + private readonly JsonPropertyConverter _valueConverter; + + public DictionaryPropertyConverter(JsonPropertyConverter valueConverter) + { + _valueConverter = valueConverter; + } + + public override Dictionary Read(PropertyMap map, object model, ref JsonReader reader, bool isTopLevel) + { + if ((isTopLevel && !reader.Read()) || reader.TokenType != JsonTokenType.StartObject) + throw new SerializationException("Bad input, expected StartObject"); + + var dic = new Dictionary(); + while (reader.Read()) + { + if (reader.TokenType == JsonTokenType.EndObject) + return dic; + if (reader.TokenType != JsonTokenType.PropertyName) + throw new SerializationException("Bad input, expected PropertyName"); + + string key = reader.Value.ParseString(); + var value = _valueConverter.Read(map, model, ref reader, false); + dic.Add(key, value); + } + return dic; + } + + public override void Write(PropertyMap map, object model, ref JsonWriter writer, Dictionary value, string key) + { + if (key != null) + writer.WriteObjectStart(key); + else + writer.WriteObjectStart(); + foreach (var pair in value) + _valueConverter.Write(map, model, ref writer, pair.Value, pair.Key); + writer.WriteObjectEnd(); + } + } +} diff --git a/src/Discord.Net.Serialization/Json/Converters/Dynamic.cs b/src/Discord.Net.Serialization/Json/Converters/Dynamic.cs index bba1bf0c2..93dfa227b 100644 --- a/src/Discord.Net.Serialization/Json/Converters/Dynamic.cs +++ b/src/Discord.Net.Serialization/Json/Converters/Dynamic.cs @@ -3,9 +3,9 @@ namespace Discord.Serialization.Json.Converters { //TODO: Only supports cases where the key arrives first - public class DynamicPropertyConverter : IJsonPropertyConverter + public class DynamicPropertyConverter : JsonPropertyConverter { - public object Read(PropertyMap map, object model, ref JsonReader reader, bool isTopLevel) + public override object Read(PropertyMap map, object model, ref JsonReader reader, bool isTopLevel) { if (map.GetDynamicConverter(model, false) is IJsonPropertyReader converter) return converter.Read(map, model, ref reader, isTopLevel); @@ -16,19 +16,19 @@ namespace Discord.Serialization.Json.Converters } } - public void Write(PropertyMap map, object model, ref JsonWriter writer, object value, bool isTopLevel) + public override void Write(PropertyMap map, object model, ref JsonWriter writer, object value, string key) { if (value == null) { - if (isTopLevel) - writer.WriteAttributeNull(map.Key); + if (key != null) + writer.WriteAttributeNull(key); else writer.WriteNull(); } else { - var converter = map.GetDynamicConverter(model, true) as IJsonPropertyWriter; - converter.Write(map, model, ref writer, value, isTopLevel); + var converter = (IJsonPropertyWriter)map.GetDynamicConverter(model, true); + converter.Write(map, model, ref writer, value, key); } } } diff --git a/src/Discord.Net.Serialization/Json/Converters/Enum.cs b/src/Discord.Net.Serialization/Json/Converters/Enum.cs index 7a5ee96eb..84d10f312 100644 --- a/src/Discord.Net.Serialization/Json/Converters/Enum.cs +++ b/src/Discord.Net.Serialization/Json/Converters/Enum.cs @@ -2,12 +2,12 @@ namespace Discord.Serialization.Json.Converters { - public class Int64EnumPropertyConverter : IJsonPropertyConverter + public class Int64EnumPropertyConverter : JsonPropertyConverter where T : struct { private static readonly EnumMap _map = EnumMap.For(); - public T Read(PropertyMap map, object model, ref JsonReader reader, bool isTopLevel) + public override T Read(PropertyMap map, object model, ref JsonReader reader, bool isTopLevel) { if (isTopLevel) reader.Read(); @@ -15,22 +15,22 @@ namespace Discord.Serialization.Json.Converters throw new SerializationException("Bad input, expected Number or String"); return _map.FromInt64(reader.Value); } - public void Write(PropertyMap map, object model, ref JsonWriter writer, T value, bool isTopLevel) + public override void Write(PropertyMap map, object model, ref JsonWriter writer, T value, string key) { - long key = _map.ToInt64(value); - if (isTopLevel) - writer.WriteAttribute(map.Key, key); + long intVal = _map.ToInt64(value); + if (key != null) + writer.WriteAttribute(key, intVal); else - writer.WriteValue(key); + writer.WriteValue(intVal); } } - public class UInt64EnumPropertyConverter : IJsonPropertyConverter + public class UInt64EnumPropertyConverter : JsonPropertyConverter where T : struct { private static readonly EnumMap _map = EnumMap.For(); - public T Read(PropertyMap map, object model, ref JsonReader reader, bool isTopLevel) + public override T Read(PropertyMap map, object model, ref JsonReader reader, bool isTopLevel) { if (isTopLevel) reader.Read(); @@ -38,22 +38,22 @@ namespace Discord.Serialization.Json.Converters throw new SerializationException("Bad input, expected Number or String"); return _map.FromUInt64(reader.Value); } - public void Write(PropertyMap map, object model, ref JsonWriter writer, T value, bool isTopLevel) + public override void Write(PropertyMap map, object model, ref JsonWriter writer, T value, string key) { - ulong key = _map.ToUInt64(value); - if (isTopLevel) - writer.WriteAttribute(map.Key, key); + ulong uintVal = _map.ToUInt64(value); + if (key != null) + writer.WriteAttribute(key, uintVal); else - writer.WriteValue(key); + writer.WriteValue(uintVal); } } - public class StringEnumPropertyConverter : IJsonPropertyConverter + public class StringEnumPropertyConverter : JsonPropertyConverter where T : struct { private static readonly EnumMap _map = EnumMap.For(); - public T Read(PropertyMap map, object model, ref JsonReader reader, bool isTopLevel) + public override T Read(PropertyMap map, object model, ref JsonReader reader, bool isTopLevel) { if (isTopLevel) reader.Read(); @@ -61,13 +61,13 @@ namespace Discord.Serialization.Json.Converters throw new SerializationException("Bad input, expected String"); return _map.FromKey(reader.Value); } - public void Write(PropertyMap map, object model, ref JsonWriter writer, T value, bool isTopLevel) + public override void Write(PropertyMap map, object model, ref JsonWriter writer, T value, string key) { - string key = _map.ToUtf16Key(value); - if (isTopLevel) - writer.WriteAttribute(map.Key, key); + string strVal = _map.ToUtf16Key(value); + if (key != null) + writer.WriteAttribute(key, strVal); else - writer.WriteValue(key); + writer.WriteValue(strVal); } } } \ No newline at end of file diff --git a/src/Discord.Net.Serialization/Json/Converters/List.cs b/src/Discord.Net.Serialization/Json/Converters/List.cs new file mode 100644 index 000000000..b0f2f5073 --- /dev/null +++ b/src/Discord.Net.Serialization/Json/Converters/List.cs @@ -0,0 +1,77 @@ +using System.Collections.Generic; +using System.Text.Json; + +namespace Discord.Serialization.Json.Converters +{ + public class ArrayPropertyConverter : JsonPropertyConverter + { + private readonly JsonPropertyConverter _innerConverter; + + public ArrayPropertyConverter(JsonPropertyConverter innerConverter) + { + _innerConverter = innerConverter; + } + + public override T[] Read(PropertyMap map, object model, ref JsonReader reader, bool isTopLevel) + { + if ((isTopLevel && !reader.Read()) || reader.TokenType != JsonTokenType.StartArray) + throw new SerializationException("Bad input, expected StartArray"); + + var list = new List(); + while (reader.Read()) + { + if (reader.TokenType == JsonTokenType.EndArray) + return list.ToArray(); + list.Add(_innerConverter.Read(map, model, ref reader, false)); + } + throw new SerializationException("Bad input, expected EndArray"); + } + + public override void Write(PropertyMap map, object model, ref JsonWriter writer, T[] value, string key) + { + if (key != null) + writer.WriteArrayStart(key); + else + writer.WriteArrayStart(); + for (int i = 0; i < value.Length; i++) + _innerConverter.Write(map, model, ref writer, value[i], null); + writer.WriteArrayEnd(); + } + } + + public class ListPropertyConverter : JsonPropertyConverter> + { + private readonly JsonPropertyConverter _innerConverter; + + public ListPropertyConverter(JsonPropertyConverter innerConverter) + { + _innerConverter = innerConverter; + } + + public override List Read(PropertyMap map, object model, ref JsonReader reader, bool isTopLevel) + { + if ((isTopLevel && !reader.Read()) || reader.TokenType != JsonTokenType.StartArray) + throw new SerializationException("Bad input, expected StartArray"); + + var list = new List(); + while (reader.Read()) + { + if (reader.TokenType == JsonTokenType.EndArray) + return list; + list.Add(_innerConverter.Read(map, model, ref reader, false)); + } + throw new SerializationException("Bad input, expected EndArray"); + } + + public override void Write(PropertyMap map, object model, ref JsonWriter writer, List value, string key) + { + if (key != null) + writer.WriteArrayStart(key); + else + writer.WriteArrayStart(); + for (int i = 0; i < value.Count; i++) + _innerConverter.Write(map, model, ref writer, value[i], null); + writer.WriteArrayEnd(); + } + } +} diff --git a/src/Discord.Net.Serialization/Json/Converters/Nullable.cs b/src/Discord.Net.Serialization/Json/Converters/Nullable.cs index f326fc7a8..5519560fe 100644 --- a/src/Discord.Net.Serialization/Json/Converters/Nullable.cs +++ b/src/Discord.Net.Serialization/Json/Converters/Nullable.cs @@ -2,17 +2,17 @@ namespace Discord.Serialization.Json.Converters { - public class NullablePropertyConverter : IJsonPropertyConverter + public class NullablePropertyConverter : JsonPropertyConverter where T : struct { - private readonly IJsonPropertyConverter _innerConverter; + private readonly JsonPropertyConverter _innerConverter; - public NullablePropertyConverter(IJsonPropertyConverter innerConverter) + public NullablePropertyConverter(JsonPropertyConverter innerConverter) { _innerConverter = innerConverter; } - public T? Read(PropertyMap map, object model, ref JsonReader reader, bool isTopLevel) + public override T? Read(PropertyMap map, object model, ref JsonReader reader, bool isTopLevel) { if (isTopLevel) reader.Read(); @@ -21,14 +21,14 @@ namespace Discord.Serialization.Json.Converters return _innerConverter.Read(map, model, ref reader, false); } - public void Write(PropertyMap map, object model, ref JsonWriter writer, T? value, bool isTopLevel) + public override void Write(PropertyMap map, object model, ref JsonWriter writer, T? value, string key) { if (value.HasValue) - _innerConverter.Write(map, model, ref writer, value.Value, isTopLevel); + _innerConverter.Write(map, model, ref writer, value.Value, key); else { - if (isTopLevel) - writer.WriteAttributeNull(map.Key); + if (key != null) + writer.WriteAttributeNull(key); else writer.WriteNull(); } diff --git a/src/Discord.Net.Serialization/Json/Converters/Object.cs b/src/Discord.Net.Serialization/Json/Converters/Object.cs index f4a9f8456..3df33c36c 100644 --- a/src/Discord.Net.Serialization/Json/Converters/Object.cs +++ b/src/Discord.Net.Serialization/Json/Converters/Object.cs @@ -2,7 +2,7 @@ namespace Discord.Serialization.Json.Converters { - public class ObjectPropertyConverter : IJsonPropertyConverter + public class ObjectPropertyConverter : JsonPropertyConverter where T : class, new() { private readonly ModelMap _map; @@ -12,19 +12,23 @@ namespace Discord.Serialization.Json.Converters _map = serializer.MapModel(); } - public T Read(PropertyMap map, object model, ref JsonReader reader, bool isTopLevel) + public override T Read(PropertyMap map, object model, ref JsonReader reader, bool isTopLevel) { var subModel = new T(); - if ((isTopLevel && !reader.Read()) || reader.TokenType != JsonTokenType.StartObject) - throw new SerializationException("Bad input, expected StartObject"); + if ((isTopLevel && !reader.Read()) || (reader.TokenType != JsonTokenType.StartObject && reader.ValueType != JsonValueType.Null)) + throw new SerializationException("Bad input, expected StartObject or Null"); + + if (reader.ValueType == JsonValueType.Null) + return null; + while (reader.Read()) { if (reader.TokenType == JsonTokenType.EndObject) return subModel; if (reader.TokenType != JsonTokenType.PropertyName) throw new SerializationException("Bad input, expected PropertyName"); - + if (_map.TryGetProperty(reader.Value, out var property)) (property as IJsonPropertyMap).Read(subModel, ref reader); else @@ -32,15 +36,25 @@ namespace Discord.Serialization.Json.Converters } throw new SerializationException("Bad input, expected EndObject"); } - public void Write(PropertyMap map, object model, ref JsonWriter writer, T value, bool isTopLevel) + public override void Write(PropertyMap map, object model, ref JsonWriter writer, T value, string key) { - if (isTopLevel) - writer.WriteObjectStart(map.Key); + if (value == null) + { + if (key != null) + writer.WriteAttributeNull(key); + else + writer.WriteNull(); + } else - writer.WriteObjectStart(); - for (int i = 0; i < _map.Properties.Length; i++) - (_map.Properties[i] as IJsonPropertyMap).Write(value, ref writer); - writer.WriteObjectEnd(); + { + if (key != null) + writer.WriteObjectStart(key); + else + writer.WriteObjectStart(); + for (int i = 0; i < _map.Properties.Length; i++) + (_map.Properties[i] as IJsonPropertyMap).Write(value, ref writer); + writer.WriteObjectEnd(); + } } } } \ No newline at end of file diff --git a/src/Discord.Net.Serialization/Json/Converters/Primitives.DateTime.cs b/src/Discord.Net.Serialization/Json/Converters/Primitives.DateTime.cs index 0e860f08d..93e2fbd74 100644 --- a/src/Discord.Net.Serialization/Json/Converters/Primitives.DateTime.cs +++ b/src/Discord.Net.Serialization/Json/Converters/Primitives.DateTime.cs @@ -3,9 +3,9 @@ using System.Text.Json; namespace Discord.Serialization.Json.Converters { - public class DateTimePropertyConverter : IJsonPropertyConverter + public class DateTimePropertyConverter : JsonPropertyConverter { - public DateTime Read(PropertyMap map, object model, ref JsonReader reader, bool isTopLevel) + public override DateTime Read(PropertyMap map, object model, ref JsonReader reader, bool isTopLevel) { if (isTopLevel) reader.Read(); @@ -13,18 +13,18 @@ namespace Discord.Serialization.Json.Converters throw new SerializationException("Bad input, expected String"); return reader.ParseDateTime(); } - public void Write(PropertyMap map, object model, ref JsonWriter writer, DateTime value, bool isTopLevel) + public override void Write(PropertyMap map, object model, ref JsonWriter writer, DateTime value, string key) { - if (isTopLevel) - writer.WriteAttribute(map.Key, value); + if (key != null) + writer.WriteAttribute(key, value); else writer.WriteValue(value); } } - public class DateTimeOffsetPropertyConverter : IJsonPropertyConverter + public class DateTimeOffsetPropertyConverter : JsonPropertyConverter { - public DateTimeOffset Read(PropertyMap map, object model, ref JsonReader reader, bool isTopLevel) + public override DateTimeOffset Read(PropertyMap map, object model, ref JsonReader reader, bool isTopLevel) { if (isTopLevel) reader.Read(); @@ -32,10 +32,10 @@ namespace Discord.Serialization.Json.Converters throw new SerializationException("Bad input, expected String"); return reader.ParseDateTimeOffset(); } - public void Write(PropertyMap map, object model, ref JsonWriter writer, DateTimeOffset value, bool isTopLevel) + public override void Write(PropertyMap map, object model, ref JsonWriter writer, DateTimeOffset value, string key) { - if (isTopLevel) - writer.WriteAttribute(map.Key, value); + if (key != null) + writer.WriteAttribute(key, value); else writer.WriteValue(value); } diff --git a/src/Discord.Net.Serialization/Json/Converters/Primitives.Float.cs b/src/Discord.Net.Serialization/Json/Converters/Primitives.Float.cs index 5f715eb8f..abb6bb1a0 100644 --- a/src/Discord.Net.Serialization/Json/Converters/Primitives.Float.cs +++ b/src/Discord.Net.Serialization/Json/Converters/Primitives.Float.cs @@ -2,9 +2,9 @@ namespace Discord.Serialization.Json.Converters { - public class SinglePropertyConverter : IJsonPropertyConverter + public class SinglePropertyConverter : JsonPropertyConverter { - public float Read(PropertyMap map, object model, ref JsonReader reader, bool isTopLevel) + public override float Read(PropertyMap map, object model, ref JsonReader reader, bool isTopLevel) { if (isTopLevel) reader.Read(); @@ -12,18 +12,18 @@ namespace Discord.Serialization.Json.Converters throw new SerializationException("Bad input, expected Number or String"); return reader.ParseSingle(); } - public void Write(PropertyMap map, object model, ref JsonWriter writer, float value, bool isTopLevel) + public override void Write(PropertyMap map, object model, ref JsonWriter writer, float value, string key) { - if (isTopLevel) - writer.WriteAttribute(map.Key, value.ToString()); + if (key != null) + writer.WriteAttribute(key, value.ToString()); else writer.WriteValue(value.ToString()); } } - public class DoublePropertyConverter : IJsonPropertyConverter + public class DoublePropertyConverter : JsonPropertyConverter { - public double Read(PropertyMap map, object model, ref JsonReader reader, bool isTopLevel) + public override double Read(PropertyMap map, object model, ref JsonReader reader, bool isTopLevel) { if (isTopLevel) reader.Read(); @@ -31,18 +31,18 @@ namespace Discord.Serialization.Json.Converters throw new SerializationException("Bad input, expected Number or String"); return reader.ParseDouble(); } - public void Write(PropertyMap map, object model, ref JsonWriter writer, double value, bool isTopLevel) + public override void Write(PropertyMap map, object model, ref JsonWriter writer, double value, string key) { - if (isTopLevel) - writer.WriteAttribute(map.Key, value.ToString()); + if (key != null) + writer.WriteAttribute(key, value.ToString()); else writer.WriteValue(value.ToString()); } } - internal class DecimalPropertyConverter : IJsonPropertyConverter + internal class DecimalPropertyConverter : JsonPropertyConverter { - public decimal Read(PropertyMap map, object model, ref JsonReader reader, bool isTopLevel) + public override decimal Read(PropertyMap map, object model, ref JsonReader reader, bool isTopLevel) { if (isTopLevel) reader.Read(); @@ -50,10 +50,10 @@ namespace Discord.Serialization.Json.Converters throw new SerializationException("Bad input, expected Number or String"); return reader.ParseDecimal(); } - public void Write(PropertyMap map, object model, ref JsonWriter writer, decimal value, bool isTopLevel) + public override void Write(PropertyMap map, object model, ref JsonWriter writer, decimal value, string key) { - if (isTopLevel) - writer.WriteAttribute(map.Key, value.ToString()); + if (key != null) + writer.WriteAttribute(key, value.ToString()); else writer.WriteValue(value.ToString()); } diff --git a/src/Discord.Net.Serialization/Json/Converters/Primitives.Other.cs b/src/Discord.Net.Serialization/Json/Converters/Primitives.Other.cs index 9273ad2d5..528c4b539 100644 --- a/src/Discord.Net.Serialization/Json/Converters/Primitives.Other.cs +++ b/src/Discord.Net.Serialization/Json/Converters/Primitives.Other.cs @@ -3,9 +3,9 @@ using System.Text.Json; namespace Discord.Serialization.Json.Converters { - public class BooleanPropertyConverter : IJsonPropertyConverter + public class BooleanPropertyConverter : JsonPropertyConverter { - public bool Read(PropertyMap map, object model, ref JsonReader reader, bool isTopLevel) + public override bool Read(PropertyMap map, object model, ref JsonReader reader, bool isTopLevel) { if (isTopLevel) reader.Read(); @@ -16,18 +16,18 @@ namespace Discord.Serialization.Json.Converters default: throw new SerializationException("Bad input, expected False or True"); } } - public void Write(PropertyMap map, object model, ref JsonWriter writer, bool value, bool isTopLevel) + public override void Write(PropertyMap map, object model, ref JsonWriter writer, bool value, string key) { - if (isTopLevel) - writer.WriteAttribute(map.Key, value); + if (key != null) + writer.WriteAttribute(key, value); else writer.WriteValue(value); } } - public class GuidPropertyConverter : IJsonPropertyConverter + public class GuidPropertyConverter : JsonPropertyConverter { - public Guid Read(PropertyMap map, object model, ref JsonReader reader, bool isTopLevel) + public override Guid Read(PropertyMap map, object model, ref JsonReader reader, bool isTopLevel) { if (isTopLevel) reader.Read(); @@ -35,10 +35,10 @@ namespace Discord.Serialization.Json.Converters throw new SerializationException("Bad input, expected String"); return reader.ParseGuid(); } - public void Write(PropertyMap map, object model, ref JsonWriter writer, Guid value, bool isTopLevel) + public override void Write(PropertyMap map, object model, ref JsonWriter writer, Guid value, string key) { - if (isTopLevel) - writer.WriteAttribute(map.Key, value.ToString()); + if (key != null) + writer.WriteAttribute(key, value.ToString()); else writer.WriteValue(value.ToString()); } diff --git a/src/Discord.Net.Serialization/Json/Converters/Primitives.Signed.cs b/src/Discord.Net.Serialization/Json/Converters/Primitives.Signed.cs index 21bd65ec5..b738efcca 100644 --- a/src/Discord.Net.Serialization/Json/Converters/Primitives.Signed.cs +++ b/src/Discord.Net.Serialization/Json/Converters/Primitives.Signed.cs @@ -2,9 +2,9 @@ namespace Discord.Serialization.Json.Converters { - public class Int8PropertyConverter : IJsonPropertyConverter + public class Int8PropertyConverter : JsonPropertyConverter { - public sbyte Read(PropertyMap map, object model, ref JsonReader reader, bool isTopLevel) + public override sbyte Read(PropertyMap map, object model, ref JsonReader reader, bool isTopLevel) { if (isTopLevel) reader.Read(); @@ -12,18 +12,18 @@ namespace Discord.Serialization.Json.Converters throw new SerializationException("Bad input, expected Number or String"); return reader.ParseInt8(); } - public void Write(PropertyMap map, object model, ref JsonWriter writer, sbyte value, bool isTopLevel) + public override void Write(PropertyMap map, object model, ref JsonWriter writer, sbyte value, string key) { - if (isTopLevel) - writer.WriteAttribute(map.Key, value); + if (key != null) + writer.WriteAttribute(key, value); else writer.WriteValue(value); } } - public class Int16PropertyConverter : IJsonPropertyConverter + public class Int16PropertyConverter : JsonPropertyConverter { - public short Read(PropertyMap map, object model, ref JsonReader reader, bool isTopLevel) + public override short Read(PropertyMap map, object model, ref JsonReader reader, bool isTopLevel) { if (isTopLevel) reader.Read(); @@ -31,18 +31,18 @@ namespace Discord.Serialization.Json.Converters throw new SerializationException("Bad input, expected Number or String"); return reader.ParseInt16(); } - public void Write(PropertyMap map, object model, ref JsonWriter writer, short value, bool isTopLevel) + public override void Write(PropertyMap map, object model, ref JsonWriter writer, short value, string key) { - if (isTopLevel) - writer.WriteAttribute(map.Key, value); + if (key != null) + writer.WriteAttribute(key, value); else writer.WriteValue(value); } } - public class Int32PropertyConverter : IJsonPropertyConverter + public class Int32PropertyConverter : JsonPropertyConverter { - public int Read(PropertyMap map, object model, ref JsonReader reader, bool isTopLevel) + public override int Read(PropertyMap map, object model, ref JsonReader reader, bool isTopLevel) { if (isTopLevel) reader.Read(); @@ -50,18 +50,18 @@ namespace Discord.Serialization.Json.Converters throw new SerializationException("Bad input, expected Number or String"); return reader.ParseInt32(); } - public void Write(PropertyMap map, object model, ref JsonWriter writer, int value, bool isTopLevel) + public override void Write(PropertyMap map, object model, ref JsonWriter writer, int value, string key) { - if (isTopLevel) - writer.WriteAttribute(map.Key, value); + if (key != null) + writer.WriteAttribute(key, value); else writer.WriteValue(value); } } - public class Int64PropertyConverter : IJsonPropertyConverter + public class Int64PropertyConverter : JsonPropertyConverter { - public long Read(PropertyMap map, object model, ref JsonReader reader, bool isTopLevel) + public override long Read(PropertyMap map, object model, ref JsonReader reader, bool isTopLevel) { if (isTopLevel) reader.Read(); @@ -69,10 +69,10 @@ namespace Discord.Serialization.Json.Converters throw new SerializationException("Bad input, expected Number or String"); return reader.ParseInt64(); } - public void Write(PropertyMap map, object model, ref JsonWriter writer, long value, bool isTopLevel) + public override void Write(PropertyMap map, object model, ref JsonWriter writer, long value, string key) { - if (isTopLevel) - writer.WriteAttribute(map.Key, value.ToString()); + if (key != null) + writer.WriteAttribute(key, value.ToString()); else writer.WriteValue(value.ToString()); } diff --git a/src/Discord.Net.Serialization/Json/Converters/Primitives.String.cs b/src/Discord.Net.Serialization/Json/Converters/Primitives.String.cs index 6b4ce384c..4b9841c95 100644 --- a/src/Discord.Net.Serialization/Json/Converters/Primitives.String.cs +++ b/src/Discord.Net.Serialization/Json/Converters/Primitives.String.cs @@ -2,9 +2,9 @@ namespace Discord.Serialization.Json.Converters { - /*public class CharPropertyConverter : IJsonPropertyConverter + /*public class CharPropertyConverter : JsonPropertyConverter { - public char Read(PropertyMap map, object model, ref JsonReader reader, bool isTopLevel) + public override char Read(PropertyMap map, object model, ref JsonReader reader, bool isTopLevel) { if (isTopLevel) reader.Read(); @@ -12,18 +12,18 @@ namespace Discord.Serialization.Json.Converters throw new SerializationException("Bad input, expected String"); return reader.ParseChar(); } - public void Write(PropertyMap map, object model, ref JsonWriter writer, char value, bool isTopLevel) + public override void Write(PropertyMap map, object model, ref JsonWriter writer, char value, string key) { - if (isTopLevel) - writer.WriteAttribute(map.Key, value); + if (key != null) + writer.WriteAttribute(key, value); else - writer.WriteValue(value.ToString()); + writer.WriteValue(value); } }*/ - public class StringPropertyConverter : IJsonPropertyConverter + public class StringPropertyConverter : JsonPropertyConverter { - public string Read(PropertyMap map, object model, ref JsonReader reader, bool isTopLevel) + public override string Read(PropertyMap map, object model, ref JsonReader reader, bool isTopLevel) { if (isTopLevel) reader.Read(); @@ -33,10 +33,10 @@ namespace Discord.Serialization.Json.Converters throw new SerializationException("Bad input, expected String"); return reader.ParseString(); } - public void Write(PropertyMap map, object model, ref JsonWriter writer, string value, bool isTopLevel) + public override void Write(PropertyMap map, object model, ref JsonWriter writer, string value, string key) { - if (isTopLevel) - writer.WriteAttribute(map.Key, value); + if (key != null) + writer.WriteAttribute(key, value); else writer.WriteValue(value); } diff --git a/src/Discord.Net.Serialization/Json/Converters/Primitives.Unsigned.cs b/src/Discord.Net.Serialization/Json/Converters/Primitives.Unsigned.cs index 7136ea290..52cb597d9 100644 --- a/src/Discord.Net.Serialization/Json/Converters/Primitives.Unsigned.cs +++ b/src/Discord.Net.Serialization/Json/Converters/Primitives.Unsigned.cs @@ -2,9 +2,9 @@ namespace Discord.Serialization.Json.Converters { - public class UInt8PropertyConverter : IJsonPropertyConverter + public class UInt8PropertyConverter : JsonPropertyConverter { - public byte Read(PropertyMap map, object model, ref JsonReader reader, bool isTopLevel) + public override byte Read(PropertyMap map, object model, ref JsonReader reader, bool isTopLevel) { if (isTopLevel) reader.Read(); @@ -12,18 +12,18 @@ namespace Discord.Serialization.Json.Converters throw new SerializationException("Bad input, expected Number or String"); return reader.ParseUInt8(); } - public void Write(PropertyMap map, object model, ref JsonWriter writer, byte value, bool isTopLevel) + public override void Write(PropertyMap map, object model, ref JsonWriter writer, byte value, string key) { - if (isTopLevel) - writer.WriteAttribute(map.Key, value); + if (key != null) + writer.WriteAttribute(key, value); else writer.WriteValue(value); } } - public class UInt16PropertyConverter : IJsonPropertyConverter + public class UInt16PropertyConverter : JsonPropertyConverter { - public ushort Read(PropertyMap map, object model, ref JsonReader reader, bool isTopLevel) + public override ushort Read(PropertyMap map, object model, ref JsonReader reader, bool isTopLevel) { if (isTopLevel) reader.Read(); @@ -31,18 +31,18 @@ namespace Discord.Serialization.Json.Converters throw new SerializationException("Bad input, expected Number or String"); return reader.ParseUInt16(); } - public void Write(PropertyMap map, object model, ref JsonWriter writer, ushort value, bool isTopLevel) + public override void Write(PropertyMap map, object model, ref JsonWriter writer, ushort value, string key) { - if (isTopLevel) - writer.WriteAttribute(map.Key, value); + if (key != null) + writer.WriteAttribute(key, value); else writer.WriteValue(value); } } - public class UInt32PropertyConverter : IJsonPropertyConverter + public class UInt32PropertyConverter : JsonPropertyConverter { - public uint Read(PropertyMap map, object model, ref JsonReader reader, bool isTopLevel) + public override uint Read(PropertyMap map, object model, ref JsonReader reader, bool isTopLevel) { if (isTopLevel) reader.Read(); @@ -50,18 +50,18 @@ namespace Discord.Serialization.Json.Converters throw new SerializationException("Bad input, expected Number or String"); return reader.ParseUInt32(); } - public void Write(PropertyMap map, object model, ref JsonWriter writer, uint value, bool isTopLevel) + public override void Write(PropertyMap map, object model, ref JsonWriter writer, uint value, string key) { - if (isTopLevel) - writer.WriteAttribute(map.Key, value); + if (key != null) + writer.WriteAttribute(key, value); else writer.WriteValue(value); } } - public class UInt64PropertyConverter : IJsonPropertyConverter + public class UInt64PropertyConverter : JsonPropertyConverter { - public ulong Read(PropertyMap map, object model, ref JsonReader reader, bool isTopLevel) + public override ulong Read(PropertyMap map, object model, ref JsonReader reader, bool isTopLevel) { if (isTopLevel) reader.Read(); @@ -69,10 +69,10 @@ namespace Discord.Serialization.Json.Converters throw new SerializationException("Bad input, expected Number or String"); return reader.ParseUInt64(); } - public void Write(PropertyMap map, object model, ref JsonWriter writer, ulong value, bool isTopLevel) + public override void Write(PropertyMap map, object model, ref JsonWriter writer, ulong value, string key) { - if (isTopLevel) - writer.WriteAttribute(map.Key, value.ToString()); + if (key != null) + writer.WriteAttribute(key, value.ToString()); else writer.WriteValue(value.ToString()); } diff --git a/src/Discord.Net.Serialization/Json/Converters/Struct.cs b/src/Discord.Net.Serialization/Json/Converters/Struct.cs new file mode 100644 index 000000000..dcea87247 --- /dev/null +++ b/src/Discord.Net.Serialization/Json/Converters/Struct.cs @@ -0,0 +1,46 @@ +using System.Text.Json; + +namespace Discord.Serialization.Json.Converters +{ + /*public class StructPropertyConverter : JsonPropertyConverter + where T : struct, new() + { + private readonly ModelMap _map; + + public StructPropertyConverter(Serializer serializer) + { + _map = serializer.MapModel(); + } + + public T Read(PropertyMap map, object model, ref JsonReader reader, bool isTopLevel) + { + var subModel = 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 subModel; + if (reader.TokenType != JsonTokenType.PropertyName) + throw new SerializationException("Bad input, expected PropertyName"); + + if (_map.TryGetProperty(reader.Value, out var property)) + (property as IJsonPropertyMap).Read(subModel, ref reader); + else + JsonReaderUtils.Skip(ref reader); //Unknown property, skip + } + throw new SerializationException("Bad input, expected EndObject"); + } + public void Write(PropertyMap map, object model, ref JsonWriter writer, T value, string key) + { + if (key != null) + writer.WriteObjectStart(key); + else + writer.WriteObjectStart(); + for (int i = 0; i < _map.Properties.Length; i++) + (_map.Properties[i] as IJsonPropertyMap).Write(value, ref writer); + writer.WriteObjectEnd(); + } + }*/ +} \ No newline at end of file diff --git a/src/Discord.Net.Serialization/Json/DefaultJsonSerializer.cs b/src/Discord.Net.Serialization/Json/DefaultJsonSerializer.cs new file mode 100644 index 000000000..f31c7cb04 --- /dev/null +++ b/src/Discord.Net.Serialization/Json/DefaultJsonSerializer.cs @@ -0,0 +1,81 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Reflection; + +namespace Discord.Serialization.Json +{ + public class DefaultJsonSerializer : JsonSerializer + { + private static readonly Lazy _singleton = new Lazy(); + public static DefaultJsonSerializer Global => _singleton.Value; + + public DefaultJsonSerializer() + : this((JsonSerializer)null) { } + public DefaultJsonSerializer(JsonSerializer parent) + : base(parent) + { + AddConverter(); + AddConverter(); + AddConverter(); + AddConverter(); + + AddConverter(); + AddConverter(); + AddConverter(); + AddConverter(); + + AddConverter(); + AddConverter(); + AddConverter(); + + //AddConverter(); //TODO: char.Parse does not support Json.Net's serialization + AddConverter(); + + AddConverter(); + AddConverter(); + + AddConverter(); + AddConverter(); + + AddConverter( + (type, prop) => prop.GetCustomAttributes().Any()); + + AddGenericConverter(typeof(List<>), typeof(Converters.ListPropertyConverter<>)); + //AddGenericConverter(typeof(IReadOnlyList<>), typeof(Converters.ListPropertyConverter<>)); + //AddGenericConverter(typeof(IReadOnlyCollection<>), typeof(Converters.ListPropertyConverter<>)); + //AddGenericConverter(typeof(IEnumerable<>), typeof(Converters.ListPropertyConverter<>)); + AddGenericConverter(typeof(Dictionary<,>), typeof(Converters.DictionaryPropertyConverter<>)); + //AddGenericConverter(typeof(IReadOnlyDictionary<,>), typeof(Converters.DictionaryPropertyConverter<>)); + AddGenericConverter(typeof(Nullable<>), typeof(Converters.NullablePropertyConverter<>)); + + AddGenericConverter(typeof(Converters.ArrayPropertyConverter<>), //Arrays + (type, prop) => type.IsArray, innerType => innerType.GetElementType()); + AddGenericConverter(typeof(Converters.StringEnumPropertyConverter<>), //Enums : string + (type, prop) => type.IsEnum && prop.GetCustomAttribute() != null); + AddGenericConverter(typeof(Converters.Int64EnumPropertyConverter<>), //Enums : sbyte/short/int/long + (type, prop) => type.IsEnum && IsSignedEnum(Enum.GetUnderlyingType(type.AsType()))); + AddGenericConverter(typeof(Converters.UInt64EnumPropertyConverter<>), //Enums: byte/ushort/uint/ulong + (type, prop) => type.IsEnum && IsUnsignedEnum(Enum.GetUnderlyingType(type.AsType()))); + AddGenericConverter(typeof(Converters.ObjectPropertyConverter<>), //Classes + (type, prop) => type.IsClass && type.DeclaredConstructors.Any(x => x.GetParameters().Length == 0)); + + //TODO: Structs? + } + + private DefaultJsonSerializer(DefaultJsonSerializer parent) + : base(parent) { } + public DefaultJsonSerializer CreateScope() => new DefaultJsonSerializer(this); + + private static bool IsSignedEnum(Type underlyingType) + => underlyingType == typeof(sbyte) || + underlyingType == typeof(short) || + underlyingType == typeof(int) || + underlyingType == typeof(long); + private static bool IsUnsignedEnum(Type underlyingType) + => underlyingType == typeof(byte) || + underlyingType == typeof(ushort) || + underlyingType == typeof(uint) || + underlyingType == typeof(ulong); + } +} diff --git a/src/Discord.Net.Serialization/Json/IJsonPropertyConverter.cs b/src/Discord.Net.Serialization/Json/IJsonPropertyConverter.cs index 824df4f0e..d5b7e6c3e 100644 --- a/src/Discord.Net.Serialization/Json/IJsonPropertyConverter.cs +++ b/src/Discord.Net.Serialization/Json/IJsonPropertyConverter.cs @@ -2,7 +2,14 @@ namespace Discord.Serialization.Json { - public interface IJsonPropertyConverter : IJsonPropertyReader, IJsonPropertyWriter { } + public abstract class JsonPropertyConverter : IJsonPropertyReader, IJsonPropertyWriter, IJsonPropertyWriter + { + public abstract T Read(PropertyMap map, object model, ref JsonReader reader, bool isTopLevel); + public abstract void Write(PropertyMap map, object model, ref JsonWriter writer, T value, string key); + + void IJsonPropertyWriter.Write(PropertyMap map, object model, ref JsonWriter writer, object value, string key) + => Write(map, model, ref writer, (T)value, key); + } public interface IJsonPropertyReader { @@ -10,6 +17,10 @@ namespace Discord.Serialization.Json } public interface IJsonPropertyWriter { - void Write(PropertyMap map, object model, ref JsonWriter writer, T value, bool isTopLevel); + void Write(PropertyMap map, object model, ref JsonWriter writer, T value, string key); + } + public interface IJsonPropertyWriter + { + void Write(PropertyMap map, object model, ref JsonWriter writer, object value, string key); } } diff --git a/src/Discord.Net.Serialization/Json/JsonPropertyMap.cs b/src/Discord.Net.Serialization/Json/JsonPropertyMap.cs index 716a2bee6..5ee0289ff 100644 --- a/src/Discord.Net.Serialization/Json/JsonPropertyMap.cs +++ b/src/Discord.Net.Serialization/Json/JsonPropertyMap.cs @@ -15,11 +15,11 @@ namespace Discord.Serialization.Json internal class JsonPropertyMap : PropertyMap, IJsonPropertyMap { - private readonly IJsonPropertyConverter _converter; + private readonly JsonPropertyConverter _converter; private readonly Func _getFunc; private readonly Action _setFunc; - public JsonPropertyMap(Serializer serializer, PropertyInfo propInfo, IJsonPropertyConverter converter) + public JsonPropertyMap(Serializer serializer, PropertyInfo propInfo, JsonPropertyConverter converter) : base(serializer, propInfo) { _converter = converter; @@ -28,17 +28,17 @@ namespace Discord.Serialization.Json _setFunc = propInfo.SetMethod.CreateDelegate(typeof(Action)) as Action; } + public void Read(TModel model, ref JsonReader reader) + { + var value = _converter.Read(this, model, ref reader, true); + _setFunc(model, value); + } public void Write(TModel model, ref JsonWriter writer) { var value = _getFunc(model); if (value == null && ExcludeNull) return; - _converter.Write(this, model, ref writer, value, true); - } - public void Read(TModel model, ref JsonReader reader) - { - var value = _converter.Read(this, model, ref reader, true); - _setFunc(model, value); + _converter.Write(this, model, ref writer, value, Key); } } } diff --git a/src/Discord.Net.Serialization/Json/JsonSerializer.cs b/src/Discord.Net.Serialization/Json/JsonSerializer.cs index e17bacb06..18581ed36 100644 --- a/src/Discord.Net.Serialization/Json/JsonSerializer.cs +++ b/src/Discord.Net.Serialization/Json/JsonSerializer.cs @@ -1,6 +1,4 @@ using System; -using System.Collections.Generic; -using System.Linq; using System.Reflection; using System.Text; using System.Text.Formatting; @@ -8,64 +6,20 @@ using System.Text.Json; namespace Discord.Serialization.Json { - public class JsonSerializer : Serializer + public abstract class JsonSerializer : Serializer { - private static readonly Lazy _singleton = new Lazy(); - public static JsonSerializer Global => _singleton.Value; - - public JsonSerializer() - { - AddConverter(); - AddConverter(); - AddConverter(); - AddConverter(); - - AddConverter(); - AddConverter(); - AddConverter(); - AddConverter(); - - AddConverter(); - AddConverter(); - AddConverter(); - - //AddConverter(); //TODO: char.Parse does not support Json.Net's serialization - AddConverter(); - - AddConverter(); - AddConverter(); - - AddConverter(); - AddConverter(); - - AddConverter( - (type, prop) => prop.GetCustomAttributes().Any()); - - AddGenericConverter(typeof(List<>), typeof(Converters.ListPropertyConverter<>)); - AddGenericConverter(typeof(Nullable<>), typeof(Converters.NullablePropertyConverter<>)); - - AddGenericConverter(typeof(Converters.StringEnumPropertyConverter<>), - (type, prop) => type.IsEnum && prop.GetCustomAttribute() != null); - AddGenericConverter(typeof(Converters.Int64EnumPropertyConverter<>), - (type, prop) => type.IsEnum && IsSignedEnum(Enum.GetUnderlyingType(type.AsType()))); - AddGenericConverter(typeof(Converters.UInt64EnumPropertyConverter<>), - (type, prop) => type.IsEnum && IsUnsignedEnum(Enum.GetUnderlyingType(type.AsType()))); - AddGenericConverter(typeof(Converters.ObjectPropertyConverter<>), - (type, prop) => type.IsClass); - } protected JsonSerializer(JsonSerializer parent) : base(parent) { } - public JsonSerializer CreateScope() => new JsonSerializer(this); public void AddConverter() - where TConverter : class, IJsonPropertyConverter + where TConverter : JsonPropertyConverter => AddConverter(typeof(TValue), typeof(TConverter)); public void AddConverter(Func condition) - where TConverter : class, IJsonPropertyConverter + where TConverter : JsonPropertyConverter => AddConverter(typeof(TValue), typeof(TConverter), condition); protected override PropertyMap CreatePropertyMap(PropertyInfo propInfo) { - var converter = (IJsonPropertyConverter)GetConverter(typeof(TValue), propInfo); + var converter = (JsonPropertyConverter)GetConverter(typeof(TValue), propInfo); return new JsonPropertyMap(this, propInfo, converter); } @@ -74,25 +28,14 @@ namespace Discord.Serialization.Json var reader = new JsonReader(data.Span, SymbolTable.InvariantUtf8); if (!reader.Read()) return default; - var converter = GetConverter(typeof(TModel)) as IJsonPropertyConverter; + var converter = GetConverter(typeof(TModel)) as JsonPropertyConverter; return converter.Read(null, null, ref reader, false); } public override void Write(ArrayFormatter stream, TModel model) { var writer = new JsonWriter(stream); - var converter = GetConverter(typeof(TModel)) as IJsonPropertyConverter; - converter.Write(null, null, ref writer, model, false); + var converter = GetConverter(typeof(TModel)) as JsonPropertyConverter; + converter.Write(null, null, ref writer, model, null); } - - private static bool IsSignedEnum(Type underlyingType) - => underlyingType == typeof(sbyte) || - underlyingType == typeof(short) || - underlyingType == typeof(int) || - underlyingType == typeof(long); - private static bool IsUnsignedEnum(Type underlyingType) - => underlyingType == typeof(byte) || - underlyingType == typeof(ushort) || - underlyingType == typeof(uint) || - underlyingType == typeof(ulong); } } diff --git a/src/Discord.Net.Serialization/PropertyMap.cs b/src/Discord.Net.Serialization/PropertyMap.cs index 6243dc77f..07caedafa 100644 --- a/src/Discord.Net.Serialization/PropertyMap.cs +++ b/src/Discord.Net.Serialization/PropertyMap.cs @@ -51,7 +51,7 @@ namespace Discord.Serialization private TKey GetSelectorKey(object model) => (_getSelectorFunc as Func)((TModel)model); public object GetDynamicConverter(object model) - => _group.GetDynamicConverter(_getWrappedSelectorFunc, model); + => _group?.GetDynamicConverter(_getWrappedSelectorFunc, model); } private readonly Delegate _getSelectorFunc, _getWrappedSelectorFunc; @@ -73,7 +73,7 @@ namespace Discord.Serialization return new Selector(prop, selectorGroup, selectorFunc); }) - .ToList(); + .ToList(); } public override object GetDynamicConverter(object model, bool throwOnMissing) diff --git a/src/Discord.Net.Serialization/Serializer.cs b/src/Discord.Net.Serialization/Serializer.cs index c30caccf7..98e74164e 100644 --- a/src/Discord.Net.Serialization/Serializer.cs +++ b/src/Discord.Net.Serialization/Serializer.cs @@ -13,65 +13,40 @@ namespace Discord.Serialization private static readonly MethodInfo _createPropertyMapMethod = typeof(Serializer).GetTypeInfo().GetDeclaredMethod(nameof(CreatePropertyMap)); - + private readonly ConcurrentDictionary _maps; private readonly ConverterCollection _converters; - public bool IsScoped { get; } - protected Serializer() - { - _maps = new ConcurrentDictionary(); - _converters = new ConverterCollection(this); - IsScoped = false; - } + : this(null) { } protected Serializer(Serializer parent) { - _maps = parent._maps; - _converters = parent._converters; - IsScoped = true; + _maps = new ConcurrentDictionary(); + _converters = new ConverterCollection(parent?._converters); } - protected object GetConverter(Type valueType, PropertyInfo propInfo = null) - => _converters.Get(valueType, propInfo); + protected internal object GetConverter(Type valueType, PropertyInfo propInfo = null) + => _converters.Get(this, valueType, propInfo); public void AddConverter(Type valueType, Type converterType) - { - CheckScoped(); - _converters.Add(valueType, converterType); - } + => _converters.Add(valueType, converterType); public void AddConverter(Type valueType, Type converterType, Func condition) - { - CheckScoped(); - _converters.Add(valueType, converterType, condition); - } + => _converters.Add(valueType, converterType, condition); - public void AddGenericConverter(Type converterType) - { - CheckScoped(); - _converters.AddGeneric(converterType); - } - public void AddGenericConverter(Type converterType, Func condition) - { - CheckScoped(); - _converters.AddGeneric(converterType, condition); - } - public void AddGenericConverter(Type valueType, Type converterType) - { - CheckScoped(); - _converters.AddGeneric(valueType, converterType); - } - public void AddGenericConverter(Type valueType, Type converterType, Func condition) - { - CheckScoped(); - _converters.AddGeneric(valueType, converterType, condition); - } + public void AddGenericConverter(Type converterType, Func typeSelector = null) + => _converters.AddGeneric(converterType, typeSelector); + public void AddGenericConverter(Type converterType, Func condition, Func typeSelector = null) + => _converters.AddGeneric(converterType, condition, typeSelector); + public void AddGenericConverter(Type valueType, Type converterType, Func typeSelector = null) + => _converters.AddGeneric(valueType, converterType, typeSelector); + public void AddGenericConverter(Type valueType, Type converterType, Func condition, Func typeSelector = null) + => _converters.AddGeneric(valueType, converterType, condition, typeSelector); public void AddSelectorConverter(string groupKey, Type keyType, object keyValue, Type converterType) - { - CheckScoped(); - _converters.AddSelector(groupKey, keyType, keyValue, converterType); - } + => _converters.AddSelector(this, groupKey, keyType, keyValue, converterType); + public void AddSelectorConverter(string groupKey, TKey keyValue) + => _converters.AddSelector(this, groupKey, typeof(TKey), keyValue, typeof(TConverter)); + public ISelectorGroup GetSelectorGroup(Type keyType, string groupKey) => _converters.GetSelectorGroup(keyType, groupKey); @@ -104,11 +79,5 @@ namespace Discord.Serialization public abstract TModel Read(ReadOnlyBuffer data); public abstract void Write(ArrayFormatter stream, TModel model); - - private void CheckScoped() - { - if (IsScoped) - throw new InvalidOperationException("Scoped serializers are read-only"); - } } } diff --git a/src/Discord.Net.Serialization/_corefxlab/System.Text.Json/System/Text/Json/JsonWriter.cs b/src/Discord.Net.Serialization/_corefxlab/System.Text.Json/System/Text/Json/JsonWriter.cs index cb9d16f1d..f031eb374 100644 --- a/src/Discord.Net.Serialization/_corefxlab/System.Text.Json/System/Text/Json/JsonWriter.cs +++ b/src/Discord.Net.Serialization/_corefxlab/System.Text.Json/System/Text/Json/JsonWriter.cs @@ -314,7 +314,7 @@ namespace System.Text.Json } [MethodImpl(MethodImplOptions.AggressiveInlining)] - internal void WriteStartAttribute(string name) + private void WriteStartAttribute(string name) { WriteItemSeperator(); _firstItem = false; diff --git a/src/Discord.Net.WebSocket/API/Gateway/IdentifyParams.cs b/src/Discord.Net.WebSocket/API/Gateway/IdentifyParams.cs index 895618200..62e70333d 100644 --- a/src/Discord.Net.WebSocket/API/Gateway/IdentifyParams.cs +++ b/src/Discord.Net.WebSocket/API/Gateway/IdentifyParams.cs @@ -9,7 +9,7 @@ namespace Discord.API.Gateway [ModelProperty("token")] public string Token { get; set; } [ModelProperty("properties")] - public IDictionary Properties { get; set; } + public Dictionary Properties { get; set; } [ModelProperty("large_threshold")] public int LargeThreshold { get; set; } [ModelProperty("compress")] diff --git a/src/Discord.Net.WebSocket/API/Gateway/MessageDeleteBulkEvent.cs b/src/Discord.Net.WebSocket/API/Gateway/MessageDeleteBulkEvent.cs index 5c2cd56ec..c63a66e67 100644 --- a/src/Discord.Net.WebSocket/API/Gateway/MessageDeleteBulkEvent.cs +++ b/src/Discord.Net.WebSocket/API/Gateway/MessageDeleteBulkEvent.cs @@ -9,6 +9,6 @@ namespace Discord.API.Gateway [ModelProperty("channel_id")] public ulong ChannelId { get; set; } [ModelProperty("ids")] - public IEnumerable Ids { get; set; } + public ulong[] Ids { get; set; } } } diff --git a/src/Discord.Net.WebSocket/API/Gateway/RequestMembersParams.cs b/src/Discord.Net.WebSocket/API/Gateway/RequestMembersParams.cs index ac429a7f1..aff043587 100644 --- a/src/Discord.Net.WebSocket/API/Gateway/RequestMembersParams.cs +++ b/src/Discord.Net.WebSocket/API/Gateway/RequestMembersParams.cs @@ -12,6 +12,6 @@ namespace Discord.API.Gateway public int Limit { get; set; } [ModelProperty("guild_id")] - public IEnumerable GuildIds { get; set; } + public ulong[] GuildIds { get; set; } } } diff --git a/src/Discord.Net.WebSocket/Audio/AudioClient.cs b/src/Discord.Net.WebSocket/Audio/AudioClient.cs index f0086e4ff..b4eb82ca6 100644 --- a/src/Discord.Net.WebSocket/Audio/AudioClient.cs +++ b/src/Discord.Net.WebSocket/Audio/AudioClient.cs @@ -67,7 +67,7 @@ namespace Discord.Audio ChannelId = channelId; _audioLogger = Discord.LogManager.CreateLogger($"Audio #{clientId}"); - _serializer = DiscordJsonSerializer.Global.CreateScope(); + _serializer = DiscordVoiceJsonSerializer.Global.CreateScope(); _serializer.Error += ex => { _audioLogger.WarningAsync("Serializer Error", ex).GetAwaiter().GetResult(); @@ -128,7 +128,7 @@ namespace Discord.Audio await ApiClient.ConnectAsync("wss://" + _url).ConfigureAwait(false); await _audioLogger.DebugAsync("Listening on port " + ApiClient.UdpPort).ConfigureAwait(false); await _audioLogger.DebugAsync("Sending Identity").ConfigureAwait(false); - await ApiClient.SendIdentityAsync(_userId, _sessionId, _token).ConfigureAwait(false); + await ApiClient.SendIdentifyAsync(_userId, _sessionId, _token).ConfigureAwait(false); //Wait for READY await _connection.WaitAsync().ConfigureAwait(false); diff --git a/src/Discord.Net.WebSocket/DiscordShardedClient.cs b/src/Discord.Net.WebSocket/DiscordShardedClient.cs index f9fa52192..2cc77ed2e 100644 --- a/src/Discord.Net.WebSocket/DiscordShardedClient.cs +++ b/src/Discord.Net.WebSocket/DiscordShardedClient.cs @@ -55,7 +55,7 @@ namespace Discord.WebSocket _baseConfig = config; _connectionGroupLock = new SemaphoreSlim(1, 1); - _serializer = DiscordJsonSerializer.Global.CreateScope(); + _serializer = DiscordSocketJsonSerializer.Global.CreateScope(); _serializer.Error += ex => { _restLogger.WarningAsync("Serializer Error", ex).GetAwaiter().GetResult(); diff --git a/src/Discord.Net.WebSocket/DiscordSocketApiClient.cs b/src/Discord.Net.WebSocket/DiscordSocketApiClient.cs index 799fef729..473b520c6 100644 --- a/src/Discord.Net.WebSocket/DiscordSocketApiClient.cs +++ b/src/Discord.Net.WebSocket/DiscordSocketApiClient.cs @@ -10,6 +10,7 @@ using System; using System.Collections.Generic; using System.IO; using System.IO.Compression; +using System.Linq; using System.Text; using System.Text.Formatting; using System.Threading; @@ -183,8 +184,9 @@ namespace Discord.API } finally { + data.Clear(); _formatters.Enqueue(data); - } + } } //Gateway @@ -236,31 +238,37 @@ namespace Discord.API public async Task SendStatusUpdateAsync(UserStatus status, bool isAFK, long? since, Game game, RequestOptions options = null) { options = RequestOptions.CreateOrClone(options); - var args = new StatusUpdateParams + var msg = new StatusUpdateParams { Status = status, IdleSince = since, IsAFK = isAFK, Game = game }; - await SendGatewayAsync(GatewayOpCode.StatusUpdate, args, options: options).ConfigureAwait(false); + await SendGatewayAsync(GatewayOpCode.StatusUpdate, msg, options: options).ConfigureAwait(false); } public async Task SendRequestMembersAsync(IEnumerable guildIds, RequestOptions options = null) { options = RequestOptions.CreateOrClone(options); - await SendGatewayAsync(GatewayOpCode.RequestGuildMembers, new RequestMembersParams { GuildIds = guildIds, Query = "", Limit = 0 }, options: options).ConfigureAwait(false); + var msg = new RequestMembersParams + { + GuildIds = guildIds.ToArray(), + Query = "", + Limit = 0 + }; + await SendGatewayAsync(GatewayOpCode.RequestGuildMembers, msg, options: options).ConfigureAwait(false); } public async Task SendVoiceStateUpdateAsync(ulong guildId, ulong? channelId, bool selfDeaf, bool selfMute, RequestOptions options = null) { options = RequestOptions.CreateOrClone(options); - var payload = new VoiceStateUpdateParams + var msg = new VoiceStateUpdateParams { GuildId = guildId, ChannelId = channelId, SelfDeaf = selfDeaf, SelfMute = selfMute }; - await SendGatewayAsync(GatewayOpCode.VoiceStateUpdate, payload, options: options).ConfigureAwait(false); + await SendGatewayAsync(GatewayOpCode.VoiceStateUpdate, msg, options: options).ConfigureAwait(false); } public async Task SendGuildSyncAsync(IEnumerable guildIds, RequestOptions options = null) { diff --git a/src/Discord.Net.WebSocket/DiscordSocketClient.cs b/src/Discord.Net.WebSocket/DiscordSocketClient.cs index 1f36a4c49..f45a1936a 100644 --- a/src/Discord.Net.WebSocket/DiscordSocketClient.cs +++ b/src/Discord.Net.WebSocket/DiscordSocketClient.cs @@ -12,7 +12,6 @@ using System.Collections.Generic; using System.Collections.Immutable; using System.IO; using System.Linq; -using System.Runtime.CompilerServices; using System.Threading; using System.Threading.Tasks; using GameModel = Discord.API.Game; @@ -88,7 +87,7 @@ namespace Discord.WebSocket _stateLock = new SemaphoreSlim(1, 1); _gatewayLogger = LogManager.CreateLogger(ShardId == 0 && TotalShards == 1 ? "Gateway" : $"Shard #{ShardId}"); - _serializer = DiscordJsonSerializer.Global.CreateScope(); + _serializer = DiscordSocketJsonSerializer.Global.CreateScope(); _serializer.Error += ex => { _restLogger.WarningAsync("Serializer Error", ex).GetAwaiter().GetResult(); @@ -593,7 +592,7 @@ namespace Discord.WebSocket { await _gatewayLogger.DebugAsync("Received Dispatch (GUILD_EMOJIS_UPDATE)").ConfigureAwait(false); - var data = payload as API.Gateway.GuildEmojiUpdateEvent; + var data = payload as GuildEmojiUpdateEvent; var guild = State.GetGuild(data.GuildId); if (guild != null) { @@ -1201,6 +1200,35 @@ namespace Discord.WebSocket } } break; + case "MESSAGE_DELETE_BULK": + { + await _gatewayLogger.DebugAsync("Received Dispatch (MESSAGE_DELETE_BULK)").ConfigureAwait(false); + + var data = payload as MessageDeleteBulkEvent; + if (State.GetChannel(data.ChannelId) is ISocketMessageChannel channel) + { + var guild = (channel as SocketGuildChannel)?.Guild; + if (!(guild?.IsSynced ?? true)) + { + await UnsyncedGuildAsync(type, guild.Id).ConfigureAwait(false); + return; + } + + foreach (ulong id in data.Ids) + { + var msg = SocketChannelHelper.RemoveMessage(channel, this, id); + bool isCached = msg != null; + var cacheable = new Cacheable(msg, id, isCached, async () => await channel.GetMessageAsync(id)); + await TimedInvokeAsync(_messageDeletedEvent, nameof(MessageDeleted), cacheable, channel).ConfigureAwait(false); + } + } + else + { + await UnknownChannelAsync(type, data.ChannelId).ConfigureAwait(false); + return; + } + } + break; case "MESSAGE_REACTION_ADD": { await _gatewayLogger.DebugAsync("Received Dispatch (MESSAGE_REACTION_ADD)").ConfigureAwait(false); @@ -1271,35 +1299,6 @@ namespace Discord.WebSocket } } break; - case "MESSAGE_DELETE_BULK": - { - await _gatewayLogger.DebugAsync("Received Dispatch (MESSAGE_DELETE_BULK)").ConfigureAwait(false); - - var data = payload as MessageDeleteBulkEvent; - if (State.GetChannel(data.ChannelId) is ISocketMessageChannel channel) - { - var guild = (channel as SocketGuildChannel)?.Guild; - if (!(guild?.IsSynced ?? true)) - { - await UnsyncedGuildAsync(type, guild.Id).ConfigureAwait(false); - return; - } - - foreach (ulong id in data.Ids) - { - var msg = SocketChannelHelper.RemoveMessage(channel, this, id); - bool isCached = msg != null; - var cacheable = new Cacheable(msg, id, isCached, async () => await channel.GetMessageAsync(id)); - await TimedInvokeAsync(_messageDeletedEvent, nameof(MessageDeleted), cacheable, channel).ConfigureAwait(false); - } - } - else - { - await UnknownChannelAsync(type, data.ChannelId).ConfigureAwait(false); - return; - } - } - break; //Statuses case "PRESENCE_UPDATE": diff --git a/src/Discord.Net.WebSocket/DiscordVoiceApiClient.cs b/src/Discord.Net.WebSocket/DiscordVoiceApiClient.cs index 842f3b620..0c3fdc188 100644 --- a/src/Discord.Net.WebSocket/DiscordVoiceApiClient.cs +++ b/src/Discord.Net.WebSocket/DiscordVoiceApiClient.cs @@ -124,6 +124,7 @@ namespace Discord.Audio } finally { + data.Clear(); _formatters.Enqueue(data); } } @@ -138,7 +139,7 @@ namespace Discord.Audio { await SendAsync(VoiceOpCode.Heartbeat, DateTimeUtils.ToUnixMilliseconds(DateTimeOffset.UtcNow), options: options).ConfigureAwait(false); } - public async Task SendIdentityAsync(ulong userId, string sessionId, string token) + public async Task SendIdentifyAsync(ulong userId, string sessionId, string token) { await SendAsync(VoiceOpCode.Identify, new IdentifyParams { diff --git a/src/Discord.Net.WebSocket/Serialization/Json/DiscordSocketJsonSerializer.cs b/src/Discord.Net.WebSocket/Serialization/Json/DiscordSocketJsonSerializer.cs new file mode 100644 index 000000000..64eeda327 --- /dev/null +++ b/src/Discord.Net.WebSocket/Serialization/Json/DiscordSocketJsonSerializer.cs @@ -0,0 +1,119 @@ +using Discord.API.Gateway; +using Discord.Serialization.Json.Converters; +using System; + +namespace Discord.Serialization.Json +{ + public class DiscordSocketJsonSerializer : JsonSerializer + { + private static readonly Lazy _singleton = new Lazy(); + public static DiscordSocketJsonSerializer Global => _singleton.Value; + + public DiscordSocketJsonSerializer() + : this((JsonSerializer)null) { } + public DiscordSocketJsonSerializer(JsonSerializer parent) + : base(parent ?? DiscordRestJsonSerializer.Global) + { + AddSelectorConverter>( + ModelSelectorGroups.GatewayFrame, GatewayOpCode.Hello); + AddSelectorConverter( + ModelSelectorGroups.GatewayFrame, GatewayOpCode.InvalidSession); + + AddSelectorConverter( + ModelSelectorGroups.GatewayFrame, GatewayOpCode.Heartbeat); + AddSelectorConverter( + ModelSelectorGroups.GatewayFrame, GatewayOpCode.HeartbeatAck); + AddSelectorConverter>( + ModelSelectorGroups.GatewayFrame, GatewayOpCode.Identify); + AddSelectorConverter>( + ModelSelectorGroups.GatewayFrame, GatewayOpCode.Resume); + AddSelectorConverter>( + ModelSelectorGroups.GatewayFrame, GatewayOpCode.StatusUpdate); + AddSelectorConverter>( + ModelSelectorGroups.GatewayFrame, GatewayOpCode.RequestGuildMembers); + AddSelectorConverter>( + ModelSelectorGroups.GatewayFrame, GatewayOpCode.VoiceStateUpdate); + + AddSelectorConverter>( + ModelSelectorGroups.GatewayDispatchFrame, "READY"); + + AddSelectorConverter>( + ModelSelectorGroups.GatewayDispatchFrame, "GUILD_CREATE"); + AddSelectorConverter>( + ModelSelectorGroups.GatewayDispatchFrame, "GUILD_UPDATE"); + AddSelectorConverter>( + ModelSelectorGroups.GatewayDispatchFrame, "GUILD_EMOJIS_UPDATE"); + AddSelectorConverter>( + ModelSelectorGroups.GatewayDispatchFrame, "GUILD_SYNC"); + AddSelectorConverter>( + ModelSelectorGroups.GatewayDispatchFrame, "GUILD_DELETE"); + + AddSelectorConverter>( + ModelSelectorGroups.GatewayDispatchFrame, "CHANNEL_CREATE"); + AddSelectorConverter>( + ModelSelectorGroups.GatewayDispatchFrame, "CHANNEL_UPDATE"); + AddSelectorConverter>( + ModelSelectorGroups.GatewayDispatchFrame, "CHANNEL_DELETE"); + + AddSelectorConverter>( + ModelSelectorGroups.GatewayDispatchFrame, "GUILD_MEMBER_ADD"); + AddSelectorConverter>( + ModelSelectorGroups.GatewayDispatchFrame, "GUILD_MEMBER_UPDATE"); + AddSelectorConverter>( + ModelSelectorGroups.GatewayDispatchFrame, "GUILD_MEMBER_REMOVE"); + AddSelectorConverter>( + ModelSelectorGroups.GatewayDispatchFrame, "GUILD_MEMBERS_CHUNK"); + + AddSelectorConverter>( + ModelSelectorGroups.GatewayDispatchFrame, "CHANNEL_RECIPIENT_ADD"); + AddSelectorConverter>( + ModelSelectorGroups.GatewayDispatchFrame, "CHANNEL_RECIPIENT_REMOVE"); + + AddSelectorConverter>( + ModelSelectorGroups.GatewayDispatchFrame, "GUILD_ROLE_CREATE"); + AddSelectorConverter>( + ModelSelectorGroups.GatewayDispatchFrame, "GUILD_ROLE_UPDATE"); + AddSelectorConverter>( + ModelSelectorGroups.GatewayDispatchFrame, "GUILD_ROLE_UPDATE"); + + AddSelectorConverter>( + ModelSelectorGroups.GatewayDispatchFrame, "GUILD_BAN_ADD"); + AddSelectorConverter>( + ModelSelectorGroups.GatewayDispatchFrame, "GUILD_BAN_REMOVE"); + + AddSelectorConverter>( + ModelSelectorGroups.GatewayDispatchFrame, "MESSAGE_CREATE"); + AddSelectorConverter>( + ModelSelectorGroups.GatewayDispatchFrame, "MESSAGE_UPDATE"); + AddSelectorConverter>( + ModelSelectorGroups.GatewayDispatchFrame, "MESSAGE_DELETE"); + AddSelectorConverter>( + ModelSelectorGroups.GatewayDispatchFrame, "MESSAGE_DELETE_BULK"); + + AddSelectorConverter>( + ModelSelectorGroups.GatewayDispatchFrame, "MESSAGE_REACTION_ADD"); + AddSelectorConverter>( + ModelSelectorGroups.GatewayDispatchFrame, "MESSAGE_REACTION_REMOVE"); + AddSelectorConverter>( + ModelSelectorGroups.GatewayDispatchFrame, "MESSAGE_REACTION_REMOVE_ALL"); + + AddSelectorConverter>( + ModelSelectorGroups.GatewayDispatchFrame, "PRESENCE_UPDATE"); + + AddSelectorConverter>( + ModelSelectorGroups.GatewayDispatchFrame, "USER_UPDATE"); + + AddSelectorConverter>( + ModelSelectorGroups.GatewayDispatchFrame, "TYPING_START"); + + AddSelectorConverter>( + ModelSelectorGroups.GatewayDispatchFrame, "VOICE_STATE_UPDATE"); + AddSelectorConverter>( + ModelSelectorGroups.GatewayDispatchFrame, "VOICE_SERVER_UPDATE"); + } + + private DiscordSocketJsonSerializer(DiscordSocketJsonSerializer parent) + : base(parent) { } + public DiscordSocketJsonSerializer CreateScope() => new DiscordSocketJsonSerializer(this); + } +} diff --git a/src/Discord.Net.WebSocket/Serialization/Json/DiscordVoiceJsonSerializer.cs b/src/Discord.Net.WebSocket/Serialization/Json/DiscordVoiceJsonSerializer.cs new file mode 100644 index 000000000..ff1daa761 --- /dev/null +++ b/src/Discord.Net.WebSocket/Serialization/Json/DiscordVoiceJsonSerializer.cs @@ -0,0 +1,21 @@ +using System; + +namespace Discord.Serialization.Json +{ + public class DiscordVoiceJsonSerializer : JsonSerializer + { + private static readonly Lazy _singleton = new Lazy(); + public static DiscordVoiceJsonSerializer Global => _singleton.Value; + + public DiscordVoiceJsonSerializer() + : this((JsonSerializer)null) { } + public DiscordVoiceJsonSerializer(JsonSerializer parent) + : base(parent ?? DiscordRestJsonSerializer.Global) + { + } + + private DiscordVoiceJsonSerializer(DiscordVoiceJsonSerializer parent) + : base(parent) { } + public DiscordVoiceJsonSerializer CreateScope() => new DiscordVoiceJsonSerializer(this); + } +} diff --git a/src/Discord.Net.Webhook/DiscordWebhookClient.cs b/src/Discord.Net.Webhook/DiscordWebhookClient.cs index db646dba1..fb93d4917 100644 --- a/src/Discord.Net.Webhook/DiscordWebhookClient.cs +++ b/src/Discord.Net.Webhook/DiscordWebhookClient.cs @@ -30,7 +30,7 @@ namespace Discord.Webhook { _webhookId = webhookId; - _serializer = DiscordJsonSerializer.Global.CreateScope(); + _serializer = DiscordRestJsonSerializer.Global.CreateScope(); _serializer.Error += ex => { _restLogger.WarningAsync("Serializer Error", ex).GetAwaiter().GetResult();