| @@ -198,7 +198,11 @@ namespace Discord.API | |||||
| var request = new JsonRestRequest(RestClient, method, endpoint, SerializeJson(data, payload), options); | var request = new JsonRestRequest(RestClient, method, endpoint, SerializeJson(data, payload), options); | ||||
| await SendInternalAsync(method, endpoint, request).ConfigureAwait(false); | await SendInternalAsync(method, endpoint, request).ConfigureAwait(false); | ||||
| } | } | ||||
| finally { _formatters.Enqueue(data); } | |||||
| finally | |||||
| { | |||||
| data.Clear(); | |||||
| _formatters.Enqueue(data); | |||||
| } | |||||
| } | } | ||||
| internal Task SendMultipartAsync(string method, Expression<Func<string>> endpointExpr, IReadOnlyDictionary<string, object> multipartArgs, BucketIds ids, | internal Task SendMultipartAsync(string method, Expression<Func<string>> endpointExpr, IReadOnlyDictionary<string, object> multipartArgs, BucketIds ids, | ||||
| @@ -247,7 +251,11 @@ namespace Discord.API | |||||
| var request = new JsonRestRequest(RestClient, method, endpoint, SerializeJson(data, payload), options); | var request = new JsonRestRequest(RestClient, method, endpoint, SerializeJson(data, payload), options); | ||||
| return DeserializeJson<TResponse>(await SendInternalAsync(method, endpoint, request).ConfigureAwait(false)); | return DeserializeJson<TResponse>(await SendInternalAsync(method, endpoint, request).ConfigureAwait(false)); | ||||
| } | } | ||||
| finally { _formatters.Enqueue(data); } | |||||
| finally | |||||
| { | |||||
| data.Clear(); | |||||
| _formatters.Enqueue(data); | |||||
| } | |||||
| } | } | ||||
| internal Task<TResponse> SendMultipartAsync<TResponse>(string method, Expression<Func<string>> endpointExpr, IReadOnlyDictionary<string, object> multipartArgs, BucketIds ids, | internal Task<TResponse> SendMultipartAsync<TResponse>(string method, Expression<Func<string>> endpointExpr, IReadOnlyDictionary<string, object> multipartArgs, BucketIds ids, | ||||
| @@ -17,7 +17,7 @@ namespace Discord.Rest | |||||
| public DiscordRestClient() : this(new DiscordRestConfig()) { } | public DiscordRestClient() : this(new DiscordRestConfig()) { } | ||||
| public DiscordRestClient(DiscordRestConfig config) : base(config) | public DiscordRestClient(DiscordRestConfig config) : base(config) | ||||
| { | { | ||||
| _serializer = DiscordJsonSerializer.Global.CreateScope(); | |||||
| _serializer = DiscordRestJsonSerializer.Global.CreateScope(); | |||||
| _serializer.Error += ex => | _serializer.Error += ex => | ||||
| { | { | ||||
| _restLogger.WarningAsync("Serializer Error", ex).GetAwaiter().GetResult(); | _restLogger.WarningAsync("Serializer Error", ex).GetAwaiter().GetResult(); | ||||
| @@ -106,7 +106,7 @@ namespace Discord.Net.Queue | |||||
| { | { | ||||
| try | try | ||||
| { | { | ||||
| var error = DiscordJsonSerializer.Global.Read<Error>(response.Data); | |||||
| var error = DiscordRestJsonSerializer.Global.Read<Error>(response.Data); | |||||
| code = error.Code; | code = error.Code; | ||||
| reason = error.Message; | reason = error.Message; | ||||
| } | } | ||||
| @@ -3,16 +3,16 @@ using System.Text.Json; | |||||
| namespace Discord.Serialization.Json.Converters | namespace Discord.Serialization.Json.Converters | ||||
| { | { | ||||
| internal class EntityOrIdPropertyConverter<T> : IJsonPropertyConverter<EntityOrId<T>> | |||||
| internal class EntityOrIdPropertyConverter<T> : JsonPropertyConverter<EntityOrId<T>> | |||||
| { | { | ||||
| private readonly IJsonPropertyConverter<T> _innerConverter; | |||||
| private readonly JsonPropertyConverter<T> _innerConverter; | |||||
| public EntityOrIdPropertyConverter(IJsonPropertyConverter<T> innerConverter) | |||||
| public EntityOrIdPropertyConverter(JsonPropertyConverter<T> innerConverter) | |||||
| { | { | ||||
| _innerConverter = innerConverter; | _innerConverter = innerConverter; | ||||
| } | } | ||||
| public EntityOrId<T> Read(PropertyMap map, object model, ref JsonReader reader, bool isTopLevel) | |||||
| public override EntityOrId<T> Read(PropertyMap map, object model, ref JsonReader reader, bool isTopLevel) | |||||
| { | { | ||||
| if (isTopLevel) | if (isTopLevel) | ||||
| reader.Read(); | reader.Read(); | ||||
| @@ -21,14 +21,14 @@ namespace Discord.Serialization.Json.Converters | |||||
| return new EntityOrId<T>(_innerConverter.Read(map, model, ref reader, false)); | return new EntityOrId<T>(_innerConverter.Read(map, model, ref reader, false)); | ||||
| } | } | ||||
| public void Write(PropertyMap map, object model, ref JsonWriter writer, EntityOrId<T> value, bool isTopLevel) | |||||
| public override void Write(PropertyMap map, object model, ref JsonWriter writer, EntityOrId<T> value, string key) | |||||
| { | { | ||||
| if (value.Object != null) | if (value.Object != null) | ||||
| _innerConverter.Write(map, model, ref writer, value.Object, isTopLevel); | |||||
| _innerConverter.Write(map, model, ref writer, value.Object, key); | |||||
| else | else | ||||
| { | { | ||||
| if (isTopLevel) | |||||
| writer.WriteAttribute(map.Key, value.Id); | |||||
| if (key != null) | |||||
| writer.WriteAttribute(key, value.Id); | |||||
| else | else | ||||
| writer.WriteValue(value.Id); | writer.WriteValue(value.Id); | ||||
| } | } | ||||
| @@ -3,9 +3,9 @@ using System.Text.Json; | |||||
| namespace Discord.Serialization.Json.Converters | namespace Discord.Serialization.Json.Converters | ||||
| { | { | ||||
| internal class ImagePropertyConverter : IJsonPropertyConverter<API.Image> | |||||
| internal class ImagePropertyConverter : JsonPropertyConverter<API.Image> | |||||
| { | { | ||||
| 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) | if (isTopLevel) | ||||
| reader.Read(); | reader.Read(); | ||||
| @@ -13,7 +13,7 @@ namespace Discord.Serialization.Json.Converters | |||||
| throw new SerializationException("Bad input, expected String"); | throw new SerializationException("Bad input, expected String"); | ||||
| return new API.Image(reader.ParseString()); | 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; | string str; | ||||
| if (value.Stream != null) | if (value.Stream != null) | ||||
| @@ -43,8 +43,8 @@ namespace Discord.Serialization.Json.Converters | |||||
| else | else | ||||
| str = value.Hash; | str = value.Hash; | ||||
| if (isTopLevel) | |||||
| writer.WriteAttribute(map.Key, str); | |||||
| if (key != null) | |||||
| writer.WriteAttribute(key, str); | |||||
| else | else | ||||
| writer.WriteValue(str); | writer.WriteValue(str); | ||||
| } | } | ||||
| @@ -2,9 +2,9 @@ | |||||
| namespace Discord.Serialization.Json.Converters | namespace Discord.Serialization.Json.Converters | ||||
| { | { | ||||
| internal class Int53PropertyConverter : IJsonPropertyConverter<long> | |||||
| internal class Int53PropertyConverter : JsonPropertyConverter<long> | |||||
| { | { | ||||
| 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) | if (isTopLevel) | ||||
| reader.Read(); | reader.Read(); | ||||
| @@ -12,10 +12,10 @@ namespace Discord.Serialization.Json.Converters | |||||
| throw new SerializationException("Bad input, expected Number"); | throw new SerializationException("Bad input, expected Number"); | ||||
| return reader.ParseInt64(); | 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 | else | ||||
| writer.WriteValue(value.ToString()); | writer.WriteValue(value.ToString()); | ||||
| } | } | ||||
| @@ -2,22 +2,22 @@ | |||||
| namespace Discord.Serialization.Json.Converters | namespace Discord.Serialization.Json.Converters | ||||
| { | { | ||||
| internal class OptionalPropertyConverter<T> : IJsonPropertyConverter<Optional<T>> | |||||
| internal class OptionalPropertyConverter<T> : JsonPropertyConverter<Optional<T>> | |||||
| { | { | ||||
| private readonly IJsonPropertyConverter<T> _innerConverter; | |||||
| private readonly JsonPropertyConverter<T> _innerConverter; | |||||
| public OptionalPropertyConverter(IJsonPropertyConverter<T> innerConverter) | |||||
| public OptionalPropertyConverter(JsonPropertyConverter<T> innerConverter) | |||||
| { | { | ||||
| _innerConverter = innerConverter; | _innerConverter = innerConverter; | ||||
| } | } | ||||
| public Optional<T> Read(PropertyMap map, object model, ref JsonReader reader, bool isTopLevel) | |||||
| public override Optional<T> Read(PropertyMap map, object model, ref JsonReader reader, bool isTopLevel) | |||||
| => new Optional<T>(_innerConverter.Read(map, model, ref reader, isTopLevel)); | => new Optional<T>(_innerConverter.Read(map, model, ref reader, isTopLevel)); | ||||
| public void Write(PropertyMap map, object model, ref JsonWriter writer, Optional<T> value, bool isTopLevel) | |||||
| public override void Write(PropertyMap map, object model, ref JsonWriter writer, Optional<T> value, string key) | |||||
| { | { | ||||
| if (value.IsSpecified) | if (value.IsSpecified) | ||||
| _innerConverter.Write(map, model, ref writer, value.Value, isTopLevel); | |||||
| _innerConverter.Write(map, model, ref writer, value.Value, key); | |||||
| } | } | ||||
| } | } | ||||
| } | } | ||||
| @@ -2,9 +2,9 @@ | |||||
| namespace Discord.Serialization.Json.Converters | namespace Discord.Serialization.Json.Converters | ||||
| { | { | ||||
| internal class UInt53PropertyConverter : IJsonPropertyConverter<ulong> | |||||
| internal class UInt53PropertyConverter : JsonPropertyConverter<ulong> | |||||
| { | { | ||||
| 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) | if (isTopLevel) | ||||
| reader.Read(); | reader.Read(); | ||||
| @@ -12,12 +12,12 @@ namespace Discord.Serialization.Json.Converters | |||||
| throw new SerializationException("Bad input, expected Number"); | throw new SerializationException("Bad input, expected Number"); | ||||
| return reader.ParseUInt64(); | 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 | else | ||||
| writer.WriteValue(value.ToString()); | |||||
| writer.WriteValue(value); | |||||
| } | } | ||||
| } | } | ||||
| } | } | ||||
| @@ -4,12 +4,15 @@ using System.Reflection; | |||||
| namespace Discord.Serialization.Json | namespace Discord.Serialization.Json | ||||
| { | { | ||||
| public class DiscordJsonSerializer : JsonSerializer | |||||
| public class DiscordRestJsonSerializer : JsonSerializer | |||||
| { | { | ||||
| private static readonly Lazy<DiscordJsonSerializer> _singleton = new Lazy<DiscordJsonSerializer>(); | |||||
| public static new DiscordJsonSerializer Global => _singleton.Value; | |||||
| private static readonly Lazy<DiscordRestJsonSerializer> _singleton = new Lazy<DiscordRestJsonSerializer>(); | |||||
| public static DiscordRestJsonSerializer Global => _singleton.Value; | |||||
| public DiscordJsonSerializer() | |||||
| public DiscordRestJsonSerializer() | |||||
| : this((JsonSerializer)null) { } | |||||
| public DiscordRestJsonSerializer(JsonSerializer parent) | |||||
| : base(parent ?? DefaultJsonSerializer.Global) | |||||
| { | { | ||||
| AddConverter<API.Image, ImagePropertyConverter>(); | AddConverter<API.Image, ImagePropertyConverter>(); | ||||
| AddConverter<long, Int53PropertyConverter>((type, prop) => prop?.GetCustomAttribute<Int53Attribute>() != null); | AddConverter<long, Int53PropertyConverter>((type, prop) => prop?.GetCustomAttribute<Int53Attribute>() != null); | ||||
| @@ -18,7 +21,9 @@ namespace Discord.Serialization.Json | |||||
| AddGenericConverter(typeof(API.EntityOrId<>), typeof(EntityOrIdPropertyConverter<>)); | AddGenericConverter(typeof(API.EntityOrId<>), typeof(EntityOrIdPropertyConverter<>)); | ||||
| AddGenericConverter(typeof(Optional<>), typeof(OptionalPropertyConverter<>)); | 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); | |||||
| } | } | ||||
| } | } | ||||
| @@ -42,11 +42,11 @@ namespace Discord.API | |||||
| } | } | ||||
| public Task SetResultAsync(ReadOnlyBuffer<byte> data) | public Task SetResultAsync(ReadOnlyBuffer<byte> data) | ||||
| { | { | ||||
| return Promise.TrySetResultAsync(DiscordJsonSerializer.Global.Read<T>(data)); | |||||
| return Promise.TrySetResultAsync(DiscordRestJsonSerializer.Global.Read<T>(data)); | |||||
| } | } | ||||
| public Task SetExceptionAsync(ReadOnlyBuffer<byte> data) | public Task SetExceptionAsync(ReadOnlyBuffer<byte> data) | ||||
| { | { | ||||
| var error = DiscordJsonSerializer.Global.Read<ErrorEvent>(data); | |||||
| var error = DiscordRestJsonSerializer.Global.Read<ErrorEvent>(data); | |||||
| return Promise.TrySetExceptionAsync(new RpcException(error.Code, error.Message)); | return Promise.TrySetExceptionAsync(new RpcException(error.Code, error.Message)); | ||||
| } | } | ||||
| } | } | ||||
| @@ -225,26 +225,23 @@ namespace Discord.API | |||||
| private async Task<TResponse> SendRpcAsyncInternal<TResponse>(string cmd, object payload, Optional<string> evt, RequestOptions options) | private async Task<TResponse> SendRpcAsyncInternal<TResponse>(string cmd, object payload, Optional<string> evt, RequestOptions options) | ||||
| where TResponse : class, new() | 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 | try | ||||
| { | { | ||||
| var guid = Guid.NewGuid(); | 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<TResponse>(options); | var requestTracker = new RpcRequest<TResponse>(options); | ||||
| _requests[guid] = requestTracker; | _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); | await _sentRpcMessageEvent.InvokeAsync(cmd).ConfigureAwait(false); | ||||
| return await requestTracker.Promise.Task.ConfigureAwait(false); | return await requestTracker.Promise.Task.ConfigureAwait(false); | ||||
| } | } | ||||
| finally | finally | ||||
| { | { | ||||
| _formatters.Enqueue(data1); | |||||
| _formatters.Enqueue(data2); | |||||
| _formatters.Enqueue(data); | |||||
| } | } | ||||
| } | } | ||||
| @@ -38,7 +38,7 @@ namespace Discord.Rpc | |||||
| _authorizeLock = new SemaphoreSlim(1, 1); | _authorizeLock = new SemaphoreSlim(1, 1); | ||||
| _rpcLogger = LogManager.CreateLogger("RPC"); | _rpcLogger = LogManager.CreateLogger("RPC"); | ||||
| _serializer = DiscordJsonSerializer.Global.CreateScope(); | |||||
| _serializer = DiscordRpcJsonSerializer.Global.CreateScope(); | |||||
| _serializer.Error += ex => | _serializer.Error += ex => | ||||
| { | { | ||||
| _rpcLogger.WarningAsync("Serializer Error", ex).GetAwaiter().GetResult(); | _rpcLogger.WarningAsync("Serializer Error", ex).GetAwaiter().GetResult(); | ||||
| @@ -0,0 +1,21 @@ | |||||
| using System; | |||||
| namespace Discord.Serialization.Json | |||||
| { | |||||
| public class DiscordRpcJsonSerializer : JsonSerializer | |||||
| { | |||||
| private static readonly Lazy<DiscordRpcJsonSerializer> _singleton = new Lazy<DiscordRpcJsonSerializer>(); | |||||
| 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); | |||||
| } | |||||
| } | |||||
| @@ -8,28 +8,46 @@ namespace Discord.Serialization | |||||
| { | { | ||||
| internal class ConverterCollection | internal class ConverterCollection | ||||
| { | { | ||||
| private class GenericConverterType | |||||
| { | |||||
| public Type Type; | |||||
| public Func<Type, Type> InnerTypeSelector; | |||||
| public GenericConverterType(Type type, Func<Type, Type> innerTypeSelector) | |||||
| { | |||||
| Type = type; | |||||
| InnerTypeSelector = innerTypeSelector ?? (x => x); | |||||
| } | |||||
| } | |||||
| private class ConverterTypeCollection | private class ConverterTypeCollection | ||||
| { | { | ||||
| public Type DefaultConverterType; | public Type DefaultConverterType; | ||||
| public List<(Func<TypeInfo, PropertyInfo, bool> Condition, Type ConverterType)> Conditionals = new List<(Func<TypeInfo, PropertyInfo, bool>, Type)>(); | |||||
| public List<(Func<TypeInfo, PropertyInfo, bool> Condition, Type ConverterType)> Conditionals | |||||
| = new List<(Func<TypeInfo, PropertyInfo, bool>, Type)>(); | |||||
| } | |||||
| private class GenericConverterTypeCollection | |||||
| { | |||||
| public GenericConverterType DefaultConverterType; | |||||
| public List<(Func<TypeInfo, PropertyInfo, bool> Condition, GenericConverterType ConverterType)> Conditionals | |||||
| = new List<(Func<TypeInfo, PropertyInfo, bool>, GenericConverterType)>(); | |||||
| } | } | ||||
| private static readonly TypeInfo _serializerType = typeof(Serializer).GetTypeInfo(); | private static readonly TypeInfo _serializerType = typeof(Serializer).GetTypeInfo(); | ||||
| private readonly Serializer _serializer; | |||||
| private readonly ConverterCollection _parent; | |||||
| private readonly ConcurrentDictionary<Type, object> _cache; | private readonly ConcurrentDictionary<Type, object> _cache; | ||||
| private readonly Dictionary<Type, ConverterTypeCollection> _types; | private readonly Dictionary<Type, ConverterTypeCollection> _types; | ||||
| private readonly Dictionary<Type, ConverterTypeCollection> _mappedGenericTypes; | |||||
| private readonly ConverterTypeCollection _genericTypes; | |||||
| private readonly Dictionary<Type, GenericConverterTypeCollection> _mappedGenericTypes; | |||||
| private readonly GenericConverterTypeCollection _genericTypes; | |||||
| private readonly ConcurrentDictionary<Type, ConcurrentDictionary<string, ISelectorGroup>> _selectorGroups; | private readonly ConcurrentDictionary<Type, ConcurrentDictionary<string, ISelectorGroup>> _selectorGroups; | ||||
| internal ConverterCollection(Serializer serializer) | |||||
| internal ConverterCollection(ConverterCollection parent = null) | |||||
| { | { | ||||
| _serializer = serializer; | |||||
| _parent = parent; | |||||
| _cache = new ConcurrentDictionary<Type, object>(); | _cache = new ConcurrentDictionary<Type, object>(); | ||||
| _types = new Dictionary<Type, ConverterTypeCollection>(); | _types = new Dictionary<Type, ConverterTypeCollection>(); | ||||
| _mappedGenericTypes = new Dictionary<Type, ConverterTypeCollection>(); | |||||
| _genericTypes = new ConverterTypeCollection(); | |||||
| _mappedGenericTypes = new Dictionary<Type, GenericConverterTypeCollection>(); | |||||
| _genericTypes = new GenericConverterTypeCollection(); | |||||
| _selectorGroups = new ConcurrentDictionary<Type, ConcurrentDictionary<string, ISelectorGroup>>(); | _selectorGroups = new ConcurrentDictionary<Type, ConcurrentDictionary<string, ISelectorGroup>>(); | ||||
| } | } | ||||
| @@ -46,63 +64,80 @@ namespace Discord.Serialization | |||||
| converters.Conditionals.Add((condition, converterType)); | converters.Conditionals.Add((condition, converterType)); | ||||
| } | } | ||||
| public void AddGeneric(Type openConverterType) | |||||
| public void AddGeneric(Type openConverterType, Func<Type, Type> innerTypeSelector = null) | |||||
| { | { | ||||
| if (openConverterType.IsConstructedGenericType) throw new InvalidOperationException($"{nameof(openConverterType)} must be an open generic"); | 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<TypeInfo, PropertyInfo, bool> condition) | |||||
| public void AddGeneric(Type openConverterType, Func<TypeInfo, PropertyInfo, bool> condition, Func<Type, Type> innerTypeSelector = null) | |||||
| { | { | ||||
| if (openConverterType.IsConstructedGenericType) throw new InvalidOperationException($"{nameof(openConverterType)} must be an open generic"); | 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<Type, Type> innerTypeSelector = null) | |||||
| { | { | ||||
| if (openType.IsConstructedGenericType) throw new InvalidOperationException($"{nameof(openType)} must be an open generic"); | 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 (openConverterType.IsConstructedGenericType) throw new InvalidOperationException($"{nameof(openConverterType)} must be an open generic"); | ||||
| if (!_mappedGenericTypes.TryGetValue(openType, out var converters)) | 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<TypeInfo, PropertyInfo, bool> condition) | |||||
| public void AddGeneric(Type openType, Type openConverterType, Func<TypeInfo, PropertyInfo, bool> condition, Func<Type, Type> innerTypeSelector = null) | |||||
| { | { | ||||
| if (openType.IsConstructedGenericType) throw new InvalidOperationException($"{nameof(openType)} must be an open generic"); | 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 (openConverterType.IsConstructedGenericType) throw new InvalidOperationException($"{nameof(openConverterType)} must be an open generic"); | ||||
| if (!_mappedGenericTypes.TryGetValue(openType, out var converters)) | 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 (propInfo == null) //Can only cache top-level due to attribute influences | ||||
| { | { | ||||
| if (_cache.TryGetValue(type, out var result)) | if (_cache.TryGetValue(type, out var result)) | ||||
| return 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(); | TypeInfo typeInfo = type.GetTypeInfo(); | ||||
| //Mapped generic converters (List<T> -> CollectionPropertyConverter<T>) | //Mapped generic converters (List<T> -> CollectionPropertyConverter<T>) | ||||
| if (typeInfo.IsGenericType) | 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); | var converterType = FindConverterType(type, _types, typeInfo, propInfo); | ||||
| if (converterType != null) | if (converterType != null) | ||||
| return Activator.CreateInstance(converterType); | |||||
| return BuildConverter(converterType, serializer); | |||||
| } | } | ||||
| //Generic converters (Model -> ObjectPropertyConverter<Model>) | //Generic converters (Model -> ObjectPropertyConverter<Model>) | ||||
| { | { | ||||
| var converterType = FindConverterType(_genericTypes, typeInfo, propInfo); | |||||
| var converterType = FindGenericConverterType(type, _genericTypes, typeInfo, propInfo); | |||||
| if (converterType != null) | 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<Type, ConverterTypeCollection> collection, TypeInfo typeInfo, PropertyInfo propInfo) | private Type FindConverterType(Type type, Dictionary<Type, ConverterTypeCollection> collection, TypeInfo typeInfo, PropertyInfo propInfo) | ||||
| @@ -150,6 +199,12 @@ namespace Discord.Serialization | |||||
| return FindConverterType(converters, typeInfo, propInfo); | return FindConverterType(converters, typeInfo, propInfo); | ||||
| return null; | return null; | ||||
| } | } | ||||
| private Type FindGenericConverterType(Type type, Type innerType, Dictionary<Type, GenericConverterTypeCollection> 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) | private Type FindConverterType(ConverterTypeCollection converters, TypeInfo typeInfo, PropertyInfo propInfo) | ||||
| { | { | ||||
| for (int i = 0; i < converters.Conditionals.Count; i++) | for (int i = 0; i < converters.Conditionals.Count; i++) | ||||
| @@ -161,17 +216,45 @@ namespace Discord.Serialization | |||||
| return converters.DefaultConverterType; | return converters.DefaultConverterType; | ||||
| return null; | 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) | 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)) | if (keyGroup.TryGetValue(groupKey, out var selectorGroup)) | ||||
| return selectorGroup; | return selectorGroup; | ||||
| return CreateSelectorGroup(keyType, keyGroup, groupKey); | return CreateSelectorGroup(keyType, keyGroup, groupKey); | ||||
| } | } | ||||
| private ISelectorGroup CreateSelectorGroup(Type keyType, ConcurrentDictionary<string, ISelectorGroup> keyGroup, string groupKey) | private ISelectorGroup CreateSelectorGroup(Type keyType, ConcurrentDictionary<string, ISelectorGroup> keyGroup, string groupKey) | ||||
| => keyGroup.GetOrAdd(groupKey, Activator.CreateInstance(typeof(SelectorGroup<>).MakeGenericType(keyType)) as ISelectorGroup); | => keyGroup.GetOrAdd(groupKey, Activator.CreateInstance(typeof(SelectorGroup<>).MakeGenericType(keyType)) as ISelectorGroup); | ||||
| private ConcurrentDictionary<string, ISelectorGroup> GetSelectorKeyGroup(Type keyType) | |||||
| private ConcurrentDictionary<string, ISelectorGroup> CreateSelectorKeyGroup(Type keyType) | |||||
| => _selectorGroups.GetOrAdd(keyType, _ => new ConcurrentDictionary<string, ISelectorGroup>()); | => _selectorGroups.GetOrAdd(keyType, _ => new ConcurrentDictionary<string, ISelectorGroup>()); | ||||
| } | } | ||||
| } | } | ||||
| @@ -1,37 +0,0 @@ | |||||
| using System.Collections.Generic; | |||||
| using System.Text.Json; | |||||
| namespace Discord.Serialization.Json.Converters | |||||
| { | |||||
| public class ListPropertyConverter<T> : IJsonPropertyConverter<List<T>> | |||||
| { | |||||
| private readonly IJsonPropertyConverter<T> _innerConverter; | |||||
| public ListPropertyConverter(IJsonPropertyConverter<T> innerConverter) | |||||
| { | |||||
| _innerConverter = innerConverter; | |||||
| } | |||||
| public List<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<T>(); | |||||
| 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<T> 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(); | |||||
| } | |||||
| } | |||||
| } | |||||
| @@ -0,0 +1,46 @@ | |||||
| using System.Collections.Generic; | |||||
| using System.Text.Json; | |||||
| namespace Discord.Serialization.Json.Converters | |||||
| { | |||||
| public class DictionaryPropertyConverter<TValue> : JsonPropertyConverter<Dictionary<string, TValue>> | |||||
| { | |||||
| private readonly JsonPropertyConverter<TValue> _valueConverter; | |||||
| public DictionaryPropertyConverter(JsonPropertyConverter<TValue> valueConverter) | |||||
| { | |||||
| _valueConverter = valueConverter; | |||||
| } | |||||
| public override Dictionary<string, TValue> 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<string, TValue>(); | |||||
| 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<string, TValue> 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(); | |||||
| } | |||||
| } | |||||
| } | |||||
| @@ -3,9 +3,9 @@ | |||||
| namespace Discord.Serialization.Json.Converters | namespace Discord.Serialization.Json.Converters | ||||
| { | { | ||||
| //TODO: Only supports cases where the key arrives first | //TODO: Only supports cases where the key arrives first | ||||
| public class DynamicPropertyConverter : IJsonPropertyConverter<object> | |||||
| public class DynamicPropertyConverter : JsonPropertyConverter<object> | |||||
| { | { | ||||
| 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<object> converter) | if (map.GetDynamicConverter(model, false) is IJsonPropertyReader<object> converter) | ||||
| return converter.Read(map, model, ref reader, isTopLevel); | 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 (value == null) | ||||
| { | { | ||||
| if (isTopLevel) | |||||
| writer.WriteAttributeNull(map.Key); | |||||
| if (key != null) | |||||
| writer.WriteAttributeNull(key); | |||||
| else | else | ||||
| writer.WriteNull(); | writer.WriteNull(); | ||||
| } | } | ||||
| else | else | ||||
| { | { | ||||
| var converter = map.GetDynamicConverter(model, true) as IJsonPropertyWriter<object>; | |||||
| converter.Write(map, model, ref writer, value, isTopLevel); | |||||
| var converter = (IJsonPropertyWriter)map.GetDynamicConverter(model, true); | |||||
| converter.Write(map, model, ref writer, value, key); | |||||
| } | } | ||||
| } | } | ||||
| } | } | ||||
| @@ -2,12 +2,12 @@ | |||||
| namespace Discord.Serialization.Json.Converters | namespace Discord.Serialization.Json.Converters | ||||
| { | { | ||||
| public class Int64EnumPropertyConverter<T> : IJsonPropertyConverter<T> | |||||
| public class Int64EnumPropertyConverter<T> : JsonPropertyConverter<T> | |||||
| where T : struct | where T : struct | ||||
| { | { | ||||
| private static readonly EnumMap<T> _map = EnumMap.For<T>(); | private static readonly EnumMap<T> _map = EnumMap.For<T>(); | ||||
| 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) | if (isTopLevel) | ||||
| reader.Read(); | reader.Read(); | ||||
| @@ -15,22 +15,22 @@ namespace Discord.Serialization.Json.Converters | |||||
| throw new SerializationException("Bad input, expected Number or String"); | throw new SerializationException("Bad input, expected Number or String"); | ||||
| return _map.FromInt64(reader.Value); | 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 | else | ||||
| writer.WriteValue(key); | |||||
| writer.WriteValue(intVal); | |||||
| } | } | ||||
| } | } | ||||
| public class UInt64EnumPropertyConverter<T> : IJsonPropertyConverter<T> | |||||
| public class UInt64EnumPropertyConverter<T> : JsonPropertyConverter<T> | |||||
| where T : struct | where T : struct | ||||
| { | { | ||||
| private static readonly EnumMap<T> _map = EnumMap.For<T>(); | private static readonly EnumMap<T> _map = EnumMap.For<T>(); | ||||
| 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) | if (isTopLevel) | ||||
| reader.Read(); | reader.Read(); | ||||
| @@ -38,22 +38,22 @@ namespace Discord.Serialization.Json.Converters | |||||
| throw new SerializationException("Bad input, expected Number or String"); | throw new SerializationException("Bad input, expected Number or String"); | ||||
| return _map.FromUInt64(reader.Value); | 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 | else | ||||
| writer.WriteValue(key); | |||||
| writer.WriteValue(uintVal); | |||||
| } | } | ||||
| } | } | ||||
| public class StringEnumPropertyConverter<T> : IJsonPropertyConverter<T> | |||||
| public class StringEnumPropertyConverter<T> : JsonPropertyConverter<T> | |||||
| where T : struct | where T : struct | ||||
| { | { | ||||
| private static readonly EnumMap<T> _map = EnumMap.For<T>(); | private static readonly EnumMap<T> _map = EnumMap.For<T>(); | ||||
| 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) | if (isTopLevel) | ||||
| reader.Read(); | reader.Read(); | ||||
| @@ -61,13 +61,13 @@ namespace Discord.Serialization.Json.Converters | |||||
| throw new SerializationException("Bad input, expected String"); | throw new SerializationException("Bad input, expected String"); | ||||
| return _map.FromKey(reader.Value); | 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 | else | ||||
| writer.WriteValue(key); | |||||
| writer.WriteValue(strVal); | |||||
| } | } | ||||
| } | } | ||||
| } | } | ||||
| @@ -0,0 +1,77 @@ | |||||
| using System.Collections.Generic; | |||||
| using System.Text.Json; | |||||
| namespace Discord.Serialization.Json.Converters | |||||
| { | |||||
| public class ArrayPropertyConverter<T> : JsonPropertyConverter<T[]> | |||||
| { | |||||
| private readonly JsonPropertyConverter<T> _innerConverter; | |||||
| public ArrayPropertyConverter(JsonPropertyConverter<T> 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<T>(); | |||||
| 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<T> : JsonPropertyConverter<List<T>> | |||||
| { | |||||
| private readonly JsonPropertyConverter<T> _innerConverter; | |||||
| public ListPropertyConverter(JsonPropertyConverter<T> innerConverter) | |||||
| { | |||||
| _innerConverter = innerConverter; | |||||
| } | |||||
| public override List<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<T>(); | |||||
| 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<T> 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(); | |||||
| } | |||||
| } | |||||
| } | |||||
| @@ -2,17 +2,17 @@ | |||||
| namespace Discord.Serialization.Json.Converters | namespace Discord.Serialization.Json.Converters | ||||
| { | { | ||||
| public class NullablePropertyConverter<T> : IJsonPropertyConverter<T?> | |||||
| public class NullablePropertyConverter<T> : JsonPropertyConverter<T?> | |||||
| where T : struct | where T : struct | ||||
| { | { | ||||
| private readonly IJsonPropertyConverter<T> _innerConverter; | |||||
| private readonly JsonPropertyConverter<T> _innerConverter; | |||||
| public NullablePropertyConverter(IJsonPropertyConverter<T> innerConverter) | |||||
| public NullablePropertyConverter(JsonPropertyConverter<T> innerConverter) | |||||
| { | { | ||||
| _innerConverter = 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) | if (isTopLevel) | ||||
| reader.Read(); | reader.Read(); | ||||
| @@ -21,14 +21,14 @@ namespace Discord.Serialization.Json.Converters | |||||
| return _innerConverter.Read(map, model, ref reader, false); | 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) | if (value.HasValue) | ||||
| _innerConverter.Write(map, model, ref writer, value.Value, isTopLevel); | |||||
| _innerConverter.Write(map, model, ref writer, value.Value, key); | |||||
| else | else | ||||
| { | { | ||||
| if (isTopLevel) | |||||
| writer.WriteAttributeNull(map.Key); | |||||
| if (key != null) | |||||
| writer.WriteAttributeNull(key); | |||||
| else | else | ||||
| writer.WriteNull(); | writer.WriteNull(); | ||||
| } | } | ||||
| @@ -2,7 +2,7 @@ | |||||
| namespace Discord.Serialization.Json.Converters | namespace Discord.Serialization.Json.Converters | ||||
| { | { | ||||
| public class ObjectPropertyConverter<T> : IJsonPropertyConverter<T> | |||||
| public class ObjectPropertyConverter<T> : JsonPropertyConverter<T> | |||||
| where T : class, new() | where T : class, new() | ||||
| { | { | ||||
| private readonly ModelMap<T> _map; | private readonly ModelMap<T> _map; | ||||
| @@ -12,19 +12,23 @@ namespace Discord.Serialization.Json.Converters | |||||
| _map = serializer.MapModel<T>(); | _map = serializer.MapModel<T>(); | ||||
| } | } | ||||
| 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(); | 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()) | while (reader.Read()) | ||||
| { | { | ||||
| if (reader.TokenType == JsonTokenType.EndObject) | if (reader.TokenType == JsonTokenType.EndObject) | ||||
| return subModel; | return subModel; | ||||
| if (reader.TokenType != JsonTokenType.PropertyName) | if (reader.TokenType != JsonTokenType.PropertyName) | ||||
| throw new SerializationException("Bad input, expected PropertyName"); | throw new SerializationException("Bad input, expected PropertyName"); | ||||
| if (_map.TryGetProperty(reader.Value, out var property)) | if (_map.TryGetProperty(reader.Value, out var property)) | ||||
| (property as IJsonPropertyMap<T>).Read(subModel, ref reader); | (property as IJsonPropertyMap<T>).Read(subModel, ref reader); | ||||
| else | else | ||||
| @@ -32,15 +36,25 @@ namespace Discord.Serialization.Json.Converters | |||||
| } | } | ||||
| throw new SerializationException("Bad input, expected EndObject"); | 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 | else | ||||
| writer.WriteObjectStart(); | |||||
| for (int i = 0; i < _map.Properties.Length; i++) | |||||
| (_map.Properties[i] as IJsonPropertyMap<T>).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<T>).Write(value, ref writer); | |||||
| writer.WriteObjectEnd(); | |||||
| } | |||||
| } | } | ||||
| } | } | ||||
| } | } | ||||
| @@ -3,9 +3,9 @@ using System.Text.Json; | |||||
| namespace Discord.Serialization.Json.Converters | namespace Discord.Serialization.Json.Converters | ||||
| { | { | ||||
| public class DateTimePropertyConverter : IJsonPropertyConverter<DateTime> | |||||
| public class DateTimePropertyConverter : JsonPropertyConverter<DateTime> | |||||
| { | { | ||||
| 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) | if (isTopLevel) | ||||
| reader.Read(); | reader.Read(); | ||||
| @@ -13,18 +13,18 @@ namespace Discord.Serialization.Json.Converters | |||||
| throw new SerializationException("Bad input, expected String"); | throw new SerializationException("Bad input, expected String"); | ||||
| return reader.ParseDateTime(); | 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 | else | ||||
| writer.WriteValue(value); | writer.WriteValue(value); | ||||
| } | } | ||||
| } | } | ||||
| public class DateTimeOffsetPropertyConverter : IJsonPropertyConverter<DateTimeOffset> | |||||
| public class DateTimeOffsetPropertyConverter : JsonPropertyConverter<DateTimeOffset> | |||||
| { | { | ||||
| 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) | if (isTopLevel) | ||||
| reader.Read(); | reader.Read(); | ||||
| @@ -32,10 +32,10 @@ namespace Discord.Serialization.Json.Converters | |||||
| throw new SerializationException("Bad input, expected String"); | throw new SerializationException("Bad input, expected String"); | ||||
| return reader.ParseDateTimeOffset(); | 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 | else | ||||
| writer.WriteValue(value); | writer.WriteValue(value); | ||||
| } | } | ||||
| @@ -2,9 +2,9 @@ | |||||
| namespace Discord.Serialization.Json.Converters | namespace Discord.Serialization.Json.Converters | ||||
| { | { | ||||
| public class SinglePropertyConverter : IJsonPropertyConverter<float> | |||||
| public class SinglePropertyConverter : JsonPropertyConverter<float> | |||||
| { | { | ||||
| 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) | if (isTopLevel) | ||||
| reader.Read(); | reader.Read(); | ||||
| @@ -12,18 +12,18 @@ namespace Discord.Serialization.Json.Converters | |||||
| throw new SerializationException("Bad input, expected Number or String"); | throw new SerializationException("Bad input, expected Number or String"); | ||||
| return reader.ParseSingle(); | 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 | else | ||||
| writer.WriteValue(value.ToString()); | writer.WriteValue(value.ToString()); | ||||
| } | } | ||||
| } | } | ||||
| public class DoublePropertyConverter : IJsonPropertyConverter<double> | |||||
| public class DoublePropertyConverter : JsonPropertyConverter<double> | |||||
| { | { | ||||
| 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) | if (isTopLevel) | ||||
| reader.Read(); | reader.Read(); | ||||
| @@ -31,18 +31,18 @@ namespace Discord.Serialization.Json.Converters | |||||
| throw new SerializationException("Bad input, expected Number or String"); | throw new SerializationException("Bad input, expected Number or String"); | ||||
| return reader.ParseDouble(); | 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 | else | ||||
| writer.WriteValue(value.ToString()); | writer.WriteValue(value.ToString()); | ||||
| } | } | ||||
| } | } | ||||
| internal class DecimalPropertyConverter : IJsonPropertyConverter<decimal> | |||||
| internal class DecimalPropertyConverter : JsonPropertyConverter<decimal> | |||||
| { | { | ||||
| 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) | if (isTopLevel) | ||||
| reader.Read(); | reader.Read(); | ||||
| @@ -50,10 +50,10 @@ namespace Discord.Serialization.Json.Converters | |||||
| throw new SerializationException("Bad input, expected Number or String"); | throw new SerializationException("Bad input, expected Number or String"); | ||||
| return reader.ParseDecimal(); | 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 | else | ||||
| writer.WriteValue(value.ToString()); | writer.WriteValue(value.ToString()); | ||||
| } | } | ||||
| @@ -3,9 +3,9 @@ using System.Text.Json; | |||||
| namespace Discord.Serialization.Json.Converters | namespace Discord.Serialization.Json.Converters | ||||
| { | { | ||||
| public class BooleanPropertyConverter : IJsonPropertyConverter<bool> | |||||
| public class BooleanPropertyConverter : JsonPropertyConverter<bool> | |||||
| { | { | ||||
| 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) | if (isTopLevel) | ||||
| reader.Read(); | reader.Read(); | ||||
| @@ -16,18 +16,18 @@ namespace Discord.Serialization.Json.Converters | |||||
| default: throw new SerializationException("Bad input, expected False or True"); | 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 | else | ||||
| writer.WriteValue(value); | writer.WriteValue(value); | ||||
| } | } | ||||
| } | } | ||||
| public class GuidPropertyConverter : IJsonPropertyConverter<Guid> | |||||
| public class GuidPropertyConverter : JsonPropertyConverter<Guid> | |||||
| { | { | ||||
| 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) | if (isTopLevel) | ||||
| reader.Read(); | reader.Read(); | ||||
| @@ -35,10 +35,10 @@ namespace Discord.Serialization.Json.Converters | |||||
| throw new SerializationException("Bad input, expected String"); | throw new SerializationException("Bad input, expected String"); | ||||
| return reader.ParseGuid(); | 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 | else | ||||
| writer.WriteValue(value.ToString()); | writer.WriteValue(value.ToString()); | ||||
| } | } | ||||
| @@ -2,9 +2,9 @@ | |||||
| namespace Discord.Serialization.Json.Converters | namespace Discord.Serialization.Json.Converters | ||||
| { | { | ||||
| public class Int8PropertyConverter : IJsonPropertyConverter<sbyte> | |||||
| public class Int8PropertyConverter : JsonPropertyConverter<sbyte> | |||||
| { | { | ||||
| 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) | if (isTopLevel) | ||||
| reader.Read(); | reader.Read(); | ||||
| @@ -12,18 +12,18 @@ namespace Discord.Serialization.Json.Converters | |||||
| throw new SerializationException("Bad input, expected Number or String"); | throw new SerializationException("Bad input, expected Number or String"); | ||||
| return reader.ParseInt8(); | 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 | else | ||||
| writer.WriteValue(value); | writer.WriteValue(value); | ||||
| } | } | ||||
| } | } | ||||
| public class Int16PropertyConverter : IJsonPropertyConverter<short> | |||||
| public class Int16PropertyConverter : JsonPropertyConverter<short> | |||||
| { | { | ||||
| 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) | if (isTopLevel) | ||||
| reader.Read(); | reader.Read(); | ||||
| @@ -31,18 +31,18 @@ namespace Discord.Serialization.Json.Converters | |||||
| throw new SerializationException("Bad input, expected Number or String"); | throw new SerializationException("Bad input, expected Number or String"); | ||||
| return reader.ParseInt16(); | 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 | else | ||||
| writer.WriteValue(value); | writer.WriteValue(value); | ||||
| } | } | ||||
| } | } | ||||
| public class Int32PropertyConverter : IJsonPropertyConverter<int> | |||||
| public class Int32PropertyConverter : JsonPropertyConverter<int> | |||||
| { | { | ||||
| 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) | if (isTopLevel) | ||||
| reader.Read(); | reader.Read(); | ||||
| @@ -50,18 +50,18 @@ namespace Discord.Serialization.Json.Converters | |||||
| throw new SerializationException("Bad input, expected Number or String"); | throw new SerializationException("Bad input, expected Number or String"); | ||||
| return reader.ParseInt32(); | 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 | else | ||||
| writer.WriteValue(value); | writer.WriteValue(value); | ||||
| } | } | ||||
| } | } | ||||
| public class Int64PropertyConverter : IJsonPropertyConverter<long> | |||||
| public class Int64PropertyConverter : JsonPropertyConverter<long> | |||||
| { | { | ||||
| 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) | if (isTopLevel) | ||||
| reader.Read(); | reader.Read(); | ||||
| @@ -69,10 +69,10 @@ namespace Discord.Serialization.Json.Converters | |||||
| throw new SerializationException("Bad input, expected Number or String"); | throw new SerializationException("Bad input, expected Number or String"); | ||||
| return reader.ParseInt64(); | 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 | else | ||||
| writer.WriteValue(value.ToString()); | writer.WriteValue(value.ToString()); | ||||
| } | } | ||||
| @@ -2,9 +2,9 @@ | |||||
| namespace Discord.Serialization.Json.Converters | namespace Discord.Serialization.Json.Converters | ||||
| { | { | ||||
| /*public class CharPropertyConverter : IJsonPropertyConverter<char> | |||||
| /*public class CharPropertyConverter : JsonPropertyConverter<char> | |||||
| { | { | ||||
| 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) | if (isTopLevel) | ||||
| reader.Read(); | reader.Read(); | ||||
| @@ -12,18 +12,18 @@ namespace Discord.Serialization.Json.Converters | |||||
| throw new SerializationException("Bad input, expected String"); | throw new SerializationException("Bad input, expected String"); | ||||
| return reader.ParseChar(); | 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 | else | ||||
| writer.WriteValue(value.ToString()); | |||||
| writer.WriteValue(value); | |||||
| } | } | ||||
| }*/ | }*/ | ||||
| public class StringPropertyConverter : IJsonPropertyConverter<string> | |||||
| public class StringPropertyConverter : JsonPropertyConverter<string> | |||||
| { | { | ||||
| 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) | if (isTopLevel) | ||||
| reader.Read(); | reader.Read(); | ||||
| @@ -33,10 +33,10 @@ namespace Discord.Serialization.Json.Converters | |||||
| throw new SerializationException("Bad input, expected String"); | throw new SerializationException("Bad input, expected String"); | ||||
| return reader.ParseString(); | 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 | else | ||||
| writer.WriteValue(value); | writer.WriteValue(value); | ||||
| } | } | ||||
| @@ -2,9 +2,9 @@ | |||||
| namespace Discord.Serialization.Json.Converters | namespace Discord.Serialization.Json.Converters | ||||
| { | { | ||||
| public class UInt8PropertyConverter : IJsonPropertyConverter<byte> | |||||
| public class UInt8PropertyConverter : JsonPropertyConverter<byte> | |||||
| { | { | ||||
| 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) | if (isTopLevel) | ||||
| reader.Read(); | reader.Read(); | ||||
| @@ -12,18 +12,18 @@ namespace Discord.Serialization.Json.Converters | |||||
| throw new SerializationException("Bad input, expected Number or String"); | throw new SerializationException("Bad input, expected Number or String"); | ||||
| return reader.ParseUInt8(); | 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 | else | ||||
| writer.WriteValue(value); | writer.WriteValue(value); | ||||
| } | } | ||||
| } | } | ||||
| public class UInt16PropertyConverter : IJsonPropertyConverter<ushort> | |||||
| public class UInt16PropertyConverter : JsonPropertyConverter<ushort> | |||||
| { | { | ||||
| 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) | if (isTopLevel) | ||||
| reader.Read(); | reader.Read(); | ||||
| @@ -31,18 +31,18 @@ namespace Discord.Serialization.Json.Converters | |||||
| throw new SerializationException("Bad input, expected Number or String"); | throw new SerializationException("Bad input, expected Number or String"); | ||||
| return reader.ParseUInt16(); | 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 | else | ||||
| writer.WriteValue(value); | writer.WriteValue(value); | ||||
| } | } | ||||
| } | } | ||||
| public class UInt32PropertyConverter : IJsonPropertyConverter<uint> | |||||
| public class UInt32PropertyConverter : JsonPropertyConverter<uint> | |||||
| { | { | ||||
| 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) | if (isTopLevel) | ||||
| reader.Read(); | reader.Read(); | ||||
| @@ -50,18 +50,18 @@ namespace Discord.Serialization.Json.Converters | |||||
| throw new SerializationException("Bad input, expected Number or String"); | throw new SerializationException("Bad input, expected Number or String"); | ||||
| return reader.ParseUInt32(); | 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 | else | ||||
| writer.WriteValue(value); | writer.WriteValue(value); | ||||
| } | } | ||||
| } | } | ||||
| public class UInt64PropertyConverter : IJsonPropertyConverter<ulong> | |||||
| public class UInt64PropertyConverter : JsonPropertyConverter<ulong> | |||||
| { | { | ||||
| 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) | if (isTopLevel) | ||||
| reader.Read(); | reader.Read(); | ||||
| @@ -69,10 +69,10 @@ namespace Discord.Serialization.Json.Converters | |||||
| throw new SerializationException("Bad input, expected Number or String"); | throw new SerializationException("Bad input, expected Number or String"); | ||||
| return reader.ParseUInt64(); | 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 | else | ||||
| writer.WriteValue(value.ToString()); | writer.WriteValue(value.ToString()); | ||||
| } | } | ||||
| @@ -0,0 +1,46 @@ | |||||
| using System.Text.Json; | |||||
| namespace Discord.Serialization.Json.Converters | |||||
| { | |||||
| /*public class StructPropertyConverter<T> : JsonPropertyConverter<T> | |||||
| where T : struct, new() | |||||
| { | |||||
| private readonly ModelMap<T> _map; | |||||
| public StructPropertyConverter(Serializer serializer) | |||||
| { | |||||
| _map = serializer.MapModel<T>(); | |||||
| } | |||||
| 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<T>).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<T>).Write(value, ref writer); | |||||
| writer.WriteObjectEnd(); | |||||
| } | |||||
| }*/ | |||||
| } | |||||
| @@ -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<DefaultJsonSerializer> _singleton = new Lazy<DefaultJsonSerializer>(); | |||||
| public static DefaultJsonSerializer Global => _singleton.Value; | |||||
| public DefaultJsonSerializer() | |||||
| : this((JsonSerializer)null) { } | |||||
| public DefaultJsonSerializer(JsonSerializer parent) | |||||
| : base(parent) | |||||
| { | |||||
| AddConverter<sbyte, Converters.Int8PropertyConverter>(); | |||||
| AddConverter<short, Converters.Int16PropertyConverter>(); | |||||
| AddConverter<int, Converters.Int32PropertyConverter>(); | |||||
| AddConverter<long, Converters.Int64PropertyConverter>(); | |||||
| AddConverter<byte, Converters.UInt8PropertyConverter>(); | |||||
| AddConverter<ushort, Converters.UInt16PropertyConverter>(); | |||||
| AddConverter<uint, Converters.UInt32PropertyConverter>(); | |||||
| AddConverter<ulong, Converters.UInt64PropertyConverter>(); | |||||
| AddConverter<float, Converters.SinglePropertyConverter>(); | |||||
| AddConverter<double, Converters.DoublePropertyConverter>(); | |||||
| AddConverter<decimal, Converters.DecimalPropertyConverter>(); | |||||
| //AddConverter<char, Converters.CharPropertyConverter>(); //TODO: char.Parse does not support Json.Net's serialization | |||||
| AddConverter<string, Converters.StringPropertyConverter>(); | |||||
| AddConverter<DateTime, Converters.DateTimePropertyConverter>(); | |||||
| AddConverter<DateTimeOffset, Converters.DateTimeOffsetPropertyConverter>(); | |||||
| AddConverter<bool, Converters.BooleanPropertyConverter>(); | |||||
| AddConverter<Guid, Converters.GuidPropertyConverter>(); | |||||
| AddConverter<object, Converters.DynamicPropertyConverter>( | |||||
| (type, prop) => prop.GetCustomAttributes<ModelSelectorAttribute>().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<ModelStringEnumAttribute>() != 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); | |||||
| } | |||||
| } | |||||
| @@ -2,7 +2,14 @@ | |||||
| namespace Discord.Serialization.Json | namespace Discord.Serialization.Json | ||||
| { | { | ||||
| public interface IJsonPropertyConverter<T> : IJsonPropertyReader<T>, IJsonPropertyWriter<T> { } | |||||
| public abstract class JsonPropertyConverter<T> : IJsonPropertyReader<T>, IJsonPropertyWriter<T>, 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<out T> | public interface IJsonPropertyReader<out T> | ||||
| { | { | ||||
| @@ -10,6 +17,10 @@ namespace Discord.Serialization.Json | |||||
| } | } | ||||
| public interface IJsonPropertyWriter<in T> | public interface IJsonPropertyWriter<in T> | ||||
| { | { | ||||
| 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); | |||||
| } | } | ||||
| } | } | ||||
| @@ -15,11 +15,11 @@ namespace Discord.Serialization.Json | |||||
| internal class JsonPropertyMap<TModel, TValue> : PropertyMap<TModel, TValue>, IJsonPropertyMap<TModel> | internal class JsonPropertyMap<TModel, TValue> : PropertyMap<TModel, TValue>, IJsonPropertyMap<TModel> | ||||
| { | { | ||||
| private readonly IJsonPropertyConverter<TValue> _converter; | |||||
| private readonly JsonPropertyConverter<TValue> _converter; | |||||
| private readonly Func<TModel, TValue> _getFunc; | private readonly Func<TModel, TValue> _getFunc; | ||||
| private readonly Action<TModel, TValue> _setFunc; | private readonly Action<TModel, TValue> _setFunc; | ||||
| public JsonPropertyMap(Serializer serializer, PropertyInfo propInfo, IJsonPropertyConverter<TValue> converter) | |||||
| public JsonPropertyMap(Serializer serializer, PropertyInfo propInfo, JsonPropertyConverter<TValue> converter) | |||||
| : base(serializer, propInfo) | : base(serializer, propInfo) | ||||
| { | { | ||||
| _converter = converter; | _converter = converter; | ||||
| @@ -28,17 +28,17 @@ namespace Discord.Serialization.Json | |||||
| _setFunc = propInfo.SetMethod.CreateDelegate(typeof(Action<TModel, TValue>)) as Action<TModel, TValue>; | _setFunc = propInfo.SetMethod.CreateDelegate(typeof(Action<TModel, TValue>)) as Action<TModel, TValue>; | ||||
| } | } | ||||
| 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) | public void Write(TModel model, ref JsonWriter writer) | ||||
| { | { | ||||
| var value = _getFunc(model); | var value = _getFunc(model); | ||||
| if (value == null && ExcludeNull) | if (value == null && ExcludeNull) | ||||
| return; | 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); | |||||
| } | } | ||||
| } | } | ||||
| } | } | ||||
| @@ -1,6 +1,4 @@ | |||||
| using System; | using System; | ||||
| using System.Collections.Generic; | |||||
| using System.Linq; | |||||
| using System.Reflection; | using System.Reflection; | ||||
| using System.Text; | using System.Text; | ||||
| using System.Text.Formatting; | using System.Text.Formatting; | ||||
| @@ -8,64 +6,20 @@ using System.Text.Json; | |||||
| namespace Discord.Serialization.Json | namespace Discord.Serialization.Json | ||||
| { | { | ||||
| public class JsonSerializer : Serializer | |||||
| public abstract class JsonSerializer : Serializer | |||||
| { | { | ||||
| private static readonly Lazy<JsonSerializer> _singleton = new Lazy<JsonSerializer>(); | |||||
| public static JsonSerializer Global => _singleton.Value; | |||||
| public JsonSerializer() | |||||
| { | |||||
| AddConverter<sbyte, Converters.Int8PropertyConverter>(); | |||||
| AddConverter<short, Converters.Int16PropertyConverter>(); | |||||
| AddConverter<int, Converters.Int32PropertyConverter>(); | |||||
| AddConverter<long, Converters.Int64PropertyConverter>(); | |||||
| AddConverter<byte, Converters.UInt8PropertyConverter>(); | |||||
| AddConverter<ushort, Converters.UInt16PropertyConverter>(); | |||||
| AddConverter<uint, Converters.UInt32PropertyConverter>(); | |||||
| AddConverter<ulong, Converters.UInt64PropertyConverter>(); | |||||
| AddConverter<float, Converters.SinglePropertyConverter>(); | |||||
| AddConverter<double, Converters.DoublePropertyConverter>(); | |||||
| AddConverter<decimal, Converters.DecimalPropertyConverter>(); | |||||
| //AddConverter<char, Converters.CharPropertyConverter>(); //TODO: char.Parse does not support Json.Net's serialization | |||||
| AddConverter<string, Converters.StringPropertyConverter>(); | |||||
| AddConverter<DateTime, Converters.DateTimePropertyConverter>(); | |||||
| AddConverter<DateTimeOffset, Converters.DateTimeOffsetPropertyConverter>(); | |||||
| AddConverter<bool, Converters.BooleanPropertyConverter>(); | |||||
| AddConverter<Guid, Converters.GuidPropertyConverter>(); | |||||
| AddConverter<object, Converters.DynamicPropertyConverter>( | |||||
| (type, prop) => prop.GetCustomAttributes<ModelSelectorAttribute>().Any()); | |||||
| AddGenericConverter(typeof(List<>), typeof(Converters.ListPropertyConverter<>)); | |||||
| AddGenericConverter(typeof(Nullable<>), typeof(Converters.NullablePropertyConverter<>)); | |||||
| AddGenericConverter(typeof(Converters.StringEnumPropertyConverter<>), | |||||
| (type, prop) => type.IsEnum && prop.GetCustomAttribute<ModelStringEnumAttribute>() != 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) { } | protected JsonSerializer(JsonSerializer parent) : base(parent) { } | ||||
| public JsonSerializer CreateScope() => new JsonSerializer(this); | |||||
| public void AddConverter<TValue, TConverter>() | public void AddConverter<TValue, TConverter>() | ||||
| where TConverter : class, IJsonPropertyConverter<TValue> | |||||
| where TConverter : JsonPropertyConverter<TValue> | |||||
| => AddConverter(typeof(TValue), typeof(TConverter)); | => AddConverter(typeof(TValue), typeof(TConverter)); | ||||
| public void AddConverter<TValue, TConverter>(Func<TypeInfo, PropertyInfo, bool> condition) | public void AddConverter<TValue, TConverter>(Func<TypeInfo, PropertyInfo, bool> condition) | ||||
| where TConverter : class, IJsonPropertyConverter<TValue> | |||||
| where TConverter : JsonPropertyConverter<TValue> | |||||
| => AddConverter(typeof(TValue), typeof(TConverter), condition); | => AddConverter(typeof(TValue), typeof(TConverter), condition); | ||||
| protected override PropertyMap CreatePropertyMap<TModel, TValue>(PropertyInfo propInfo) | protected override PropertyMap CreatePropertyMap<TModel, TValue>(PropertyInfo propInfo) | ||||
| { | { | ||||
| var converter = (IJsonPropertyConverter<TValue>)GetConverter(typeof(TValue), propInfo); | |||||
| var converter = (JsonPropertyConverter<TValue>)GetConverter(typeof(TValue), propInfo); | |||||
| return new JsonPropertyMap<TModel, TValue>(this, propInfo, converter); | return new JsonPropertyMap<TModel, TValue>(this, propInfo, converter); | ||||
| } | } | ||||
| @@ -74,25 +28,14 @@ namespace Discord.Serialization.Json | |||||
| var reader = new JsonReader(data.Span, SymbolTable.InvariantUtf8); | var reader = new JsonReader(data.Span, SymbolTable.InvariantUtf8); | ||||
| if (!reader.Read()) | if (!reader.Read()) | ||||
| return default; | return default; | ||||
| var converter = GetConverter(typeof(TModel)) as IJsonPropertyConverter<TModel>; | |||||
| var converter = GetConverter(typeof(TModel)) as JsonPropertyConverter<TModel>; | |||||
| return converter.Read(null, null, ref reader, false); | return converter.Read(null, null, ref reader, false); | ||||
| } | } | ||||
| public override void Write<TModel>(ArrayFormatter stream, TModel model) | public override void Write<TModel>(ArrayFormatter stream, TModel model) | ||||
| { | { | ||||
| var writer = new JsonWriter(stream); | var writer = new JsonWriter(stream); | ||||
| var converter = GetConverter(typeof(TModel)) as IJsonPropertyConverter<TModel>; | |||||
| converter.Write(null, null, ref writer, model, false); | |||||
| var converter = GetConverter(typeof(TModel)) as JsonPropertyConverter<TModel>; | |||||
| 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); | |||||
| } | } | ||||
| } | } | ||||
| @@ -51,7 +51,7 @@ namespace Discord.Serialization | |||||
| private TKey GetSelectorKey<TKey>(object model) | private TKey GetSelectorKey<TKey>(object model) | ||||
| => (_getSelectorFunc as Func<TModel, TKey>)((TModel)model); | => (_getSelectorFunc as Func<TModel, TKey>)((TModel)model); | ||||
| public object GetDynamicConverter(object model) | public object GetDynamicConverter(object model) | ||||
| => _group.GetDynamicConverter(_getWrappedSelectorFunc, model); | |||||
| => _group?.GetDynamicConverter(_getWrappedSelectorFunc, model); | |||||
| } | } | ||||
| private readonly Delegate _getSelectorFunc, _getWrappedSelectorFunc; | private readonly Delegate _getSelectorFunc, _getWrappedSelectorFunc; | ||||
| @@ -73,7 +73,7 @@ namespace Discord.Serialization | |||||
| return new Selector(prop, selectorGroup, selectorFunc); | return new Selector(prop, selectorGroup, selectorFunc); | ||||
| }) | }) | ||||
| .ToList(); | |||||
| .ToList(); | |||||
| } | } | ||||
| public override object GetDynamicConverter(object model, bool throwOnMissing) | public override object GetDynamicConverter(object model, bool throwOnMissing) | ||||
| @@ -13,65 +13,40 @@ namespace Discord.Serialization | |||||
| private static readonly MethodInfo _createPropertyMapMethod | private static readonly MethodInfo _createPropertyMapMethod | ||||
| = typeof(Serializer).GetTypeInfo().GetDeclaredMethod(nameof(CreatePropertyMap)); | = typeof(Serializer).GetTypeInfo().GetDeclaredMethod(nameof(CreatePropertyMap)); | ||||
| private readonly ConcurrentDictionary<Type, object> _maps; | private readonly ConcurrentDictionary<Type, object> _maps; | ||||
| private readonly ConverterCollection _converters; | private readonly ConverterCollection _converters; | ||||
| public bool IsScoped { get; } | |||||
| protected Serializer() | protected Serializer() | ||||
| { | |||||
| _maps = new ConcurrentDictionary<Type, object>(); | |||||
| _converters = new ConverterCollection(this); | |||||
| IsScoped = false; | |||||
| } | |||||
| : this(null) { } | |||||
| protected Serializer(Serializer parent) | protected Serializer(Serializer parent) | ||||
| { | { | ||||
| _maps = parent._maps; | |||||
| _converters = parent._converters; | |||||
| IsScoped = true; | |||||
| _maps = new ConcurrentDictionary<Type, object>(); | |||||
| _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) | public void AddConverter(Type valueType, Type converterType) | ||||
| { | |||||
| CheckScoped(); | |||||
| _converters.Add(valueType, converterType); | |||||
| } | |||||
| => _converters.Add(valueType, converterType); | |||||
| public void AddConverter(Type valueType, Type converterType, Func<TypeInfo, PropertyInfo, bool> condition) | public void AddConverter(Type valueType, Type converterType, Func<TypeInfo, PropertyInfo, bool> 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<TypeInfo, PropertyInfo, bool> 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<TypeInfo, PropertyInfo, bool> condition) | |||||
| { | |||||
| CheckScoped(); | |||||
| _converters.AddGeneric(valueType, converterType, condition); | |||||
| } | |||||
| public void AddGenericConverter(Type converterType, Func<Type, Type> typeSelector = null) | |||||
| => _converters.AddGeneric(converterType, typeSelector); | |||||
| public void AddGenericConverter(Type converterType, Func<TypeInfo, PropertyInfo, bool> condition, Func<Type, Type> typeSelector = null) | |||||
| => _converters.AddGeneric(converterType, condition, typeSelector); | |||||
| public void AddGenericConverter(Type valueType, Type converterType, Func<Type, Type> typeSelector = null) | |||||
| => _converters.AddGeneric(valueType, converterType, typeSelector); | |||||
| public void AddGenericConverter(Type valueType, Type converterType, Func<TypeInfo, PropertyInfo, bool> condition, Func<Type, Type> typeSelector = null) | |||||
| => _converters.AddGeneric(valueType, converterType, condition, typeSelector); | |||||
| public void AddSelectorConverter(string groupKey, Type keyType, object keyValue, Type converterType) | 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<TKey, TConverter>(string groupKey, TKey keyValue) | |||||
| => _converters.AddSelector(this, groupKey, typeof(TKey), keyValue, typeof(TConverter)); | |||||
| public ISelectorGroup GetSelectorGroup(Type keyType, string groupKey) | public ISelectorGroup GetSelectorGroup(Type keyType, string groupKey) | ||||
| => _converters.GetSelectorGroup(keyType, groupKey); | => _converters.GetSelectorGroup(keyType, groupKey); | ||||
| @@ -104,11 +79,5 @@ namespace Discord.Serialization | |||||
| public abstract TModel Read<TModel>(ReadOnlyBuffer<byte> data); | public abstract TModel Read<TModel>(ReadOnlyBuffer<byte> data); | ||||
| public abstract void Write<TModel>(ArrayFormatter stream, TModel model); | public abstract void Write<TModel>(ArrayFormatter stream, TModel model); | ||||
| private void CheckScoped() | |||||
| { | |||||
| if (IsScoped) | |||||
| throw new InvalidOperationException("Scoped serializers are read-only"); | |||||
| } | |||||
| } | } | ||||
| } | } | ||||
| @@ -314,7 +314,7 @@ namespace System.Text.Json | |||||
| } | } | ||||
| [MethodImpl(MethodImplOptions.AggressiveInlining)] | [MethodImpl(MethodImplOptions.AggressiveInlining)] | ||||
| internal void WriteStartAttribute(string name) | |||||
| private void WriteStartAttribute(string name) | |||||
| { | { | ||||
| WriteItemSeperator(); | WriteItemSeperator(); | ||||
| _firstItem = false; | _firstItem = false; | ||||
| @@ -9,7 +9,7 @@ namespace Discord.API.Gateway | |||||
| [ModelProperty("token")] | [ModelProperty("token")] | ||||
| public string Token { get; set; } | public string Token { get; set; } | ||||
| [ModelProperty("properties")] | [ModelProperty("properties")] | ||||
| public IDictionary<string, string> Properties { get; set; } | |||||
| public Dictionary<string, string> Properties { get; set; } | |||||
| [ModelProperty("large_threshold")] | [ModelProperty("large_threshold")] | ||||
| public int LargeThreshold { get; set; } | public int LargeThreshold { get; set; } | ||||
| [ModelProperty("compress")] | [ModelProperty("compress")] | ||||
| @@ -9,6 +9,6 @@ namespace Discord.API.Gateway | |||||
| [ModelProperty("channel_id")] | [ModelProperty("channel_id")] | ||||
| public ulong ChannelId { get; set; } | public ulong ChannelId { get; set; } | ||||
| [ModelProperty("ids")] | [ModelProperty("ids")] | ||||
| public IEnumerable<ulong> Ids { get; set; } | |||||
| public ulong[] Ids { get; set; } | |||||
| } | } | ||||
| } | } | ||||
| @@ -12,6 +12,6 @@ namespace Discord.API.Gateway | |||||
| public int Limit { get; set; } | public int Limit { get; set; } | ||||
| [ModelProperty("guild_id")] | [ModelProperty("guild_id")] | ||||
| public IEnumerable<ulong> GuildIds { get; set; } | |||||
| public ulong[] GuildIds { get; set; } | |||||
| } | } | ||||
| } | } | ||||
| @@ -67,7 +67,7 @@ namespace Discord.Audio | |||||
| ChannelId = channelId; | ChannelId = channelId; | ||||
| _audioLogger = Discord.LogManager.CreateLogger($"Audio #{clientId}"); | _audioLogger = Discord.LogManager.CreateLogger($"Audio #{clientId}"); | ||||
| _serializer = DiscordJsonSerializer.Global.CreateScope(); | |||||
| _serializer = DiscordVoiceJsonSerializer.Global.CreateScope(); | |||||
| _serializer.Error += ex => | _serializer.Error += ex => | ||||
| { | { | ||||
| _audioLogger.WarningAsync("Serializer Error", ex).GetAwaiter().GetResult(); | _audioLogger.WarningAsync("Serializer Error", ex).GetAwaiter().GetResult(); | ||||
| @@ -128,7 +128,7 @@ namespace Discord.Audio | |||||
| await ApiClient.ConnectAsync("wss://" + _url).ConfigureAwait(false); | await ApiClient.ConnectAsync("wss://" + _url).ConfigureAwait(false); | ||||
| await _audioLogger.DebugAsync("Listening on port " + ApiClient.UdpPort).ConfigureAwait(false); | await _audioLogger.DebugAsync("Listening on port " + ApiClient.UdpPort).ConfigureAwait(false); | ||||
| await _audioLogger.DebugAsync("Sending Identity").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 | //Wait for READY | ||||
| await _connection.WaitAsync().ConfigureAwait(false); | await _connection.WaitAsync().ConfigureAwait(false); | ||||
| @@ -55,7 +55,7 @@ namespace Discord.WebSocket | |||||
| _baseConfig = config; | _baseConfig = config; | ||||
| _connectionGroupLock = new SemaphoreSlim(1, 1); | _connectionGroupLock = new SemaphoreSlim(1, 1); | ||||
| _serializer = DiscordJsonSerializer.Global.CreateScope(); | |||||
| _serializer = DiscordSocketJsonSerializer.Global.CreateScope(); | |||||
| _serializer.Error += ex => | _serializer.Error += ex => | ||||
| { | { | ||||
| _restLogger.WarningAsync("Serializer Error", ex).GetAwaiter().GetResult(); | _restLogger.WarningAsync("Serializer Error", ex).GetAwaiter().GetResult(); | ||||
| @@ -10,6 +10,7 @@ using System; | |||||
| using System.Collections.Generic; | using System.Collections.Generic; | ||||
| using System.IO; | using System.IO; | ||||
| using System.IO.Compression; | using System.IO.Compression; | ||||
| using System.Linq; | |||||
| using System.Text; | using System.Text; | ||||
| using System.Text.Formatting; | using System.Text.Formatting; | ||||
| using System.Threading; | using System.Threading; | ||||
| @@ -183,8 +184,9 @@ namespace Discord.API | |||||
| } | } | ||||
| finally | finally | ||||
| { | { | ||||
| data.Clear(); | |||||
| _formatters.Enqueue(data); | _formatters.Enqueue(data); | ||||
| } | |||||
| } | |||||
| } | } | ||||
| //Gateway | //Gateway | ||||
| @@ -236,31 +238,37 @@ namespace Discord.API | |||||
| public async Task SendStatusUpdateAsync(UserStatus status, bool isAFK, long? since, Game game, RequestOptions options = null) | public async Task SendStatusUpdateAsync(UserStatus status, bool isAFK, long? since, Game game, RequestOptions options = null) | ||||
| { | { | ||||
| options = RequestOptions.CreateOrClone(options); | options = RequestOptions.CreateOrClone(options); | ||||
| var args = new StatusUpdateParams | |||||
| var msg = new StatusUpdateParams | |||||
| { | { | ||||
| Status = status, | Status = status, | ||||
| IdleSince = since, | IdleSince = since, | ||||
| IsAFK = isAFK, | IsAFK = isAFK, | ||||
| Game = game | 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<ulong> guildIds, RequestOptions options = null) | public async Task SendRequestMembersAsync(IEnumerable<ulong> guildIds, RequestOptions options = null) | ||||
| { | { | ||||
| options = RequestOptions.CreateOrClone(options); | 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) | public async Task SendVoiceStateUpdateAsync(ulong guildId, ulong? channelId, bool selfDeaf, bool selfMute, RequestOptions options = null) | ||||
| { | { | ||||
| options = RequestOptions.CreateOrClone(options); | options = RequestOptions.CreateOrClone(options); | ||||
| var payload = new VoiceStateUpdateParams | |||||
| var msg = new VoiceStateUpdateParams | |||||
| { | { | ||||
| GuildId = guildId, | GuildId = guildId, | ||||
| ChannelId = channelId, | ChannelId = channelId, | ||||
| SelfDeaf = selfDeaf, | SelfDeaf = selfDeaf, | ||||
| SelfMute = selfMute | 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<ulong> guildIds, RequestOptions options = null) | public async Task SendGuildSyncAsync(IEnumerable<ulong> guildIds, RequestOptions options = null) | ||||
| { | { | ||||
| @@ -12,7 +12,6 @@ using System.Collections.Generic; | |||||
| using System.Collections.Immutable; | using System.Collections.Immutable; | ||||
| using System.IO; | using System.IO; | ||||
| using System.Linq; | using System.Linq; | ||||
| using System.Runtime.CompilerServices; | |||||
| using System.Threading; | using System.Threading; | ||||
| using System.Threading.Tasks; | using System.Threading.Tasks; | ||||
| using GameModel = Discord.API.Game; | using GameModel = Discord.API.Game; | ||||
| @@ -88,7 +87,7 @@ namespace Discord.WebSocket | |||||
| _stateLock = new SemaphoreSlim(1, 1); | _stateLock = new SemaphoreSlim(1, 1); | ||||
| _gatewayLogger = LogManager.CreateLogger(ShardId == 0 && TotalShards == 1 ? "Gateway" : $"Shard #{ShardId}"); | _gatewayLogger = LogManager.CreateLogger(ShardId == 0 && TotalShards == 1 ? "Gateway" : $"Shard #{ShardId}"); | ||||
| _serializer = DiscordJsonSerializer.Global.CreateScope(); | |||||
| _serializer = DiscordSocketJsonSerializer.Global.CreateScope(); | |||||
| _serializer.Error += ex => | _serializer.Error += ex => | ||||
| { | { | ||||
| _restLogger.WarningAsync("Serializer Error", ex).GetAwaiter().GetResult(); | _restLogger.WarningAsync("Serializer Error", ex).GetAwaiter().GetResult(); | ||||
| @@ -593,7 +592,7 @@ namespace Discord.WebSocket | |||||
| { | { | ||||
| await _gatewayLogger.DebugAsync("Received Dispatch (GUILD_EMOJIS_UPDATE)").ConfigureAwait(false); | 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); | var guild = State.GetGuild(data.GuildId); | ||||
| if (guild != null) | if (guild != null) | ||||
| { | { | ||||
| @@ -1201,6 +1200,35 @@ namespace Discord.WebSocket | |||||
| } | } | ||||
| } | } | ||||
| break; | 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<IMessage, ulong>(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": | case "MESSAGE_REACTION_ADD": | ||||
| { | { | ||||
| await _gatewayLogger.DebugAsync("Received Dispatch (MESSAGE_REACTION_ADD)").ConfigureAwait(false); | await _gatewayLogger.DebugAsync("Received Dispatch (MESSAGE_REACTION_ADD)").ConfigureAwait(false); | ||||
| @@ -1271,35 +1299,6 @@ namespace Discord.WebSocket | |||||
| } | } | ||||
| } | } | ||||
| break; | 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<IMessage, ulong>(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 | //Statuses | ||||
| case "PRESENCE_UPDATE": | case "PRESENCE_UPDATE": | ||||
| @@ -124,6 +124,7 @@ namespace Discord.Audio | |||||
| } | } | ||||
| finally | finally | ||||
| { | { | ||||
| data.Clear(); | |||||
| _formatters.Enqueue(data); | _formatters.Enqueue(data); | ||||
| } | } | ||||
| } | } | ||||
| @@ -138,7 +139,7 @@ namespace Discord.Audio | |||||
| { | { | ||||
| await SendAsync(VoiceOpCode.Heartbeat, DateTimeUtils.ToUnixMilliseconds(DateTimeOffset.UtcNow), options: options).ConfigureAwait(false); | 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 | await SendAsync(VoiceOpCode.Identify, new IdentifyParams | ||||
| { | { | ||||
| @@ -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<DiscordSocketJsonSerializer> _singleton = new Lazy<DiscordSocketJsonSerializer>(); | |||||
| public static DiscordSocketJsonSerializer Global => _singleton.Value; | |||||
| public DiscordSocketJsonSerializer() | |||||
| : this((JsonSerializer)null) { } | |||||
| public DiscordSocketJsonSerializer(JsonSerializer parent) | |||||
| : base(parent ?? DiscordRestJsonSerializer.Global) | |||||
| { | |||||
| AddSelectorConverter<GatewayOpCode, ObjectPropertyConverter<HelloEvent>>( | |||||
| ModelSelectorGroups.GatewayFrame, GatewayOpCode.Hello); | |||||
| AddSelectorConverter<GatewayOpCode, BooleanPropertyConverter>( | |||||
| ModelSelectorGroups.GatewayFrame, GatewayOpCode.InvalidSession); | |||||
| AddSelectorConverter<GatewayOpCode, Int32PropertyConverter>( | |||||
| ModelSelectorGroups.GatewayFrame, GatewayOpCode.Heartbeat); | |||||
| AddSelectorConverter<GatewayOpCode, Int32PropertyConverter>( | |||||
| ModelSelectorGroups.GatewayFrame, GatewayOpCode.HeartbeatAck); | |||||
| AddSelectorConverter<GatewayOpCode, ObjectPropertyConverter<IdentifyParams>>( | |||||
| ModelSelectorGroups.GatewayFrame, GatewayOpCode.Identify); | |||||
| AddSelectorConverter<GatewayOpCode, ObjectPropertyConverter<ResumeParams>>( | |||||
| ModelSelectorGroups.GatewayFrame, GatewayOpCode.Resume); | |||||
| AddSelectorConverter<GatewayOpCode, ObjectPropertyConverter<StatusUpdateParams>>( | |||||
| ModelSelectorGroups.GatewayFrame, GatewayOpCode.StatusUpdate); | |||||
| AddSelectorConverter<GatewayOpCode, ObjectPropertyConverter<RequestMembersParams>>( | |||||
| ModelSelectorGroups.GatewayFrame, GatewayOpCode.RequestGuildMembers); | |||||
| AddSelectorConverter<GatewayOpCode, ObjectPropertyConverter<VoiceStateUpdateParams>>( | |||||
| ModelSelectorGroups.GatewayFrame, GatewayOpCode.VoiceStateUpdate); | |||||
| AddSelectorConverter<string, ObjectPropertyConverter<ReadyEvent>>( | |||||
| ModelSelectorGroups.GatewayDispatchFrame, "READY"); | |||||
| AddSelectorConverter<string, ObjectPropertyConverter<ExtendedGuild>>( | |||||
| ModelSelectorGroups.GatewayDispatchFrame, "GUILD_CREATE"); | |||||
| AddSelectorConverter<string, ObjectPropertyConverter<API.Guild>>( | |||||
| ModelSelectorGroups.GatewayDispatchFrame, "GUILD_UPDATE"); | |||||
| AddSelectorConverter<string, ObjectPropertyConverter<GuildEmojiUpdateEvent>>( | |||||
| ModelSelectorGroups.GatewayDispatchFrame, "GUILD_EMOJIS_UPDATE"); | |||||
| AddSelectorConverter<string, ObjectPropertyConverter<GuildSyncEvent>>( | |||||
| ModelSelectorGroups.GatewayDispatchFrame, "GUILD_SYNC"); | |||||
| AddSelectorConverter<string, ObjectPropertyConverter<ExtendedGuild>>( | |||||
| ModelSelectorGroups.GatewayDispatchFrame, "GUILD_DELETE"); | |||||
| AddSelectorConverter<string, ObjectPropertyConverter<API.Channel>>( | |||||
| ModelSelectorGroups.GatewayDispatchFrame, "CHANNEL_CREATE"); | |||||
| AddSelectorConverter<string, ObjectPropertyConverter<API.Channel>>( | |||||
| ModelSelectorGroups.GatewayDispatchFrame, "CHANNEL_UPDATE"); | |||||
| AddSelectorConverter<string, ObjectPropertyConverter<API.Channel>>( | |||||
| ModelSelectorGroups.GatewayDispatchFrame, "CHANNEL_DELETE"); | |||||
| AddSelectorConverter<string, ObjectPropertyConverter<GuildMemberAddEvent>>( | |||||
| ModelSelectorGroups.GatewayDispatchFrame, "GUILD_MEMBER_ADD"); | |||||
| AddSelectorConverter<string, ObjectPropertyConverter<GuildMemberUpdateEvent>>( | |||||
| ModelSelectorGroups.GatewayDispatchFrame, "GUILD_MEMBER_UPDATE"); | |||||
| AddSelectorConverter<string, ObjectPropertyConverter<GuildMemberRemoveEvent>>( | |||||
| ModelSelectorGroups.GatewayDispatchFrame, "GUILD_MEMBER_REMOVE"); | |||||
| AddSelectorConverter<string, ObjectPropertyConverter<GuildMembersChunkEvent>>( | |||||
| ModelSelectorGroups.GatewayDispatchFrame, "GUILD_MEMBERS_CHUNK"); | |||||
| AddSelectorConverter<string, ObjectPropertyConverter<RecipientEvent>>( | |||||
| ModelSelectorGroups.GatewayDispatchFrame, "CHANNEL_RECIPIENT_ADD"); | |||||
| AddSelectorConverter<string, ObjectPropertyConverter<RecipientEvent>>( | |||||
| ModelSelectorGroups.GatewayDispatchFrame, "CHANNEL_RECIPIENT_REMOVE"); | |||||
| AddSelectorConverter<string, ObjectPropertyConverter<GuildRoleCreateEvent>>( | |||||
| ModelSelectorGroups.GatewayDispatchFrame, "GUILD_ROLE_CREATE"); | |||||
| AddSelectorConverter<string, ObjectPropertyConverter<GuildRoleUpdateEvent>>( | |||||
| ModelSelectorGroups.GatewayDispatchFrame, "GUILD_ROLE_UPDATE"); | |||||
| AddSelectorConverter<string, ObjectPropertyConverter<GuildRoleDeleteEvent>>( | |||||
| ModelSelectorGroups.GatewayDispatchFrame, "GUILD_ROLE_UPDATE"); | |||||
| AddSelectorConverter<string, ObjectPropertyConverter<GuildBanEvent>>( | |||||
| ModelSelectorGroups.GatewayDispatchFrame, "GUILD_BAN_ADD"); | |||||
| AddSelectorConverter<string, ObjectPropertyConverter<GuildBanEvent>>( | |||||
| ModelSelectorGroups.GatewayDispatchFrame, "GUILD_BAN_REMOVE"); | |||||
| AddSelectorConverter<string, ObjectPropertyConverter<API.Message>>( | |||||
| ModelSelectorGroups.GatewayDispatchFrame, "MESSAGE_CREATE"); | |||||
| AddSelectorConverter<string, ObjectPropertyConverter<API.Message>>( | |||||
| ModelSelectorGroups.GatewayDispatchFrame, "MESSAGE_UPDATE"); | |||||
| AddSelectorConverter<string, ObjectPropertyConverter<API.Message>>( | |||||
| ModelSelectorGroups.GatewayDispatchFrame, "MESSAGE_DELETE"); | |||||
| AddSelectorConverter<string, ObjectPropertyConverter<MessageDeleteBulkEvent>>( | |||||
| ModelSelectorGroups.GatewayDispatchFrame, "MESSAGE_DELETE_BULK"); | |||||
| AddSelectorConverter<string, ObjectPropertyConverter<Reaction>>( | |||||
| ModelSelectorGroups.GatewayDispatchFrame, "MESSAGE_REACTION_ADD"); | |||||
| AddSelectorConverter<string, ObjectPropertyConverter<Reaction>>( | |||||
| ModelSelectorGroups.GatewayDispatchFrame, "MESSAGE_REACTION_REMOVE"); | |||||
| AddSelectorConverter<string, ObjectPropertyConverter<RemoveAllReactionsEvent>>( | |||||
| ModelSelectorGroups.GatewayDispatchFrame, "MESSAGE_REACTION_REMOVE_ALL"); | |||||
| AddSelectorConverter<string, ObjectPropertyConverter<API.Presence>>( | |||||
| ModelSelectorGroups.GatewayDispatchFrame, "PRESENCE_UPDATE"); | |||||
| AddSelectorConverter<string, ObjectPropertyConverter<API.User>>( | |||||
| ModelSelectorGroups.GatewayDispatchFrame, "USER_UPDATE"); | |||||
| AddSelectorConverter<string, ObjectPropertyConverter<TypingStartEvent>>( | |||||
| ModelSelectorGroups.GatewayDispatchFrame, "TYPING_START"); | |||||
| AddSelectorConverter<string, ObjectPropertyConverter<API.VoiceState>>( | |||||
| ModelSelectorGroups.GatewayDispatchFrame, "VOICE_STATE_UPDATE"); | |||||
| AddSelectorConverter<string, ObjectPropertyConverter<VoiceServerUpdateEvent>>( | |||||
| ModelSelectorGroups.GatewayDispatchFrame, "VOICE_SERVER_UPDATE"); | |||||
| } | |||||
| private DiscordSocketJsonSerializer(DiscordSocketJsonSerializer parent) | |||||
| : base(parent) { } | |||||
| public DiscordSocketJsonSerializer CreateScope() => new DiscordSocketJsonSerializer(this); | |||||
| } | |||||
| } | |||||
| @@ -0,0 +1,21 @@ | |||||
| using System; | |||||
| namespace Discord.Serialization.Json | |||||
| { | |||||
| public class DiscordVoiceJsonSerializer : JsonSerializer | |||||
| { | |||||
| private static readonly Lazy<DiscordVoiceJsonSerializer> _singleton = new Lazy<DiscordVoiceJsonSerializer>(); | |||||
| 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); | |||||
| } | |||||
| } | |||||
| @@ -30,7 +30,7 @@ namespace Discord.Webhook | |||||
| { | { | ||||
| _webhookId = webhookId; | _webhookId = webhookId; | ||||
| _serializer = DiscordJsonSerializer.Global.CreateScope(); | |||||
| _serializer = DiscordRestJsonSerializer.Global.CreateScope(); | |||||
| _serializer.Error += ex => | _serializer.Error += ex => | ||||
| { | { | ||||
| _restLogger.WarningAsync("Serializer Error", ex).GetAwaiter().GetResult(); | _restLogger.WarningAsync("Serializer Error", ex).GetAwaiter().GetResult(); | ||||