diff --git a/src/Discord.Net.Core/Entities/Guilds/PermissionTarget.cs b/src/Discord.Net.Core/Entities/Guilds/PermissionTarget.cs index f6479a0ae..f0faaefb8 100644 --- a/src/Discord.Net.Core/Entities/Guilds/PermissionTarget.cs +++ b/src/Discord.Net.Core/Entities/Guilds/PermissionTarget.cs @@ -4,9 +4,9 @@ namespace Discord { public enum PermissionTarget { - [ModelEnum("role")] + [ModelEnumValue("role")] Role, - [ModelEnum("user")] + [ModelEnumValue("user")] User } } diff --git a/src/Discord.Net.Core/Entities/Permissions/Overwrite.cs b/src/Discord.Net.Core/Entities/Permissions/Overwrite.cs index bda67a870..3531d6147 100644 --- a/src/Discord.Net.Core/Entities/Permissions/Overwrite.cs +++ b/src/Discord.Net.Core/Entities/Permissions/Overwrite.cs @@ -1,10 +1,13 @@ -namespace Discord +using Discord.Serialization; + +namespace Discord { public struct Overwrite { /// Gets the unique identifier for the object this overwrite is targeting. public ulong TargetId { get; } /// Gets the type of object this overwrite is targeting. + [ModelStringEnum] public PermissionTarget TargetType { get; } /// Gets the permissions associated with this overwrite entry. public OverwritePermissions Permissions { get; } diff --git a/src/Discord.Net.Core/Entities/Users/UserStatus.cs b/src/Discord.Net.Core/Entities/Users/UserStatus.cs index 7d02fe68c..5f9ce9df5 100644 --- a/src/Discord.Net.Core/Entities/Users/UserStatus.cs +++ b/src/Discord.Net.Core/Entities/Users/UserStatus.cs @@ -5,13 +5,13 @@ namespace Discord public enum UserStatus { Offline, - [ModelEnum("online")] + [ModelEnumValue("online")] Online, - [ModelEnum("idle")] + [ModelEnumValue("idle")] Idle, - [ModelEnum("idle", EnumValueType.WriteOnly)] + [ModelEnumValue("idle", EnumValueType.WriteOnly)] AFK, - [ModelEnum("dnd")] + [ModelEnumValue("dnd")] DoNotDisturb, Invisible, } diff --git a/src/Discord.Net.Rest/API/Common/Presence.cs b/src/Discord.Net.Rest/API/Common/Presence.cs index 68dae302a..c6c7d3436 100644 --- a/src/Discord.Net.Rest/API/Common/Presence.cs +++ b/src/Discord.Net.Rest/API/Common/Presence.cs @@ -10,6 +10,7 @@ namespace Discord.API [ModelProperty("guild_id")] public Optional GuildId { get; set; } [ModelProperty("status")] + [ModelStringEnum] public UserStatus Status { get; set; } [ModelProperty("game")] public Game Game { get; set; } diff --git a/src/Discord.Net.Rest/DiscordRestApiClient.cs b/src/Discord.Net.Rest/DiscordRestApiClient.cs index 5574afaa8..c708df258 100644 --- a/src/Discord.Net.Rest/DiscordRestApiClient.cs +++ b/src/Discord.Net.Rest/DiscordRestApiClient.cs @@ -191,7 +191,7 @@ namespace Discord.API options.BucketId = AuthTokenType == TokenType.User ? ClientBucket.Get(clientBucket).Id : bucketId; options.IsClientBucket = AuthTokenType == TokenType.User; - if (_formatters.TryDequeue(out var data)) + if (!_formatters.TryDequeue(out var data)) data = new ArrayFormatter(128, SymbolTable.InvariantUtf8); try { @@ -240,7 +240,7 @@ namespace Discord.API options.BucketId = AuthTokenType == TokenType.User ? ClientBucket.Get(clientBucket).Id : bucketId; options.IsClientBucket = AuthTokenType == TokenType.User; - if (_formatters.TryDequeue(out var data)) + if (!_formatters.TryDequeue(out var data)) data = new ArrayFormatter(128, SymbolTable.InvariantUtf8); try { @@ -1168,13 +1168,12 @@ namespace Discord.API throw new InvalidOperationException("Client is not logged in."); } protected static double ToMilliseconds(Stopwatch stopwatch) => Math.Round((double)stopwatch.ElapsedTicks / (double)Stopwatch.Frequency * 1000.0, 2); - protected ReadOnlyBuffer SerializeJson(ArrayFormatter data, object value) + protected ReadOnlyBuffer SerializeJson(ArrayFormatter data, T value) { _serializer.Write(data, value); return new ReadOnlyBuffer(data.Formatted.Array, 0, data.Formatted.Count); } protected T DeserializeJson(ReadOnlyBuffer data) - where T : class, new() { return _serializer.Read(data); } diff --git a/src/Discord.Net.Rest/Serialization/Json/Converters/EntityOrIdPropertyConverter.cs b/src/Discord.Net.Rest/Serialization/Json/Converters/EntityOrIdPropertyConverter.cs index c25302c18..640de4016 100644 --- a/src/Discord.Net.Rest/Serialization/Json/Converters/EntityOrIdPropertyConverter.cs +++ b/src/Discord.Net.Rest/Serialization/Json/Converters/EntityOrIdPropertyConverter.cs @@ -12,19 +12,19 @@ namespace Discord.Serialization.Json.Converters _innerConverter = innerConverter; } - public EntityOrId Read(PropertyMap map, ref JsonReader reader, bool isTopLevel) + public EntityOrId Read(PropertyMap map, object model, ref JsonReader reader, bool isTopLevel) { if (isTopLevel) reader.Read(); if (reader.ValueType == JsonValueType.Number) return new EntityOrId(reader.ParseUInt64()); - return new EntityOrId(_innerConverter.Read(map, ref reader, false)); + return new EntityOrId(_innerConverter.Read(map, model, ref reader, false)); } - public void Write(PropertyMap map, ref JsonWriter writer, EntityOrId value, bool isTopLevel) + public void Write(PropertyMap map, object model, ref JsonWriter writer, EntityOrId value, bool isTopLevel) { if (value.Object != null) - _innerConverter.Write(map, ref writer, value.Object, isTopLevel); + _innerConverter.Write(map, model, ref writer, value.Object, isTopLevel); else { if (isTopLevel) diff --git a/src/Discord.Net.Rest/Serialization/Json/Converters/ImagePropertyConverter.cs b/src/Discord.Net.Rest/Serialization/Json/Converters/ImagePropertyConverter.cs index 6d28136aa..a5687b16d 100644 --- a/src/Discord.Net.Rest/Serialization/Json/Converters/ImagePropertyConverter.cs +++ b/src/Discord.Net.Rest/Serialization/Json/Converters/ImagePropertyConverter.cs @@ -5,7 +5,7 @@ namespace Discord.Serialization.Json.Converters { internal class ImagePropertyConverter : IJsonPropertyConverter { - public API.Image Read(PropertyMap map, ref JsonReader reader, bool isTopLevel) + public 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, ref JsonWriter writer, API.Image value, bool isTopLevel) + public void Write(PropertyMap map, object model, ref JsonWriter writer, API.Image value, bool isTopLevel) { string str; if (value.Stream != null) diff --git a/src/Discord.Net.Rest/Serialization/Json/Converters/Int53PropertyConverter.cs b/src/Discord.Net.Rest/Serialization/Json/Converters/Int53PropertyConverter.cs index d59fd1610..3b9fe89a6 100644 --- a/src/Discord.Net.Rest/Serialization/Json/Converters/Int53PropertyConverter.cs +++ b/src/Discord.Net.Rest/Serialization/Json/Converters/Int53PropertyConverter.cs @@ -4,7 +4,7 @@ namespace Discord.Serialization.Json.Converters { internal class Int53PropertyConverter : IJsonPropertyConverter { - public long Read(PropertyMap map, ref JsonReader reader, bool isTopLevel) + public long Read(PropertyMap map, object model, ref JsonReader reader, bool isTopLevel) { if (isTopLevel) reader.Read(); @@ -12,7 +12,7 @@ namespace Discord.Serialization.Json.Converters throw new SerializationException("Bad input, expected Number"); return reader.ParseInt64(); } - public void Write(PropertyMap map, ref JsonWriter writer, long value, bool isTopLevel) + public void Write(PropertyMap map, object model, ref JsonWriter writer, long value, bool isTopLevel) { if (isTopLevel) writer.WriteAttribute(map.Key, value); diff --git a/src/Discord.Net.Rest/Serialization/Json/Converters/OptionalPropertyConverter.cs b/src/Discord.Net.Rest/Serialization/Json/Converters/OptionalPropertyConverter.cs index 06609fbbd..4a5218a34 100644 --- a/src/Discord.Net.Rest/Serialization/Json/Converters/OptionalPropertyConverter.cs +++ b/src/Discord.Net.Rest/Serialization/Json/Converters/OptionalPropertyConverter.cs @@ -11,13 +11,13 @@ namespace Discord.Serialization.Json.Converters _innerConverter = innerConverter; } - public Optional Read(PropertyMap map, ref JsonReader reader, bool isTopLevel) - => new Optional(_innerConverter.Read(map, ref reader, isTopLevel)); + public 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, ref JsonWriter writer, Optional value, bool isTopLevel) + public void Write(PropertyMap map, object model, ref JsonWriter writer, Optional value, bool isTopLevel) { if (value.IsSpecified) - _innerConverter.Write(map, ref writer, value.Value, isTopLevel); + _innerConverter.Write(map, model, ref writer, value.Value, isTopLevel); } } } diff --git a/src/Discord.Net.Rest/Serialization/Json/Converters/UInt53PropertyConverter.cs b/src/Discord.Net.Rest/Serialization/Json/Converters/UInt53PropertyConverter.cs index fa35ff711..9664fc3a2 100644 --- a/src/Discord.Net.Rest/Serialization/Json/Converters/UInt53PropertyConverter.cs +++ b/src/Discord.Net.Rest/Serialization/Json/Converters/UInt53PropertyConverter.cs @@ -4,7 +4,7 @@ namespace Discord.Serialization.Json.Converters { internal class UInt53PropertyConverter : IJsonPropertyConverter { - public ulong Read(PropertyMap map, ref JsonReader reader, bool isTopLevel) + public ulong Read(PropertyMap map, object model, ref JsonReader reader, bool isTopLevel) { if (isTopLevel) reader.Read(); @@ -12,7 +12,7 @@ namespace Discord.Serialization.Json.Converters throw new SerializationException("Bad input, expected Number"); return reader.ParseUInt64(); } - public void Write(PropertyMap map, ref JsonWriter writer, ulong value, bool isTopLevel) + public void Write(PropertyMap map, object model, ref JsonWriter writer, ulong value, bool isTopLevel) { if (isTopLevel) writer.WriteAttribute(map.Key, value); diff --git a/src/Discord.Net.Rest/Serialization/ModelSelectorGroups.cs b/src/Discord.Net.Rest/Serialization/ModelSelectorGroups.cs new file mode 100644 index 000000000..f35d99c0d --- /dev/null +++ b/src/Discord.Net.Rest/Serialization/ModelSelectorGroups.cs @@ -0,0 +1,11 @@ +namespace Discord.Serialization +{ + public static class ModelSelectorGroups + { + public const string GatewayFrame = nameof(GatewayFrame); + public const string GatewayDispatchFrame = nameof(GatewayDispatchFrame); + public const string VoiceFrame = nameof(VoiceFrame); + public const string VoiceDispatchFrame = nameof(VoiceDispatchFrame); + public const string RpcFrame = nameof(RpcFrame); + } +} diff --git a/src/Discord.Net.Rpc/API/RpcFrame.cs b/src/Discord.Net.Rpc/API/RpcFrame.cs index 0bdd9f809..621ced976 100644 --- a/src/Discord.Net.Rpc/API/RpcFrame.cs +++ b/src/Discord.Net.Rpc/API/RpcFrame.cs @@ -14,7 +14,9 @@ namespace Discord.API.Rpc public Optional Event { get; set; } [ModelProperty("data")] public Optional> Data { get; set; } + [ModelProperty("args")] + [ModelSelector(ModelSelectorGroups.RpcFrame, nameof(Event))] public object Args { get; set; } } } diff --git a/src/Discord.Net.Rpc/DiscordRpcApiClient.cs b/src/Discord.Net.Rpc/DiscordRpcApiClient.cs index 34eb1efd3..5ff89f71e 100644 --- a/src/Discord.Net.Rpc/DiscordRpcApiClient.cs +++ b/src/Discord.Net.Rpc/DiscordRpcApiClient.cs @@ -5,6 +5,7 @@ using Discord.Net.Rest; using Discord.Net.WebSockets; using Discord.Rpc; using Discord.Serialization; +using Discord.Serialization.Json; using System; using System.Collections.Concurrent; using System.Collections.Generic; @@ -41,11 +42,11 @@ namespace Discord.API } public Task SetResultAsync(ReadOnlyBuffer data) { - return Promise.TrySetResultAsync(Serializer.Json.Read(data)); + return Promise.TrySetResultAsync(DiscordJsonSerializer.Global.Read(data)); } public Task SetExceptionAsync(ReadOnlyBuffer data) { - var error = Serializer.Json.Read(data); + var error = DiscordJsonSerializer.Global.Read(data); return Promise.TrySetExceptionAsync(new RpcException(error.Code, error.Message)); } } @@ -231,12 +232,12 @@ namespace Discord.API try { var guid = Guid.NewGuid(); - payload = 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 = SerializeJson(data1, payload), Nonce = guid }; var requestTracker = new RpcRequest(options); _requests[guid] = requestTracker; - await RequestQueue.SendAsync(new WebSocketRequest(_webSocketClient, null, SerializeJson(data2, payload), true, options)).ConfigureAwait(false); + await RequestQueue.SendAsync(new WebSocketRequest(_webSocketClient, null, SerializeJson(data2, frame), true, options)).ConfigureAwait(false); await _sentRpcMessageEvent.InvokeAsync(cmd).ConfigureAwait(false); return await requestTracker.Promise.Task.ConfigureAwait(false); } diff --git a/src/Discord.Net.Rpc/DiscordRpcClient.cs b/src/Discord.Net.Rpc/DiscordRpcClient.cs index 2615972f9..b208e4f40 100644 --- a/src/Discord.Net.Rpc/DiscordRpcClient.cs +++ b/src/Discord.Net.Rpc/DiscordRpcClient.cs @@ -8,6 +8,7 @@ using System.Linq; using System.Threading.Tasks; using System.Threading; using Discord.Serialization; +using Discord.Serialization.Json; namespace Discord.Rpc { @@ -37,7 +38,7 @@ namespace Discord.Rpc _authorizeLock = new SemaphoreSlim(1, 1); _rpcLogger = LogManager.CreateLogger("RPC"); - _serializer = new Serializer(SerializationFormat.Json); + _serializer = DiscordJsonSerializer.Global.CreateScope(); _serializer.Error += ex => { _rpcLogger.WarningAsync("Serializer Error", ex).GetAwaiter().GetResult(); diff --git a/src/Discord.Net.Serialization/EnumValueAttribute.cs b/src/Discord.Net.Serialization/Attributes/ModelEnumValueAttribute.cs similarity index 71% rename from src/Discord.Net.Serialization/EnumValueAttribute.cs rename to src/Discord.Net.Serialization/Attributes/ModelEnumValueAttribute.cs index 8b5e235f0..3cdcd3601 100644 --- a/src/Discord.Net.Serialization/EnumValueAttribute.cs +++ b/src/Discord.Net.Serialization/Attributes/ModelEnumValueAttribute.cs @@ -10,12 +10,12 @@ namespace Discord.Serialization } [AttributeUsage(AttributeTargets.Field, AllowMultiple = false)] - public class ModelEnumAttribute : Attribute + public class ModelEnumValueAttribute : Attribute { public string Key { get; } public EnumValueType Type { get; } - public ModelEnumAttribute(string key, EnumValueType type = EnumValueType.ReadWrite) + public ModelEnumValueAttribute(string key, EnumValueType type = EnumValueType.ReadWrite) { Key = key; Type = type; diff --git a/src/Discord.Net.Serialization/ModelPropertyAttribute.cs b/src/Discord.Net.Serialization/Attributes/ModelPropertyAttribute.cs similarity index 72% rename from src/Discord.Net.Serialization/ModelPropertyAttribute.cs rename to src/Discord.Net.Serialization/Attributes/ModelPropertyAttribute.cs index 338c7a857..6a33371e1 100644 --- a/src/Discord.Net.Serialization/ModelPropertyAttribute.cs +++ b/src/Discord.Net.Serialization/Attributes/ModelPropertyAttribute.cs @@ -8,7 +8,9 @@ namespace Discord.Serialization public string Key { get; } public bool ExcludeNull { get; set; } - public ModelPropertyAttribute(string key = null) + public ModelPropertyAttribute() + : this(null) { } + public ModelPropertyAttribute(string key) { Key = key; } diff --git a/src/Discord.Net.Serialization/Attributes/ModelSelectorAttribute.cs b/src/Discord.Net.Serialization/Attributes/ModelSelectorAttribute.cs new file mode 100644 index 000000000..895093f1c --- /dev/null +++ b/src/Discord.Net.Serialization/Attributes/ModelSelectorAttribute.cs @@ -0,0 +1,17 @@ +using System; + +namespace Discord.Serialization +{ + [AttributeUsage(AttributeTargets.Property, AllowMultiple = true)] + public class ModelSelectorAttribute : Attribute + { + public string SelectorProperty { get; } + public string SelectorGroup { get; } + + public ModelSelectorAttribute(string selectorProperty, string selectorGroup) + { + SelectorProperty = selectorProperty; + SelectorGroup = selectorGroup; + } + } +} diff --git a/src/Discord.Net.Serialization/Attributes/ModelStringEnumAttribute.cs b/src/Discord.Net.Serialization/Attributes/ModelStringEnumAttribute.cs new file mode 100644 index 000000000..9c88a23ae --- /dev/null +++ b/src/Discord.Net.Serialization/Attributes/ModelStringEnumAttribute.cs @@ -0,0 +1,9 @@ +using System; + +namespace Discord.Serialization +{ + [AttributeUsage(AttributeTargets.Property, AllowMultiple = false)] + public class ModelStringEnumAttribute : Attribute + { + } +} diff --git a/src/Discord.Net.Serialization/ConverterCollection.cs b/src/Discord.Net.Serialization/ConverterCollection.cs index e2d8f0eb7..c1a0efa81 100644 --- a/src/Discord.Net.Serialization/ConverterCollection.cs +++ b/src/Discord.Net.Serialization/ConverterCollection.cs @@ -17,14 +17,20 @@ namespace Discord.Serialization private static readonly TypeInfo _serializerType = typeof(Serializer).GetTypeInfo(); private readonly Serializer _serializer; - private readonly ConcurrentDictionary _cache = new ConcurrentDictionary(); - private readonly Dictionary _types = new Dictionary(); - private readonly Dictionary _mappedGenericTypes = new Dictionary(); - private readonly ConverterTypeCollection _genericTypes = new ConverterTypeCollection(); + private readonly ConcurrentDictionary _cache; + private readonly Dictionary _types; + private readonly Dictionary _mappedGenericTypes; + private readonly ConverterTypeCollection _genericTypes; + private readonly ConcurrentDictionary> _selectorGroups; internal ConverterCollection(Serializer serializer) { _serializer = serializer; + _cache = new ConcurrentDictionary(); + _types = new Dictionary(); + _mappedGenericTypes = new Dictionary(); + _genericTypes = new ConverterTypeCollection(); + _selectorGroups = new ConcurrentDictionary>(); } public void Add(Type type, Type converterType) @@ -66,15 +72,22 @@ namespace Discord.Serialization _mappedGenericTypes.Add(openType, converters = new ConverterTypeCollection()); converters.Conditionals.Add((condition, openConverterType)); } - + + public void AddSelector(string groupKey, Type keyType, object keyValue, Type converterType) + { + var group = GetSelectorGroup(keyType, groupKey); + group.AddDynamicConverter(keyValue, converterType); + } + public object Get(Type type, PropertyInfo propInfo = null) { - if (!_cache.TryGetValue(type, out var result)) + if (propInfo == null) //Can only cache top-level due to attribute influences { - object converter = Create(type, propInfo); - result = _cache.GetOrAdd(type, converter); + if (_cache.TryGetValue(type, out var result)) + return result; + return _cache.GetOrAdd(type, Create(type, propInfo)); } - return result; + return Create(type, propInfo); } private object Create(Type type, PropertyInfo propInfo) { @@ -108,7 +121,7 @@ namespace Discord.Serialization converterType = converterType.MakeGenericType(type); var converterTypeInfo = converterType.GetTypeInfo(); - var constructors = converterTypeInfo.DeclaredConstructors.ToArray(); + 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) @@ -148,5 +161,17 @@ namespace Discord.Serialization return converters.DefaultConverterType; return null; } + + public ISelectorGroup GetSelectorGroup(Type keyType, string groupKey) + { + var keyGroup = GetSelectorKeyGroup(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) + => _selectorGroups.GetOrAdd(keyType, _ => new ConcurrentDictionary()); } } diff --git a/src/Discord.Net.Serialization/EnumMap.cs b/src/Discord.Net.Serialization/EnumMap.cs index 80aaef942..6c1d1a806 100644 --- a/src/Discord.Net.Serialization/EnumMap.cs +++ b/src/Discord.Net.Serialization/EnumMap.cs @@ -16,9 +16,12 @@ namespace Discord.Serialization { public static readonly EnumMap Instance = new EnumMap(); - private readonly BufferDictionary _keyToValue; + private readonly Dictionary _keyToValue; + private readonly BufferDictionary _utf8KeyToValue; + private readonly Dictionary _intToValue; + private readonly Dictionary _valueToKey; - private readonly Dictionary> _valueToUtf8Key; + private readonly Dictionary _valueToInt; public EnumMap() { @@ -26,50 +29,104 @@ namespace Discord.Serialization if (!typeInfo.IsEnum) throw new InvalidOperationException($"{typeInfo.Name} is not an Enum"); - _keyToValue = new BufferDictionary(); + _keyToValue = new Dictionary(); + _utf8KeyToValue = new BufferDictionary(); + _intToValue = new Dictionary(); + _valueToKey = new Dictionary(); - _valueToUtf8Key = new Dictionary>(); + _valueToInt = new Dictionary(); foreach (T val in Enum.GetValues(typeof(T)).OfType()) { var fieldInfo = typeInfo.GetDeclaredField(Enum.GetName(typeof(T), val)); - var attr = fieldInfo.GetCustomAttribute(); + var attr = fieldInfo.GetCustomAttribute(); if (attr != null) { - var key = new ReadOnlyBuffer(new Utf8String(attr.Key).Bytes.ToArray()); + string key = attr.Key; + var keyBuffer = new ReadOnlyBuffer(new Utf8String(attr.Key).Bytes.ToArray()); if (attr.Type != EnumValueType.WriteOnly) - _keyToValue.Add(key, val); - if (attr.Type != EnumValueType.ReadOnly) { - _valueToUtf8Key.Add(val, key); - _valueToKey.Add(val, attr.Key); + _keyToValue.Add(key, val); + _utf8KeyToValue.Add(keyBuffer, val); } + if (attr.Type != EnumValueType.ReadOnly) + _valueToKey.Add(val, key); } + + var underlyingType = Enum.GetUnderlyingType(typeof(T)); + long baseVal; + if (underlyingType == typeof(sbyte)) + baseVal = (sbyte)(ValueType)val; + else if (underlyingType == typeof(short)) + baseVal = (short)(ValueType)val; + else if (underlyingType == typeof(int)) + baseVal = (int)(ValueType)val; + else if (underlyingType == typeof(long)) + baseVal = (long)(ValueType)val; + else if (underlyingType == typeof(byte)) + baseVal = (byte)(ValueType)val; + else if (underlyingType == typeof(ushort)) + baseVal = (ushort)(ValueType)val; + else if (underlyingType == typeof(uint)) + baseVal = (uint)(ValueType)val; + else if (underlyingType == typeof(ulong)) + baseVal = (long)(ulong)(ValueType)val; + else + throw new SerializationException($"Unsupported underlying enum type: {underlyingType.Name}"); + + _intToValue.Add(baseVal, val); + _valueToInt.Add(val, baseVal); } } - - public T GetValue(ReadOnlyBuffer key) + + public T FromKey(ReadOnlyBuffer key) { - if (_keyToValue.TryGetValue(key, out var value)) + if (_utf8KeyToValue.TryGetValue(key, out var value)) return value; throw new SerializationException($"Unknown enum key: {new Utf8String(key.Span).ToString()}"); } - public T GetValue(ReadOnlySpan key) + public T FromKey(ReadOnlySpan key) { - if (_keyToValue.TryGetValue(key, out var value)) + if (_utf8KeyToValue.TryGetValue(key, out var value)) return value; throw new SerializationException($"Unknown enum key: {new Utf8String(key).ToString()}"); } - public string GetKey(T value) + public string ToUtf16Key(T value) { if (_valueToKey.TryGetValue(value, out var key)) return key; throw new SerializationException($"Unknown enum value: {value}"); } - public ReadOnlyBuffer GetUtf8Key(T value) + + public T FromInt64(ReadOnlyBuffer intBuffer) + => FromInt64(intBuffer.Span); + public T FromInt64(ReadOnlySpan intBuffer) { - if (_valueToUtf8Key.TryGetValue(value, out var key)) - return key; + long intValue = intBuffer.ParseInt64(); + if (_intToValue.TryGetValue(intValue, out var value)) + return value; + throw new SerializationException($"Unknown enum value: {intValue}"); + } + public long ToInt64(T value) + { + if (_valueToInt.TryGetValue(value, out var intValue)) + return intValue; + throw new SerializationException($"Unknown enum value: {value}"); + } + + public T FromUInt64(ReadOnlyBuffer intBuffer) + => FromUInt64(intBuffer.Span); + public T FromUInt64(ReadOnlySpan intBuffer) + { + ulong intValue = intBuffer.ParseUInt64(); + if (_intToValue.TryGetValue((long)intValue, out var value)) + return value; + throw new SerializationException($"Unknown enum value: {intValue}"); + } + public ulong ToUInt64(T value) + { + if (_valueToInt.TryGetValue(value, out var intValue)) + return (ulong)intValue; throw new SerializationException($"Unknown enum value: {value}"); } } diff --git a/src/Discord.Net.Serialization/Extensions/JsonReaderExtensions.cs b/src/Discord.Net.Serialization/Extensions/JsonReaderExtensions.cs index 84b094724..9993c2e1b 100644 --- a/src/Discord.Net.Serialization/Extensions/JsonReaderExtensions.cs +++ b/src/Discord.Net.Serialization/Extensions/JsonReaderExtensions.cs @@ -29,7 +29,7 @@ namespace Discord.Serialization public static Guid ParseGuid(this JsonReader reader) => reader.Value.ParseGuid(); } - public static class JsonUtils + public static class JsonReaderUtils { public static void Skip(ref JsonReader reader) { diff --git a/src/Discord.Net.Serialization/Json/Converters/Collections.cs b/src/Discord.Net.Serialization/Json/Converters/Collections.cs index b0096b860..9dcf3b373 100644 --- a/src/Discord.Net.Serialization/Json/Converters/Collections.cs +++ b/src/Discord.Net.Serialization/Json/Converters/Collections.cs @@ -3,7 +3,7 @@ using System.Text.Json; namespace Discord.Serialization.Json.Converters { - internal class ListPropertyConverter : IJsonPropertyConverter> + public class ListPropertyConverter : IJsonPropertyConverter> { private readonly IJsonPropertyConverter _innerConverter; @@ -12,25 +12,25 @@ namespace Discord.Serialization.Json.Converters _innerConverter = innerConverter; } - public List Read(PropertyMap map, ref JsonReader reader, bool isTopLevel) + 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, ref reader, false)); + list.Add(_innerConverter.Read(map, model, ref reader, false)); return list; } - public void Write(PropertyMap map, ref JsonWriter writer, List value, bool isTopLevel) + 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, ref writer, value[i], false); + _innerConverter.Write(map, model, ref writer, value[i], false); writer.WriteArrayEnd(); } } diff --git a/src/Discord.Net.Serialization/Json/Converters/Dynamic.cs b/src/Discord.Net.Serialization/Json/Converters/Dynamic.cs new file mode 100644 index 000000000..bba1bf0c2 --- /dev/null +++ b/src/Discord.Net.Serialization/Json/Converters/Dynamic.cs @@ -0,0 +1,35 @@ +using System.Text.Json; + +namespace Discord.Serialization.Json.Converters +{ + //TODO: Only supports cases where the key arrives first + public class DynamicPropertyConverter : IJsonPropertyConverter + { + public 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); + else + { + JsonReaderUtils.Skip(ref reader); + return null; + } + } + + public void Write(PropertyMap map, object model, ref JsonWriter writer, object value, bool isTopLevel) + { + if (value == null) + { + if (isTopLevel) + writer.WriteAttributeNull(map.Key); + else + writer.WriteNull(); + } + else + { + var converter = map.GetDynamicConverter(model, true) as IJsonPropertyWriter; + converter.Write(map, model, ref writer, value, isTopLevel); + } + } + } +} diff --git a/src/Discord.Net.Serialization/Json/Converters/Enum.cs b/src/Discord.Net.Serialization/Json/Converters/Enum.cs index c320b5018..7a5ee96eb 100644 --- a/src/Discord.Net.Serialization/Json/Converters/Enum.cs +++ b/src/Discord.Net.Serialization/Json/Converters/Enum.cs @@ -2,22 +2,68 @@ namespace Discord.Serialization.Json.Converters { - internal class EnumPropertyConverter : IJsonPropertyConverter + public class Int64EnumPropertyConverter : IJsonPropertyConverter where T : struct { private static readonly EnumMap _map = EnumMap.For(); - public T Read(PropertyMap map, ref JsonReader reader, bool isTopLevel) + public T Read(PropertyMap map, object model, ref JsonReader reader, bool isTopLevel) + { + if (isTopLevel) + reader.Read(); + if (reader.ValueType != JsonValueType.Number && reader.ValueType != JsonValueType.String) + 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) + { + long key = _map.ToInt64(value); + if (isTopLevel) + writer.WriteAttribute(map.Key, key); + else + writer.WriteValue(key); + } + } + + public class UInt64EnumPropertyConverter : IJsonPropertyConverter + where T : struct + { + private static readonly EnumMap _map = EnumMap.For(); + + public T Read(PropertyMap map, object model, ref JsonReader reader, bool isTopLevel) + { + if (isTopLevel) + reader.Read(); + if (reader.ValueType != JsonValueType.Number && reader.ValueType != JsonValueType.String) + 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) + { + ulong key = _map.ToUInt64(value); + if (isTopLevel) + writer.WriteAttribute(map.Key, key); + else + writer.WriteValue(key); + } + } + + public class StringEnumPropertyConverter : IJsonPropertyConverter + where T : struct + { + private static readonly EnumMap _map = EnumMap.For(); + + public T Read(PropertyMap map, object model, ref JsonReader reader, bool isTopLevel) { if (isTopLevel) reader.Read(); if (reader.ValueType != JsonValueType.String) throw new SerializationException("Bad input, expected String"); - return _map.GetValue(reader.Value); + return _map.FromKey(reader.Value); } - public void Write(PropertyMap map, ref JsonWriter writer, T value, bool isTopLevel) + public void Write(PropertyMap map, object model, ref JsonWriter writer, T value, bool isTopLevel) { - string key = _map.GetKey(value); + string key = _map.ToUtf16Key(value); if (isTopLevel) writer.WriteAttribute(map.Key, key); else diff --git a/src/Discord.Net.Serialization/Json/Converters/Nullable.cs b/src/Discord.Net.Serialization/Json/Converters/Nullable.cs index bd1efaa31..f326fc7a8 100644 --- a/src/Discord.Net.Serialization/Json/Converters/Nullable.cs +++ b/src/Discord.Net.Serialization/Json/Converters/Nullable.cs @@ -2,7 +2,7 @@ namespace Discord.Serialization.Json.Converters { - internal class NullablePropertyConverter : IJsonPropertyConverter + public class NullablePropertyConverter : IJsonPropertyConverter where T : struct { private readonly IJsonPropertyConverter _innerConverter; @@ -12,19 +12,19 @@ namespace Discord.Serialization.Json.Converters _innerConverter = innerConverter; } - public T? Read(PropertyMap map, ref JsonReader reader, bool isTopLevel) + public T? Read(PropertyMap map, object model, ref JsonReader reader, bool isTopLevel) { if (isTopLevel) reader.Read(); if (reader.ValueType == JsonValueType.Null) return null; - return _innerConverter.Read(map, ref reader, false); + return _innerConverter.Read(map, model, ref reader, false); } - public void Write(PropertyMap map, ref JsonWriter writer, T? value, bool isTopLevel) + public void Write(PropertyMap map, object model, ref JsonWriter writer, T? value, bool isTopLevel) { if (value.HasValue) - _innerConverter.Write(map, ref writer, value.Value, isTopLevel); + _innerConverter.Write(map, model, ref writer, value.Value, isTopLevel); else { if (isTopLevel) diff --git a/src/Discord.Net.Serialization/Json/Converters/Object.cs b/src/Discord.Net.Serialization/Json/Converters/Object.cs index 115b369f1..f4a9f8456 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 { - internal class ObjectPropertyConverter : IJsonPropertyConverter + public class ObjectPropertyConverter : IJsonPropertyConverter where T : class, new() { private readonly ModelMap _map; @@ -12,27 +12,27 @@ namespace Discord.Serialization.Json.Converters _map = serializer.MapModel(); } - public T Read(PropertyMap map, ref JsonReader reader, bool isTopLevel) + public T Read(PropertyMap map, object model, ref JsonReader reader, bool isTopLevel) { - var model = new T(); + 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 model; + 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(model, ref reader); + (property as IJsonPropertyMap).Read(subModel, ref reader); else - JsonUtils.Skip(ref reader); //Unknown property, skip + JsonReaderUtils.Skip(ref reader); //Unknown property, skip } throw new SerializationException("Bad input, expected EndObject"); } - public void Write(PropertyMap map, ref JsonWriter writer, T value, bool isTopLevel) + public void Write(PropertyMap map, object model, ref JsonWriter writer, T value, bool isTopLevel) { if (isTopLevel) writer.WriteObjectStart(map.Key); diff --git a/src/Discord.Net.Serialization/Json/Converters/Primitives.DateTime.cs b/src/Discord.Net.Serialization/Json/Converters/Primitives.DateTime.cs index e8e8e4ab8..0e860f08d 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 { - internal class DateTimePropertyConverter : IJsonPropertyConverter + public class DateTimePropertyConverter : IJsonPropertyConverter { - public DateTime Read(PropertyMap map, ref JsonReader reader, bool isTopLevel) + public DateTime 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 reader.ParseDateTime(); } - public void Write(PropertyMap map, ref JsonWriter writer, DateTime value, bool isTopLevel) + public void Write(PropertyMap map, object model, ref JsonWriter writer, DateTime value, bool isTopLevel) { if (isTopLevel) writer.WriteAttribute(map.Key, value); @@ -22,9 +22,9 @@ namespace Discord.Serialization.Json.Converters } } - internal class DateTimeOffsetPropertyConverter : IJsonPropertyConverter + public class DateTimeOffsetPropertyConverter : IJsonPropertyConverter { - public DateTimeOffset Read(PropertyMap map, ref JsonReader reader, bool isTopLevel) + public DateTimeOffset Read(PropertyMap map, object model, ref JsonReader reader, bool isTopLevel) { if (isTopLevel) reader.Read(); @@ -32,7 +32,7 @@ namespace Discord.Serialization.Json.Converters throw new SerializationException("Bad input, expected String"); return reader.ParseDateTimeOffset(); } - public void Write(PropertyMap map, ref JsonWriter writer, DateTimeOffset value, bool isTopLevel) + public void Write(PropertyMap map, object model, ref JsonWriter writer, DateTimeOffset value, bool isTopLevel) { if (isTopLevel) writer.WriteAttribute(map.Key, value); diff --git a/src/Discord.Net.Serialization/Json/Converters/Primitives.Float.cs b/src/Discord.Net.Serialization/Json/Converters/Primitives.Float.cs index 1c5b33678..5f715eb8f 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 { - internal class SinglePropertyConverter : IJsonPropertyConverter + public class SinglePropertyConverter : IJsonPropertyConverter { - public float Read(PropertyMap map, ref JsonReader reader, bool isTopLevel) + public float Read(PropertyMap map, object model, ref JsonReader reader, bool isTopLevel) { if (isTopLevel) reader.Read(); @@ -12,7 +12,7 @@ namespace Discord.Serialization.Json.Converters throw new SerializationException("Bad input, expected Number or String"); return reader.ParseSingle(); } - public void Write(PropertyMap map, ref JsonWriter writer, float value, bool isTopLevel) + public void Write(PropertyMap map, object model, ref JsonWriter writer, float value, bool isTopLevel) { if (isTopLevel) writer.WriteAttribute(map.Key, value.ToString()); @@ -21,9 +21,9 @@ namespace Discord.Serialization.Json.Converters } } - internal class DoublePropertyConverter : IJsonPropertyConverter + public class DoublePropertyConverter : IJsonPropertyConverter { - public double Read(PropertyMap map, ref JsonReader reader, bool isTopLevel) + public double Read(PropertyMap map, object model, ref JsonReader reader, bool isTopLevel) { if (isTopLevel) reader.Read(); @@ -31,7 +31,7 @@ namespace Discord.Serialization.Json.Converters throw new SerializationException("Bad input, expected Number or String"); return reader.ParseDouble(); } - public void Write(PropertyMap map, ref JsonWriter writer, double value, bool isTopLevel) + public void Write(PropertyMap map, object model, ref JsonWriter writer, double value, bool isTopLevel) { if (isTopLevel) writer.WriteAttribute(map.Key, value.ToString()); @@ -42,7 +42,7 @@ namespace Discord.Serialization.Json.Converters internal class DecimalPropertyConverter : IJsonPropertyConverter { - public decimal Read(PropertyMap map, ref JsonReader reader, bool isTopLevel) + public decimal Read(PropertyMap map, object model, ref JsonReader reader, bool isTopLevel) { if (isTopLevel) reader.Read(); @@ -50,7 +50,7 @@ namespace Discord.Serialization.Json.Converters throw new SerializationException("Bad input, expected Number or String"); return reader.ParseDecimal(); } - public void Write(PropertyMap map, ref JsonWriter writer, decimal value, bool isTopLevel) + public void Write(PropertyMap map, object model, ref JsonWriter writer, decimal value, bool isTopLevel) { if (isTopLevel) writer.WriteAttribute(map.Key, 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 57ca83c3d..9273ad2d5 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 { - internal class BooleanPropertyConverter : IJsonPropertyConverter + public class BooleanPropertyConverter : IJsonPropertyConverter { - public bool Read(PropertyMap map, ref JsonReader reader, bool isTopLevel) + public bool Read(PropertyMap map, object model, ref JsonReader reader, bool isTopLevel) { if (isTopLevel) reader.Read(); @@ -16,7 +16,7 @@ namespace Discord.Serialization.Json.Converters default: throw new SerializationException("Bad input, expected False or True"); } } - public void Write(PropertyMap map, ref JsonWriter writer, bool value, bool isTopLevel) + public void Write(PropertyMap map, object model, ref JsonWriter writer, bool value, bool isTopLevel) { if (isTopLevel) writer.WriteAttribute(map.Key, value); @@ -25,9 +25,9 @@ namespace Discord.Serialization.Json.Converters } } - internal class GuidPropertyConverter : IJsonPropertyConverter + public class GuidPropertyConverter : IJsonPropertyConverter { - public Guid Read(PropertyMap map, ref JsonReader reader, bool isTopLevel) + public Guid Read(PropertyMap map, object model, ref JsonReader reader, bool isTopLevel) { if (isTopLevel) reader.Read(); @@ -35,7 +35,7 @@ namespace Discord.Serialization.Json.Converters throw new SerializationException("Bad input, expected String"); return reader.ParseGuid(); } - public void Write(PropertyMap map, ref JsonWriter writer, Guid value, bool isTopLevel) + public void Write(PropertyMap map, object model, ref JsonWriter writer, Guid value, bool isTopLevel) { if (isTopLevel) writer.WriteAttribute(map.Key, 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 dcf0cd946..21bd65ec5 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 { - internal class Int8PropertyConverter : IJsonPropertyConverter + public class Int8PropertyConverter : IJsonPropertyConverter { - public sbyte Read(PropertyMap map, ref JsonReader reader, bool isTopLevel) + public sbyte Read(PropertyMap map, object model, ref JsonReader reader, bool isTopLevel) { if (isTopLevel) reader.Read(); @@ -12,7 +12,7 @@ namespace Discord.Serialization.Json.Converters throw new SerializationException("Bad input, expected Number or String"); return reader.ParseInt8(); } - public void Write(PropertyMap map, ref JsonWriter writer, sbyte value, bool isTopLevel) + public void Write(PropertyMap map, object model, ref JsonWriter writer, sbyte value, bool isTopLevel) { if (isTopLevel) writer.WriteAttribute(map.Key, value); @@ -21,9 +21,9 @@ namespace Discord.Serialization.Json.Converters } } - internal class Int16PropertyConverter : IJsonPropertyConverter + public class Int16PropertyConverter : IJsonPropertyConverter { - public short Read(PropertyMap map, ref JsonReader reader, bool isTopLevel) + public short Read(PropertyMap map, object model, ref JsonReader reader, bool isTopLevel) { if (isTopLevel) reader.Read(); @@ -31,7 +31,7 @@ namespace Discord.Serialization.Json.Converters throw new SerializationException("Bad input, expected Number or String"); return reader.ParseInt16(); } - public void Write(PropertyMap map, ref JsonWriter writer, short value, bool isTopLevel) + public void Write(PropertyMap map, object model, ref JsonWriter writer, short value, bool isTopLevel) { if (isTopLevel) writer.WriteAttribute(map.Key, value); @@ -40,9 +40,9 @@ namespace Discord.Serialization.Json.Converters } } - internal class Int32PropertyConverter : IJsonPropertyConverter + public class Int32PropertyConverter : IJsonPropertyConverter { - public int Read(PropertyMap map, ref JsonReader reader, bool isTopLevel) + public int Read(PropertyMap map, object model, ref JsonReader reader, bool isTopLevel) { if (isTopLevel) reader.Read(); @@ -50,7 +50,7 @@ namespace Discord.Serialization.Json.Converters throw new SerializationException("Bad input, expected Number or String"); return reader.ParseInt32(); } - public void Write(PropertyMap map, ref JsonWriter writer, int value, bool isTopLevel) + public void Write(PropertyMap map, object model, ref JsonWriter writer, int value, bool isTopLevel) { if (isTopLevel) writer.WriteAttribute(map.Key, value); @@ -59,9 +59,9 @@ namespace Discord.Serialization.Json.Converters } } - internal class Int64PropertyConverter : IJsonPropertyConverter + public class Int64PropertyConverter : IJsonPropertyConverter { - public long Read(PropertyMap map, ref JsonReader reader, bool isTopLevel) + public long Read(PropertyMap map, object model, ref JsonReader reader, bool isTopLevel) { if (isTopLevel) reader.Read(); @@ -69,7 +69,7 @@ namespace Discord.Serialization.Json.Converters throw new SerializationException("Bad input, expected Number or String"); return reader.ParseInt64(); } - public void Write(PropertyMap map, ref JsonWriter writer, long value, bool isTopLevel) + public void Write(PropertyMap map, object model, ref JsonWriter writer, long value, bool isTopLevel) { if (isTopLevel) writer.WriteAttribute(map.Key, 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 2ec6b2b1e..6b4ce384c 100644 --- a/src/Discord.Net.Serialization/Json/Converters/Primitives.String.cs +++ b/src/Discord.Net.Serialization/Json/Converters/Primitives.String.cs @@ -1,11 +1,10 @@ using System.Text.Json; -using System.Text.Utf8; namespace Discord.Serialization.Json.Converters { - /*internal class CharPropertyConverter : IJsonPropertyConverter + /*public class CharPropertyConverter : IJsonPropertyConverter { - public char Read(PropertyMap map, JsonReader reader, bool isTopLevel) + public char Read(PropertyMap map, object model, ref JsonReader reader, bool isTopLevel) { if (isTopLevel) reader.Read(); @@ -13,7 +12,7 @@ namespace Discord.Serialization.Json.Converters throw new SerializationException("Bad input, expected String"); return reader.ParseChar(); } - public void Write(PropertyMap map, JsonWriter writer, char value, bool isTopLevel) + public void Write(PropertyMap map, object model, ref JsonWriter writer, char value, bool isTopLevel) { if (isTopLevel) writer.WriteAttribute(map.Key, value); @@ -22,17 +21,19 @@ namespace Discord.Serialization.Json.Converters } }*/ - internal class StringPropertyConverter : IJsonPropertyConverter + public class StringPropertyConverter : IJsonPropertyConverter { - public string Read(PropertyMap map, ref JsonReader reader, bool isTopLevel) + public string Read(PropertyMap map, object model, ref JsonReader reader, bool isTopLevel) { if (isTopLevel) reader.Read(); - if (reader.ValueType != JsonValueType.String) + if (reader.ValueType == JsonValueType.Null) + return null; + else if (reader.ValueType != JsonValueType.String) throw new SerializationException("Bad input, expected String"); return reader.ParseString(); } - public void Write(PropertyMap map, ref JsonWriter writer, string value, bool isTopLevel) + public void Write(PropertyMap map, object model, ref JsonWriter writer, string value, bool isTopLevel) { if (isTopLevel) writer.WriteAttribute(map.Key, value); diff --git a/src/Discord.Net.Serialization/Json/Converters/Primitives.Unsigned.cs b/src/Discord.Net.Serialization/Json/Converters/Primitives.Unsigned.cs index cd1267abb..7136ea290 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 { - internal class UInt8PropertyConverter : IJsonPropertyConverter + public class UInt8PropertyConverter : IJsonPropertyConverter { - public byte Read(PropertyMap map, ref JsonReader reader, bool isTopLevel) + public byte Read(PropertyMap map, object model, ref JsonReader reader, bool isTopLevel) { if (isTopLevel) reader.Read(); @@ -12,7 +12,7 @@ namespace Discord.Serialization.Json.Converters throw new SerializationException("Bad input, expected Number or String"); return reader.ParseUInt8(); } - public void Write(PropertyMap map, ref JsonWriter writer, byte value, bool isTopLevel) + public void Write(PropertyMap map, object model, ref JsonWriter writer, byte value, bool isTopLevel) { if (isTopLevel) writer.WriteAttribute(map.Key, value); @@ -21,9 +21,9 @@ namespace Discord.Serialization.Json.Converters } } - internal class UInt16PropertyConverter : IJsonPropertyConverter + public class UInt16PropertyConverter : IJsonPropertyConverter { - public ushort Read(PropertyMap map, ref JsonReader reader, bool isTopLevel) + public ushort Read(PropertyMap map, object model, ref JsonReader reader, bool isTopLevel) { if (isTopLevel) reader.Read(); @@ -31,7 +31,7 @@ namespace Discord.Serialization.Json.Converters throw new SerializationException("Bad input, expected Number or String"); return reader.ParseUInt16(); } - public void Write(PropertyMap map, ref JsonWriter writer, ushort value, bool isTopLevel) + public void Write(PropertyMap map, object model, ref JsonWriter writer, ushort value, bool isTopLevel) { if (isTopLevel) writer.WriteAttribute(map.Key, value); @@ -40,9 +40,9 @@ namespace Discord.Serialization.Json.Converters } } - internal class UInt32PropertyConverter : IJsonPropertyConverter + public class UInt32PropertyConverter : IJsonPropertyConverter { - public uint Read(PropertyMap map, ref JsonReader reader, bool isTopLevel) + public uint Read(PropertyMap map, object model, ref JsonReader reader, bool isTopLevel) { if (isTopLevel) reader.Read(); @@ -50,7 +50,7 @@ namespace Discord.Serialization.Json.Converters throw new SerializationException("Bad input, expected Number or String"); return reader.ParseUInt32(); } - public void Write(PropertyMap map, ref JsonWriter writer, uint value, bool isTopLevel) + public void Write(PropertyMap map, object model, ref JsonWriter writer, uint value, bool isTopLevel) { if (isTopLevel) writer.WriteAttribute(map.Key, value); @@ -59,9 +59,9 @@ namespace Discord.Serialization.Json.Converters } } - internal class UInt64PropertyConverter : IJsonPropertyConverter + public class UInt64PropertyConverter : IJsonPropertyConverter { - public ulong Read(PropertyMap map, ref JsonReader reader, bool isTopLevel) + public ulong Read(PropertyMap map, object model, ref JsonReader reader, bool isTopLevel) { if (isTopLevel) reader.Read(); @@ -69,7 +69,7 @@ namespace Discord.Serialization.Json.Converters throw new SerializationException("Bad input, expected Number or String"); return reader.ParseUInt64(); } - public void Write(PropertyMap map, ref JsonWriter writer, ulong value, bool isTopLevel) + public void Write(PropertyMap map, object model, ref JsonWriter writer, ulong value, bool isTopLevel) { if (isTopLevel) writer.WriteAttribute(map.Key, value.ToString()); diff --git a/src/Discord.Net.Serialization/Json/IJsonPropertyConverter.cs b/src/Discord.Net.Serialization/Json/IJsonPropertyConverter.cs index 08adc28e9..824df4f0e 100644 --- a/src/Discord.Net.Serialization/Json/IJsonPropertyConverter.cs +++ b/src/Discord.Net.Serialization/Json/IJsonPropertyConverter.cs @@ -2,9 +2,14 @@ namespace Discord.Serialization.Json { - public interface IJsonPropertyConverter + public interface IJsonPropertyConverter : IJsonPropertyReader, IJsonPropertyWriter { } + + public interface IJsonPropertyReader + { + T Read(PropertyMap map, object model, ref JsonReader reader, bool isTopLevel); + } + public interface IJsonPropertyWriter { - T Read(PropertyMap map, ref JsonReader reader, bool isTopLevel); - void Write(PropertyMap map, ref JsonWriter writer, T value, bool isTopLevel); + void Write(PropertyMap map, object model, ref JsonWriter writer, T value, bool isTopLevel); } } diff --git a/src/Discord.Net.Serialization/Json/IJsonPropertyMap.cs b/src/Discord.Net.Serialization/Json/IJsonPropertyMap.cs deleted file mode 100644 index 57c4564a5..000000000 --- a/src/Discord.Net.Serialization/Json/IJsonPropertyMap.cs +++ /dev/null @@ -1,14 +0,0 @@ -using System; -using System.Text.Json; - -namespace Discord.Serialization -{ - internal interface IJsonPropertyMap - { - string Key { get; } - ReadOnlyBuffer Utf8Key { get; } - - void Write(TModel model, ref JsonWriter writer); - void Read(TModel model, ref JsonReader reader); - } -} diff --git a/src/Discord.Net.Serialization/Json/JsonPropertyMap.cs b/src/Discord.Net.Serialization/Json/JsonPropertyMap.cs index ce57bfd54..716a2bee6 100644 --- a/src/Discord.Net.Serialization/Json/JsonPropertyMap.cs +++ b/src/Discord.Net.Serialization/Json/JsonPropertyMap.cs @@ -4,19 +4,28 @@ using System.Text.Json; namespace Discord.Serialization.Json { - internal class JsonPropertyMap : PropertyMap, IJsonPropertyMap + internal interface IJsonPropertyMap { - private readonly IJsonPropertyConverter _converter; - private readonly Func _getFunc; - private readonly Action _setFunc; + string Key { get; } + ReadOnlyBuffer Utf8Key { get; } - public JsonPropertyMap(PropertyInfo propInfo, IJsonPropertyConverter converter) - : base(propInfo) + void Write(TModel model, ref JsonWriter writer); + void Read(TModel model, ref JsonReader reader); + } + + internal class JsonPropertyMap : PropertyMap, IJsonPropertyMap + { + private readonly IJsonPropertyConverter _converter; + private readonly Func _getFunc; + private readonly Action _setFunc; + + public JsonPropertyMap(Serializer serializer, PropertyInfo propInfo, IJsonPropertyConverter converter) + : base(serializer, propInfo) { _converter = converter; - _getFunc = propInfo.GetMethod.CreateDelegate(typeof(Func)) as Func; - _setFunc = propInfo.SetMethod.CreateDelegate(typeof(Action)) as Action; + _getFunc = propInfo.GetMethod.CreateDelegate(typeof(Func)) as Func; + _setFunc = propInfo.SetMethod.CreateDelegate(typeof(Action)) as Action; } public void Write(TModel model, ref JsonWriter writer) @@ -24,11 +33,11 @@ namespace Discord.Serialization.Json var value = _getFunc(model); if (value == null && ExcludeNull) return; - _converter.Write(this, ref writer, value, true); + _converter.Write(this, model, ref writer, value, true); } public void Read(TModel model, ref JsonReader reader) { - var value = _converter.Read(this, ref reader, true); + var value = _converter.Read(this, model, ref reader, true); _setFunc(model, value); } } diff --git a/src/Discord.Net.Serialization/Json/JsonSerializer.cs b/src/Discord.Net.Serialization/Json/JsonSerializer.cs index edf8f58c2..e17bacb06 100644 --- a/src/Discord.Net.Serialization/Json/JsonSerializer.cs +++ b/src/Discord.Net.Serialization/Json/JsonSerializer.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.Linq; using System.Reflection; using System.Text; using System.Text.Formatting; @@ -28,7 +29,7 @@ namespace Discord.Serialization.Json AddConverter(); AddConverter(); - //AddConverter(); //char.Parse does not support Json.Net's serialization + //AddConverter(); //TODO: char.Parse does not support Json.Net's serialization AddConverter(); AddConverter(); @@ -37,11 +38,20 @@ namespace Discord.Serialization.Json AddConverter(); AddConverter(); + AddConverter( + (type, prop) => prop.GetCustomAttributes().Any()); + AddGenericConverter(typeof(List<>), typeof(Converters.ListPropertyConverter<>)); AddGenericConverter(typeof(Nullable<>), typeof(Converters.NullablePropertyConverter<>)); - AddGenericConverter(typeof(Converters.EnumPropertyConverter<>), (type, prop) => type.IsEnum); - AddGenericConverter(typeof(Converters.ObjectPropertyConverter<>), (type, prop) => type.IsClass); + 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); @@ -56,22 +66,33 @@ namespace Discord.Serialization.Json protected override PropertyMap CreatePropertyMap(PropertyInfo propInfo) { var converter = (IJsonPropertyConverter)GetConverter(typeof(TValue), propInfo); - return new JsonPropertyMap(propInfo, converter); + return new JsonPropertyMap(this, propInfo, converter); } public override TModel Read(ReadOnlyBuffer data) { var reader = new JsonReader(data.Span, SymbolTable.InvariantUtf8); if (!reader.Read()) - return null; + return default; var converter = GetConverter(typeof(TModel)) as IJsonPropertyConverter; - return converter.Read(null, ref reader, false); + 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, ref writer, model, false); + converter.Write(null, null, ref writer, model, false); } + + 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/ModelMap.cs b/src/Discord.Net.Serialization/ModelMap.cs index 29657aa22..ba8fa405a 100644 --- a/src/Discord.Net.Serialization/ModelMap.cs +++ b/src/Discord.Net.Serialization/ModelMap.cs @@ -1,24 +1,27 @@ using System; using System.Collections.Generic; using System.Linq; +using System.Reflection; namespace Discord.Serialization { public class ModelMap where TModel : class, new() { - private BufferDictionary _dictionary; + private BufferDictionary _propDict; + + public bool HasDynamics { get; } public PropertyMap[] Properties { get; } - public ModelMap(List properties) + public ModelMap(Serializer serializer, TypeInfo type, List properties) { Properties = properties.ToArray(); - _dictionary = new BufferDictionary(properties.ToDictionary(x => x.Utf8Key)); + _propDict = new BufferDictionary(properties.ToDictionary(x => x.Utf8Key)); } public bool TryGetProperty(ReadOnlyBuffer key, out PropertyMap value) - => _dictionary.TryGetValue(key, out value); + => _propDict.TryGetValue(key, out value); public bool TryGetProperty(ReadOnlySpan key, out PropertyMap value) - => _dictionary.TryGetValue(key, out value); + => _propDict.TryGetValue(key, out value); } } \ No newline at end of file diff --git a/src/Discord.Net.Serialization/PropertyMap.cs b/src/Discord.Net.Serialization/PropertyMap.cs index e2f50fda5..6243dc77f 100644 --- a/src/Discord.Net.Serialization/PropertyMap.cs +++ b/src/Discord.Net.Serialization/PropertyMap.cs @@ -1,4 +1,6 @@ using System; +using System.Collections.Generic; +using System.Linq; using System.Reflection; using System.Text.Utf8; @@ -8,15 +10,83 @@ namespace Discord.Serialization { public string Key { get; } public ReadOnlyBuffer Utf8Key { get; } + public string Name { get; } + public bool ExcludeNull { get; } - public PropertyMap(PropertyInfo propInfo) + public PropertyMap(Serializer serializer, PropertyInfo propInfo) { - var jsonProperty = propInfo.GetCustomAttribute(); + Name = propInfo.Name; - Key = jsonProperty?.Key ?? propInfo.Name; + var attr = propInfo.GetCustomAttribute(); + Key = attr.Key ?? propInfo.Name; Utf8Key = new ReadOnlyBuffer(new Utf8String(Key).Bytes.ToArray()); - ExcludeNull = jsonProperty?.ExcludeNull ?? false; + ExcludeNull = attr.ExcludeNull; + + } + + public abstract object GetDynamicConverter(object model, bool throwOnMissing); + } + + public abstract class PropertyMap : PropertyMap + { + private class Selector + { + private static readonly MethodInfo _getSelectorKeyFunc + = typeof(Selector).GetTypeInfo().GetDeclaredMethod(nameof(GetSelectorKey)); + + private readonly ISelectorGroup _group; + private readonly Delegate _getSelectorFunc; + private readonly Delegate _getWrappedSelectorFunc; + + public Selector(PropertyInfo prop, ISelectorGroup group, Delegate getSelectorFunc) + { + _group = group; + _getSelectorFunc = getSelectorFunc; + + var funcType = typeof(Func<,>).MakeGenericType(typeof(object), prop.PropertyType); + _getWrappedSelectorFunc = _getSelectorKeyFunc.MakeGenericMethod(prop.PropertyType).CreateDelegate(funcType, this); + } + + private TKey GetSelectorKey(object model) + => (_getSelectorFunc as Func)((TModel)model); + public object GetDynamicConverter(object model) + => _group.GetDynamicConverter(_getWrappedSelectorFunc, model); + } + + private readonly Delegate _getSelectorFunc, _getWrappedSelectorFunc; + private readonly IReadOnlyList _selectors; + + public PropertyMap(Serializer serializer, PropertyInfo propInfo) + : base(serializer, propInfo) + { + _selectors = propInfo.GetCustomAttributes() + .Select(x => + { + var prop = typeof(TModel).GetTypeInfo().DeclaredProperties.FirstOrDefault(y => y.Name == x.SelectorProperty); + if (prop == null) + throw new SerializationException($"Selector key \"{x.SelectorProperty}\" not found"); + var selectorGroup = serializer.GetSelectorGroup(prop.PropertyType, x.SelectorGroup); + + var funcType = typeof(Func<,>).MakeGenericType(typeof(TModel), prop.PropertyType); + var selectorFunc = prop.GetMethod.CreateDelegate(funcType); + + return new Selector(prop, selectorGroup, selectorFunc); + }) + .ToList(); + } + + public override object GetDynamicConverter(object model, bool throwOnMissing) + { + for (int i = 0; i < _selectors.Count; i++) + { + object converter = _selectors[i].GetDynamicConverter(model); + if (converter != null) + return converter; + } + if (throwOnMissing) + throw new SerializationException($"Unable to find a converter for {Name}."); + return null; } } } diff --git a/src/Discord.Net.Serialization/SelectorGroup.cs b/src/Discord.Net.Serialization/SelectorGroup.cs new file mode 100644 index 000000000..7b6a6aa2b --- /dev/null +++ b/src/Discord.Net.Serialization/SelectorGroup.cs @@ -0,0 +1,40 @@ +using System; +using System.Collections.Concurrent; + +namespace Discord.Serialization +{ + public interface ISelectorGroup + { + Type Type { get; } + + void AddDynamicConverter(object key, object converter); + object GetDynamicConverter(Delegate getKeyFunc, object model); + } + + internal class SelectorGroup : ISelectorGroup + { + private ConcurrentDictionary _mapping; + + public Type Type => typeof(TKey); + + public SelectorGroup() + { + _mapping = new ConcurrentDictionary(); + } + + public void AddDynamicConverter(object key, object converter) + => _mapping[(TKey)key] = converter; + + public object GetDynamicConverter(Delegate getKeyFunc, object model) + { + var keyFunc = getKeyFunc as Func; + var key = keyFunc(model); + if (key == null) + return null; + if (_mapping.TryGetValue(key, out var converter)) + return converter; + + return null; + } + } +} diff --git a/src/Discord.Net.Serialization/Serializer.cs b/src/Discord.Net.Serialization/Serializer.cs index 1b8bf7928..c30caccf7 100644 --- a/src/Discord.Net.Serialization/Serializer.cs +++ b/src/Discord.Net.Serialization/Serializer.cs @@ -32,41 +32,49 @@ namespace Discord.Serialization IsScoped = true; } - protected object GetConverter(Type type, PropertyInfo propInfo = null) - => _converters.Get(type, propInfo); + protected object GetConverter(Type valueType, PropertyInfo propInfo = null) + => _converters.Get(valueType, propInfo); - public void AddConverter(Type type, Type converter) + public void AddConverter(Type valueType, Type converterType) { CheckScoped(); - _converters.Add(type, converter); + _converters.Add(valueType, converterType); } - public void AddConverter(Type type, Type converter, Func condition) + public void AddConverter(Type valueType, Type converterType, Func condition) { CheckScoped(); - _converters.Add(type, converter, condition); + _converters.Add(valueType, converterType, condition); } - public void AddGenericConverter(Type converter) + public void AddGenericConverter(Type converterType) { CheckScoped(); - _converters.AddGeneric(converter); + _converters.AddGeneric(converterType); } - public void AddGenericConverter(Type converter, Func condition) + public void AddGenericConverter(Type converterType, Func condition) { CheckScoped(); - _converters.AddGeneric(converter, condition); + _converters.AddGeneric(converterType, condition); } - public void AddGenericConverter(Type value, Type converter) + public void AddGenericConverter(Type valueType, Type converterType) { CheckScoped(); - _converters.AddGeneric(value, converter); + _converters.AddGeneric(valueType, converterType); } - public void AddGenericConverter(Type value, Type converter, Func condition) + public void AddGenericConverter(Type valueType, Type converterType, Func condition) { CheckScoped(); - _converters.AddGeneric(value, converter, condition); + _converters.AddGeneric(valueType, converterType, condition); } + public void AddSelectorConverter(string groupKey, Type keyType, object keyValue, Type converterType) + { + CheckScoped(); + _converters.AddSelector(groupKey, keyType, keyValue, converterType); + } + public ISelectorGroup GetSelectorGroup(Type keyType, string groupKey) + => _converters.GetSelectorGroup(keyType, groupKey); + protected internal ModelMap MapModel() where TModel : class, new() { @@ -80,24 +88,23 @@ namespace Discord.Serialization var properties = new List(); for (int i = 0; i < propInfos.Length; i++) { - var propMap = MapProperty(propInfos[i]); - properties.Add(propMap); + if (propInfos[i].GetCustomAttribute() != null) + { + var propMap = MapProperty(propInfos[i]); + properties.Add(propMap); + } } - return new ModelMap(properties); + return new ModelMap(this, type, properties); }) as ModelMap; } private PropertyMap MapProperty(PropertyInfo propInfo) - where TModel : class, new() => _createPropertyMapMethod.MakeGenericMethod(typeof(TModel), propInfo.PropertyType).Invoke(this, new object[] { propInfo }) as PropertyMap; - protected abstract PropertyMap CreatePropertyMap(PropertyInfo propInfo) - where TModel : class, new(); - - public abstract TModel Read(ReadOnlyBuffer data) - where TModel : class, new(); - public abstract void Write(ArrayFormatter stream, TModel model) - where TModel : class, new(); + protected abstract PropertyMap CreatePropertyMap(PropertyInfo propInfo); + public abstract TModel Read(ReadOnlyBuffer data); + public abstract void Write(ArrayFormatter stream, TModel model); + private void CheckScoped() { if (IsScoped) diff --git a/src/Discord.Net.WebSocket/API/Gateway/GatewaySocketFrame.cs b/src/Discord.Net.WebSocket/API/Gateway/GatewaySocketFrame.cs new file mode 100644 index 000000000..a6e270abf --- /dev/null +++ b/src/Discord.Net.WebSocket/API/Gateway/GatewaySocketFrame.cs @@ -0,0 +1,20 @@ +#pragma warning disable CS1591 +using Discord.Serialization; + +namespace Discord.API.Gateway +{ + internal class GatewaySocketFrame + { + [ModelProperty("op")] + public GatewayOpCode Operation { get; set; } + [ModelProperty("t", ExcludeNull = true)] + public string Type { get; set; } + [ModelProperty("s", ExcludeNull = true)] + public int? Sequence { get; set; } + + [ModelProperty("d")] + [ModelSelector(nameof(Operation), ModelSelectorGroups.GatewayFrame)] + [ModelSelector(nameof(Type), ModelSelectorGroups.GatewayDispatchFrame)] + public object Payload { get; set; } + } +} diff --git a/src/Discord.Net.WebSocket/API/SocketFrame.cs b/src/Discord.Net.WebSocket/API/Voice/VoiceSocketFrame.cs similarity index 50% rename from src/Discord.Net.WebSocket/API/SocketFrame.cs rename to src/Discord.Net.WebSocket/API/Voice/VoiceSocketFrame.cs index fa7ea4a09..882f4c4ea 100644 --- a/src/Discord.Net.WebSocket/API/SocketFrame.cs +++ b/src/Discord.Net.WebSocket/API/Voice/VoiceSocketFrame.cs @@ -1,18 +1,20 @@ #pragma warning disable CS1591 using Discord.Serialization; -using System; -namespace Discord.API +namespace Discord.API.Voice { - internal class SocketFrame + internal class VoiceSocketFrame { [ModelProperty("op")] - public int Operation { get; set; } + public VoiceOpCode Operation { get; set; } [ModelProperty("t", ExcludeNull = true)] public string Type { get; set; } [ModelProperty("s", ExcludeNull = true)] public int? Sequence { get; set; } + [ModelProperty("d")] - public ReadOnlyBuffer Payload { get; set; } + [ModelSelector(nameof(Operation), ModelSelectorGroups.VoiceFrame)] + [ModelSelector(nameof(Type), ModelSelectorGroups.VoiceDispatchFrame)] + public object Payload { get; set; } } } diff --git a/src/Discord.Net.WebSocket/Audio/AudioClient.cs b/src/Discord.Net.WebSocket/Audio/AudioClient.cs index 75d08424c..f0086e4ff 100644 --- a/src/Discord.Net.WebSocket/Audio/AudioClient.cs +++ b/src/Discord.Net.WebSocket/Audio/AudioClient.cs @@ -11,6 +11,7 @@ using System.Threading.Tasks; using System.Collections.Generic; using System.Runtime.InteropServices; using Discord.Serialization; +using Discord.Serialization.Json; namespace Discord.Audio { @@ -66,7 +67,7 @@ namespace Discord.Audio ChannelId = channelId; _audioLogger = Discord.LogManager.CreateLogger($"Audio #{clientId}"); - _serializer = new Serializer(SerializationFormat.Json); + _serializer = DiscordJsonSerializer.Global.CreateScope(); _serializer.Error += ex => { _audioLogger.WarningAsync("Serializer Error", ex).GetAwaiter().GetResult(); @@ -225,7 +226,7 @@ namespace Discord.Audio _streams.Clear(); } - private async Task ProcessMessageAsync(VoiceOpCode opCode, ReadOnlyBuffer payload) + private async Task ProcessMessageAsync(VoiceOpCode opCode, object payload) { _lastMessageTime = Environment.TickCount; @@ -236,7 +237,7 @@ namespace Discord.Audio case VoiceOpCode.Ready: { await _audioLogger.DebugAsync("Received Ready").ConfigureAwait(false); - var data = _serializer.Read(payload); + var data = payload as ReadyEvent; _ssrc = data.SSRC; @@ -252,7 +253,7 @@ namespace Discord.Audio case VoiceOpCode.SessionDescription: { await _audioLogger.DebugAsync("Received SessionDescription").ConfigureAwait(false); - var data = _serializer.Read(payload); + var data = payload as SessionDescriptionEvent; if (data.Mode != DiscordVoiceApiClient.Mode) throw new InvalidOperationException($"Discord selected an unexpected mode: {data.Mode}"); @@ -288,7 +289,7 @@ namespace Discord.Audio { await _audioLogger.DebugAsync("Received Speaking").ConfigureAwait(false); - var data = _serializer.Read(payload); + var data = payload as SpeakingEvent; _ssrcMap[data.Ssrc] = data.UserId; //TODO: Memory Leak: SSRCs are never cleaned up await _speakingUpdatedEvent.InvokeAsync(data.UserId, data.Speaking); diff --git a/src/Discord.Net.WebSocket/DiscordShardedClient.cs b/src/Discord.Net.WebSocket/DiscordShardedClient.cs index 7a60aea39..f9fa52192 100644 --- a/src/Discord.Net.WebSocket/DiscordShardedClient.cs +++ b/src/Discord.Net.WebSocket/DiscordShardedClient.cs @@ -7,6 +7,7 @@ using System.Linq; using System.Threading.Tasks; using System.Threading; using Discord.Serialization; +using Discord.Serialization.Json; namespace Discord.WebSocket { @@ -54,7 +55,7 @@ namespace Discord.WebSocket _baseConfig = config; _connectionGroupLock = new SemaphoreSlim(1, 1); - _serializer = new Serializer(SerializationFormat.Json); + _serializer = DiscordJsonSerializer.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 4e8b2fe28..799fef729 100644 --- a/src/Discord.Net.WebSocket/DiscordSocketApiClient.cs +++ b/src/Discord.Net.WebSocket/DiscordSocketApiClient.cs @@ -21,8 +21,8 @@ namespace Discord.API { public event Func SentGatewayMessage { add { _sentGatewayMessageEvent.Add(value); } remove { _sentGatewayMessageEvent.Remove(value); } } private readonly AsyncEvent> _sentGatewayMessageEvent = new AsyncEvent>(); - public event Func, Task> ReceivedGatewayEvent { add { _receivedGatewayEvent.Add(value); } remove { _receivedGatewayEvent.Remove(value); } } - private readonly AsyncEvent, Task>> _receivedGatewayEvent = new AsyncEvent, Task>>(); + public event Func ReceivedGatewayEvent { add { _receivedGatewayEvent.Add(value); } remove { _receivedGatewayEvent.Remove(value); } } + private readonly AsyncEvent> _receivedGatewayEvent = new AsyncEvent>(); public event Func Disconnected { add { _disconnectedEvent.Add(value); } remove { _disconnectedEvent.Remove(value); } } private readonly AsyncEvent> _disconnectedEvent = new AsyncEvent>(); @@ -60,16 +60,16 @@ namespace Discord.API _decompressionStream.SetLength(_decompressionStream.Position); _decompressionStream.Position = 0; - var msg = _serializer.Read(_decompressionStream.ToReadOnlyBuffer()); + var msg = _serializer.Read(_decompressionStream.ToReadOnlyBuffer()); if (msg != null) - await _receivedGatewayEvent.InvokeAsync((GatewayOpCode)msg.Operation, msg.Sequence, msg.Type, msg.Payload).ConfigureAwait(false); + await _receivedGatewayEvent.InvokeAsync(msg.Operation, msg.Sequence, msg.Type, msg.Payload).ConfigureAwait(false); } } else { - var msg = _serializer.Read(data); + var msg = _serializer.Read(data); if (msg != null) - await _receivedGatewayEvent.InvokeAsync((GatewayOpCode)msg.Operation, msg.Sequence, msg.Type, msg.Payload).ConfigureAwait(false); + await _receivedGatewayEvent.InvokeAsync(msg.Operation, msg.Sequence, msg.Type, msg.Payload).ConfigureAwait(false); } }; WebSocketClient.Closed += async ex => @@ -173,20 +173,17 @@ namespace Discord.API { CheckState(); - 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 { - payload = new SocketFrame { Operation = (int)opCode, Payload = SerializeJson(data1, payload) }; - await RequestQueue.SendAsync(new WebSocketRequest(WebSocketClient, null, SerializeJson(data2, payload), true, options)).ConfigureAwait(false); + var frame = new GatewaySocketFrame { Operation = opCode, Payload = payload }; + await RequestQueue.SendAsync(new WebSocketRequest(WebSocketClient, null, SerializeJson(data, frame), true, options)).ConfigureAwait(false); await _sentGatewayMessageEvent.InvokeAsync(opCode).ConfigureAwait(false); } finally { - _formatters.Enqueue(data1); - _formatters.Enqueue(data2); + _formatters.Enqueue(data); } } diff --git a/src/Discord.Net.WebSocket/DiscordSocketClient.cs b/src/Discord.Net.WebSocket/DiscordSocketClient.cs index 2816c8041..1f36a4c49 100644 --- a/src/Discord.Net.WebSocket/DiscordSocketClient.cs +++ b/src/Discord.Net.WebSocket/DiscordSocketClient.cs @@ -5,6 +5,7 @@ using Discord.Net.Udp; using Discord.Net.WebSockets; using Discord.Rest; using Discord.Serialization; +using Discord.Serialization.Json; using System; using System.Collections.Concurrent; using System.Collections.Generic; @@ -87,7 +88,7 @@ namespace Discord.WebSocket _stateLock = new SemaphoreSlim(1, 1); _gatewayLogger = LogManager.CreateLogger(ShardId == 0 && TotalShards == 1 ? "Gateway" : $"Shard #{ShardId}"); - _serializer = new Serializer(SerializationFormat.Json); + _serializer = DiscordJsonSerializer.Global.CreateScope(); _serializer.Error += ex => { _restLogger.WarningAsync("Serializer Error", ex).GetAwaiter().GetResult(); @@ -382,7 +383,7 @@ namespace Discord.WebSocket gameModel).ConfigureAwait(false); } - private async Task ProcessMessageAsync(GatewayOpCode opCode, int? seq, string type, ReadOnlyBuffer payload) + private async Task ProcessMessageAsync(GatewayOpCode opCode, int? seq, string type, object payload) { if (seq != null) _lastSeq = seq.Value; @@ -395,7 +396,7 @@ namespace Discord.WebSocket case GatewayOpCode.Hello: { await _gatewayLogger.DebugAsync("Received Hello").ConfigureAwait(false); - var data = _serializer.Read(payload); + var data = payload as HelloEvent; _heartbeatTask = RunHeartbeatAsync(data.HeartbeatInterval, _connection.CancelToken); } @@ -428,7 +429,7 @@ namespace Discord.WebSocket _sessionId = null; _lastSeq = 0; - bool retry = IsTrue(); + bool retry = (bool)payload; if (retry) _connection.Reconnect(); //TODO: Untested else @@ -451,7 +452,7 @@ namespace Discord.WebSocket { await _gatewayLogger.DebugAsync("Received Dispatch (READY)").ConfigureAwait(false); - var data = _serializer.Read(payload); + var data = payload as ReadyEvent; var state = new ClientState(data.Guilds.Length, data.PrivateChannels.Length); var currentUser = SocketSelfUser.Create(this, state, data.User); @@ -521,7 +522,7 @@ namespace Discord.WebSocket //Guilds case "GUILD_CREATE": { - var data = _serializer.Read(payload); + var data = payload as ExtendedGuild; if (data.Unavailable == false) { @@ -573,7 +574,7 @@ namespace Discord.WebSocket { await _gatewayLogger.DebugAsync("Received Dispatch (GUILD_UPDATE)").ConfigureAwait(false); - var data = _serializer.Read(payload); + var data = payload as API.Guild; var guild = State.GetGuild(data.Id); if (guild != null) { @@ -592,7 +593,7 @@ namespace Discord.WebSocket { await _gatewayLogger.DebugAsync("Received Dispatch (GUILD_EMOJIS_UPDATE)").ConfigureAwait(false); - var data = _serializer.Read(payload); + var data = payload as API.Gateway.GuildEmojiUpdateEvent; var guild = State.GetGuild(data.GuildId); if (guild != null) { @@ -610,7 +611,7 @@ namespace Discord.WebSocket case "GUILD_SYNC": { await _gatewayLogger.DebugAsync("Received Dispatch (GUILD_SYNC)").ConfigureAwait(false); - var data = _serializer.Read(payload); + var data = payload as GuildSyncEvent; var guild = State.GetGuild(data.Id); if (guild != null) { @@ -631,7 +632,7 @@ namespace Discord.WebSocket break; case "GUILD_DELETE": { - var data = _serializer.Read(payload); + var data = payload as ExtendedGuild; if (data.Unavailable == true) { type = "GUILD_UNAVAILABLE"; @@ -673,7 +674,7 @@ namespace Discord.WebSocket { await _gatewayLogger.DebugAsync("Received Dispatch (CHANNEL_CREATE)").ConfigureAwait(false); - var data = _serializer.Read(payload); + var data = payload as API.Channel; SocketChannel channel = null; if (data.GuildId.IsSpecified) { @@ -705,7 +706,7 @@ namespace Discord.WebSocket { await _gatewayLogger.DebugAsync("Received Dispatch (CHANNEL_UPDATE)").ConfigureAwait(false); - var data = _serializer.Read(payload); + var data = payload as API.Channel; var channel = State.GetChannel(data.Id); if (channel != null) { @@ -733,7 +734,7 @@ namespace Discord.WebSocket await _gatewayLogger.DebugAsync("Received Dispatch (CHANNEL_DELETE)").ConfigureAwait(false); SocketChannel channel = null; - var data = _serializer.Read(payload); + var data = payload as API.Channel; if (data.GuildId.IsSpecified) { var guild = State.GetGuild(data.GuildId.Value); @@ -771,7 +772,7 @@ namespace Discord.WebSocket { await _gatewayLogger.DebugAsync("Received Dispatch (GUILD_MEMBER_ADD)").ConfigureAwait(false); - var data = _serializer.Read(payload); + var data = payload as GuildMemberAddEvent; var guild = State.GetGuild(data.GuildId); if (guild != null) { @@ -797,7 +798,7 @@ namespace Discord.WebSocket { await _gatewayLogger.DebugAsync("Received Dispatch (GUILD_MEMBER_UPDATE)").ConfigureAwait(false); - var data = _serializer.Read(payload); + var data = payload as GuildMemberUpdateEvent; var guild = State.GetGuild(data.GuildId); if (guild != null) { @@ -835,7 +836,7 @@ namespace Discord.WebSocket { await _gatewayLogger.DebugAsync("Received Dispatch (GUILD_MEMBER_REMOVE)").ConfigureAwait(false); - var data = _serializer.Read(payload); + var data = payload as GuildMemberRemoveEvent; var guild = State.GetGuild(data.GuildId); if (guild != null) { @@ -870,7 +871,7 @@ namespace Discord.WebSocket { await _gatewayLogger.DebugAsync("Received Dispatch (GUILD_MEMBERS_CHUNK)").ConfigureAwait(false); - var data = _serializer.Read(payload); + var data = payload as GuildMembersChunkEvent; var guild = State.GetGuild(data.GuildId); if (guild != null) { @@ -894,7 +895,7 @@ namespace Discord.WebSocket { await _gatewayLogger.DebugAsync("Received Dispatch (CHANNEL_RECIPIENT_ADD)").ConfigureAwait(false); - var data = _serializer.Read(payload); + var data = payload as RecipientEvent; if (State.GetChannel(data.ChannelId) is SocketGroupChannel channel) { var user = channel.GetOrAddUser(data.User); @@ -911,7 +912,7 @@ namespace Discord.WebSocket { await _gatewayLogger.DebugAsync("Received Dispatch (CHANNEL_RECIPIENT_REMOVE)").ConfigureAwait(false); - var data = _serializer.Read(payload); + var data = payload as RecipientEvent; if (State.GetChannel(data.ChannelId) is SocketGroupChannel channel) { var user = channel.RemoveUser(data.User.Id); @@ -936,7 +937,7 @@ namespace Discord.WebSocket { await _gatewayLogger.DebugAsync("Received Dispatch (GUILD_ROLE_CREATE)").ConfigureAwait(false); - var data = _serializer.Read(payload); + var data = payload as GuildRoleCreateEvent; var guild = State.GetGuild(data.GuildId); if (guild != null) { @@ -960,7 +961,7 @@ namespace Discord.WebSocket { await _gatewayLogger.DebugAsync("Received Dispatch (GUILD_ROLE_UPDATE)").ConfigureAwait(false); - var data = _serializer.Read(payload); + var data = payload as GuildRoleUpdateEvent; var guild = State.GetGuild(data.GuildId); if (guild != null) { @@ -995,7 +996,7 @@ namespace Discord.WebSocket { await _gatewayLogger.DebugAsync("Received Dispatch (GUILD_ROLE_DELETE)").ConfigureAwait(false); - var data = _serializer.Read(payload); + var data = payload as GuildRoleDeleteEvent; var guild = State.GetGuild(data.GuildId); if (guild != null) { @@ -1029,7 +1030,7 @@ namespace Discord.WebSocket { await _gatewayLogger.DebugAsync("Received Dispatch (GUILD_BAN_ADD)").ConfigureAwait(false); - var data = _serializer.Read(payload); + var data = payload as GuildBanEvent; var guild = State.GetGuild(data.GuildId); if (guild != null) { @@ -1055,7 +1056,7 @@ namespace Discord.WebSocket { await _gatewayLogger.DebugAsync("Received Dispatch (GUILD_BAN_REMOVE)").ConfigureAwait(false); - var data = _serializer.Read(payload); + var data = payload as GuildBanEvent; var guild = State.GetGuild(data.GuildId); if (guild != null) { @@ -1083,7 +1084,7 @@ namespace Discord.WebSocket { await _gatewayLogger.DebugAsync("Received Dispatch (MESSAGE_CREATE)").ConfigureAwait(false); - var data = _serializer.Read(payload); + var data = payload as API.Message; if (State.GetChannel(data.ChannelId) is ISocketMessageChannel channel) { var guild = (channel as SocketGuildChannel)?.Guild; @@ -1130,7 +1131,7 @@ namespace Discord.WebSocket { await _gatewayLogger.DebugAsync("Received Dispatch (MESSAGE_UPDATE)").ConfigureAwait(false); - var data = _serializer.Read(payload); + var data = payload as API.Message; if (State.GetChannel(data.ChannelId) is ISocketMessageChannel channel) { var guild = (channel as SocketGuildChannel)?.Guild; @@ -1177,7 +1178,7 @@ namespace Discord.WebSocket { await _gatewayLogger.DebugAsync("Received Dispatch (MESSAGE_DELETE)").ConfigureAwait(false); - var data = _serializer.Read(payload); + var data = payload as API.Message; if (State.GetChannel(data.ChannelId) is ISocketMessageChannel channel) { var guild = (channel as SocketGuildChannel)?.Guild; @@ -1204,7 +1205,7 @@ namespace Discord.WebSocket { await _gatewayLogger.DebugAsync("Received Dispatch (MESSAGE_REACTION_ADD)").ConfigureAwait(false); - var data = _serializer.Read(payload); + var data = payload as API.Gateway.Reaction; if (State.GetChannel(data.ChannelId) is ISocketMessageChannel channel) { var cachedMsg = channel.GetCachedMessage(data.MessageId) as SocketUserMessage; @@ -1228,7 +1229,7 @@ namespace Discord.WebSocket { await _gatewayLogger.DebugAsync("Received Dispatch (MESSAGE_REACTION_REMOVE)").ConfigureAwait(false); - var data = _serializer.Read(payload); + var data = payload as API.Gateway.Reaction; if (State.GetChannel(data.ChannelId) is ISocketMessageChannel channel) { var cachedMsg = channel.GetCachedMessage(data.MessageId) as SocketUserMessage; @@ -1252,7 +1253,7 @@ namespace Discord.WebSocket { await _gatewayLogger.DebugAsync("Received Dispatch (MESSAGE_REACTION_REMOVE_ALL)").ConfigureAwait(false); - var data = _serializer.Read(payload); + var data = payload as RemoveAllReactionsEvent; if (State.GetChannel(data.ChannelId) is ISocketMessageChannel channel) { var cachedMsg = channel.GetCachedMessage(data.MessageId) as SocketUserMessage; @@ -1274,7 +1275,7 @@ namespace Discord.WebSocket { await _gatewayLogger.DebugAsync("Received Dispatch (MESSAGE_DELETE_BULK)").ConfigureAwait(false); - var data = _serializer.Read(payload); + var data = payload as MessageDeleteBulkEvent; if (State.GetChannel(data.ChannelId) is ISocketMessageChannel channel) { var guild = (channel as SocketGuildChannel)?.Guild; @@ -1305,7 +1306,7 @@ namespace Discord.WebSocket { await _gatewayLogger.DebugAsync("Received Dispatch (PRESENCE_UPDATE)").ConfigureAwait(false); - var data = _serializer.Read(payload); + var data = payload as API.Presence; if (data.GuildId.IsSpecified) { @@ -1364,7 +1365,7 @@ namespace Discord.WebSocket { await _gatewayLogger.DebugAsync("Received Dispatch (TYPING_START)").ConfigureAwait(false); - var data = _serializer.Read(payload); + var data = payload as TypingStartEvent; if (State.GetChannel(data.ChannelId) is ISocketMessageChannel channel) { var guild = (channel as SocketGuildChannel)?.Guild; @@ -1386,7 +1387,7 @@ namespace Discord.WebSocket { await _gatewayLogger.DebugAsync("Received Dispatch (USER_UPDATE)").ConfigureAwait(false); - var data = _serializer.Read(payload); + var data = payload as API.User; if (data.Id == CurrentUser.Id) { var before = CurrentUser.Clone(); @@ -1406,7 +1407,7 @@ namespace Discord.WebSocket { await _gatewayLogger.DebugAsync("Received Dispatch (VOICE_STATE_UPDATE)").ConfigureAwait(false); - var data = _serializer.Read(payload); + var data = payload as API.VoiceState; SocketUser user; SocketVoiceState before, after; if (data.GuildId != null) @@ -1478,7 +1479,7 @@ namespace Discord.WebSocket { await _gatewayLogger.DebugAsync("Received Dispatch (VOICE_SERVER_UPDATE)").ConfigureAwait(false); - var data = _serializer.Read(payload); + var data = payload as VoiceServerUpdateEvent; var guild = State.GetGuild(data.GuildId); if (guild != null) { @@ -1528,15 +1529,6 @@ namespace Discord.WebSocket { await _gatewayLogger.ErrorAsync($"Error handling {opCode}{(type != null ? $" ({type})" : "")}", ex).ConfigureAwait(false); } - - bool IsTrue() - { - ref var ptr = ref payload.Span.DangerousGetPinnableReference(); - return Unsafe.Add(ref ptr, 0) == (byte)'t' && - Unsafe.Add(ref ptr, 1) == (byte)'r' && - Unsafe.Add(ref ptr, 2) == (byte)'u' && - Unsafe.Add(ref ptr, 3) == (byte)'e'; - } } private async Task RunHeartbeatAsync(int intervalMillis, CancellationToken cancelToken) diff --git a/src/Discord.Net.WebSocket/DiscordVoiceApiClient.cs b/src/Discord.Net.WebSocket/DiscordVoiceApiClient.cs index 35989f81e..842f3b620 100644 --- a/src/Discord.Net.WebSocket/DiscordVoiceApiClient.cs +++ b/src/Discord.Net.WebSocket/DiscordVoiceApiClient.cs @@ -31,8 +31,8 @@ namespace Discord.Audio public event Func SentData { add { _sentDataEvent.Add(value); } remove { _sentDataEvent.Remove(value); } } private readonly AsyncEvent> _sentDataEvent = new AsyncEvent>(); - public event Func, Task> ReceivedEvent { add { _receivedEvent.Add(value); } remove { _receivedEvent.Remove(value); } } - private readonly AsyncEvent, Task>> _receivedEvent = new AsyncEvent, Task>>(); + public event Func ReceivedEvent { add { _receivedEvent.Add(value); } remove { _receivedEvent.Remove(value); } } + private readonly AsyncEvent> _receivedEvent = new AsyncEvent>(); public event Func ReceivedPacket { add { _receivedPacketEvent.Add(value); } remove { _receivedPacketEvent.Remove(value); } } private readonly AsyncEvent> _receivedPacketEvent = new AsyncEvent>(); public event Func Disconnected { add { _disconnectedEvent.Add(value); } remove { _disconnectedEvent.Remove(value); } } @@ -79,16 +79,16 @@ namespace Discord.Audio _decompressionStream.SetLength(_decompressionStream.Position); _decompressionStream.Position = 0; - var msg = _serializer.Read(_decompressionStream.ToReadOnlyBuffer()); + var msg = _serializer.Read(_decompressionStream.ToReadOnlyBuffer()); if (msg != null) - await _receivedEvent.InvokeAsync((VoiceOpCode)msg.Operation, msg.Payload).ConfigureAwait(false); + await _receivedEvent.InvokeAsync(msg.Operation, msg.Payload).ConfigureAwait(false); } } else { - var msg = _serializer.Read(data); + var msg = _serializer.Read(data); if (msg != null) - await _receivedEvent.InvokeAsync((VoiceOpCode)msg.Operation, msg.Payload).ConfigureAwait(false); + await _receivedEvent.InvokeAsync(msg.Operation, msg.Payload).ConfigureAwait(false); } }; WebSocketClient.Closed += async ex => @@ -114,20 +114,17 @@ namespace Discord.Audio public async Task SendAsync(VoiceOpCode opCode, object payload, RequestOptions options = null) { - 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 { - payload = new SocketFrame { Operation = (int)opCode, Payload = SerializeJson(data1, payload) }; - await WebSocketClient.SendAsync(SerializeJson(data2, payload), true).ConfigureAwait(false); + var frame = new VoiceSocketFrame { Operation = opCode, Payload = payload }; + await WebSocketClient.SendAsync(SerializeJson(data, frame), true).ConfigureAwait(false); await _sentGatewayMessageEvent.InvokeAsync(opCode).ConfigureAwait(false); } finally { - _formatters.Enqueue(data1); - _formatters.Enqueue(data2); + _formatters.Enqueue(data); } } public async Task SendAsync(byte[] data, int offset, int bytes) diff --git a/src/Discord.Net.Webhook/DiscordWebhookClient.cs b/src/Discord.Net.Webhook/DiscordWebhookClient.cs index d72d4b96f..db646dba1 100644 --- a/src/Discord.Net.Webhook/DiscordWebhookClient.cs +++ b/src/Discord.Net.Webhook/DiscordWebhookClient.cs @@ -6,6 +6,7 @@ using System.Threading.Tasks; using System.Linq; using Discord.Logging; using Discord.Serialization; +using Discord.Serialization.Json; namespace Discord.Webhook { @@ -29,7 +30,7 @@ namespace Discord.Webhook { _webhookId = webhookId; - _serializer = new Serializer(SerializationFormat.Json); + _serializer = DiscordJsonSerializer.Global.CreateScope(); _serializer.Error += ex => { _restLogger.WarningAsync("Serializer Error", ex).GetAwaiter().GetResult();