diff --git a/src/Discord.Net.Rest/Serialization/JsonConverters/EntityOrIdPropertyConverter.cs b/src/Discord.Net.Rest/Serialization/JsonConverters/EntityOrIdPropertyConverter.cs index c25302c18..d7e917213 100644 --- a/src/Discord.Net.Rest/Serialization/JsonConverters/EntityOrIdPropertyConverter.cs +++ b/src/Discord.Net.Rest/Serialization/JsonConverters/EntityOrIdPropertyConverter.cs @@ -28,7 +28,7 @@ namespace Discord.Serialization.Json.Converters else { if (isTopLevel) - writer.WriteAttribute(map.Key, value.Id); + writer.WriteAttribute(map.Utf16Key, value.Id); else writer.WriteValue(value.Id); } diff --git a/src/Discord.Net.Rest/Serialization/JsonConverters/Int53PropertyConverter.cs b/src/Discord.Net.Rest/Serialization/JsonConverters/Int53PropertyConverter.cs index d59fd1610..9f8d51a46 100644 --- a/src/Discord.Net.Rest/Serialization/JsonConverters/Int53PropertyConverter.cs +++ b/src/Discord.Net.Rest/Serialization/JsonConverters/Int53PropertyConverter.cs @@ -15,7 +15,7 @@ namespace Discord.Serialization.Json.Converters public void Write(PropertyMap map, ref JsonWriter writer, long value, bool isTopLevel) { if (isTopLevel) - writer.WriteAttribute(map.Key, value); + writer.WriteAttribute(map.Utf16Key, value); else writer.WriteValue(value.ToString()); } diff --git a/src/Discord.Net.Rest/Serialization/JsonConverters/UInt53PropertyConverter.cs b/src/Discord.Net.Rest/Serialization/JsonConverters/UInt53PropertyConverter.cs index fa35ff711..07f6e54a3 100644 --- a/src/Discord.Net.Rest/Serialization/JsonConverters/UInt53PropertyConverter.cs +++ b/src/Discord.Net.Rest/Serialization/JsonConverters/UInt53PropertyConverter.cs @@ -15,7 +15,7 @@ namespace Discord.Serialization.Json.Converters public void Write(PropertyMap map, ref JsonWriter writer, ulong value, bool isTopLevel) { if (isTopLevel) - writer.WriteAttribute(map.Key, value); + writer.WriteAttribute(map.Utf16Key, value); else writer.WriteValue(value.ToString()); } diff --git a/src/Discord.Net.Serialization/ConverterCollection.cs b/src/Discord.Net.Serialization/ConverterCollection.cs index d6e32ecc4..eee25dfbe 100644 --- a/src/Discord.Net.Serialization/ConverterCollection.cs +++ b/src/Discord.Net.Serialization/ConverterCollection.cs @@ -65,42 +65,48 @@ namespace Discord.Serialization public object Get(PropertyInfo propInfo = null) { - return _cache.GetOrAdd(typeof(TType), _ => + if (!_cache.TryGetValue(typeof(TType), out var result)) { - TypeInfo typeInfo = typeof(TType).GetTypeInfo(); + object converter = Create(typeof(TType), propInfo); + result = _cache.GetOrAdd(typeof(TType), converter); + } + return result; + } + private object Create(Type type, PropertyInfo propInfo) + { + TypeInfo typeInfo = type.GetTypeInfo(); - //Mapped generic converters (List -> CollectionPropertyConverter) - if (typeInfo.IsGenericType) + //Mapped generic converters (List -> CollectionPropertyConverter) + if (typeInfo.IsGenericType) + { + var converterType = FindConverterType(typeInfo.GetGenericTypeDefinition(), _mappedGenericTypes, typeInfo, propInfo); + if (converterType != null) { - var converterType = FindConverterType(typeInfo.GetGenericTypeDefinition(), _mappedGenericTypes, typeInfo, propInfo); - if (converterType != null) - { - var innerType = typeInfo.GenericTypeArguments[0]; - converterType = converterType.MakeGenericType(innerType); - object innerConverter = GetInnerConverter(innerType, propInfo); - return Activator.CreateInstance(converterType, innerConverter); - } + var innerType = typeInfo.GenericTypeArguments[0]; + converterType = converterType.MakeGenericType(innerType); + object innerConverter = GetInnerConverter(innerType, propInfo); + return Activator.CreateInstance(converterType, innerConverter); } + } - //Normal converters (bool -> BooleanPropertyConverter) - { - var converterType = FindConverterType(typeof(TType), _types, typeInfo, propInfo); - if (converterType != null) - return Activator.CreateInstance(converterType); - } + //Normal converters (bool -> BooleanPropertyConverter) + { + var converterType = FindConverterType(type, _types, typeInfo, propInfo); + if (converterType != null) + return Activator.CreateInstance(converterType); + } - //Generic converters (Model -> ObjectPropertyConverter) + //Generic converters (Model -> ObjectPropertyConverter) + { + var converterType = FindConverterType(_genericTypes, typeInfo, propInfo); + if (converterType != null) { - var converterType = FindConverterType(_genericTypes, typeInfo, propInfo); - if (converterType != null) - { - converterType = converterType.MakeGenericType(typeof(TType)); - return Activator.CreateInstance(converterType); - } + converterType = converterType.MakeGenericType(type); + return Activator.CreateInstance(converterType); } + } - throw new InvalidOperationException($"Unsupported model type: {typeof(TType).Name}"); - }); + throw new InvalidOperationException($"Unsupported model type: {type.Name}"); } private object GetInnerConverter(Type type, PropertyInfo propInfo) => _getConverterMethod.MakeGenericMethod(type).Invoke(this, new object[] { propInfo }); diff --git a/src/Discord.Net.Serialization/Json/Converters/Collections.cs b/src/Discord.Net.Serialization/Json/Converters/Collections.cs index b0096b860..f811bf086 100644 --- a/src/Discord.Net.Serialization/Json/Converters/Collections.cs +++ b/src/Discord.Net.Serialization/Json/Converters/Collections.cs @@ -26,7 +26,7 @@ namespace Discord.Serialization.Json.Converters public void Write(PropertyMap map, ref JsonWriter writer, List value, bool isTopLevel) { if (isTopLevel) - writer.WriteArrayStart(map.Key); + writer.WriteArrayStart(map.Utf16Key); else writer.WriteArrayStart(); for (int i = 0; i < value.Count; i++) diff --git a/src/Discord.Net.Serialization/Json/Converters/Nullable.cs b/src/Discord.Net.Serialization/Json/Converters/Nullable.cs index bd1efaa31..d40b40ca9 100644 --- a/src/Discord.Net.Serialization/Json/Converters/Nullable.cs +++ b/src/Discord.Net.Serialization/Json/Converters/Nullable.cs @@ -28,7 +28,7 @@ namespace Discord.Serialization.Json.Converters else { if (isTopLevel) - writer.WriteAttributeNull(map.Key); + writer.WriteAttributeNull(map.Utf16Key); else writer.WriteNull(); } diff --git a/src/Discord.Net.Serialization/Json/Converters/Object.cs b/src/Discord.Net.Serialization/Json/Converters/Object.cs index 06eaeb045..5938af573 100644 --- a/src/Discord.Net.Serialization/Json/Converters/Object.cs +++ b/src/Discord.Net.Serialization/Json/Converters/Object.cs @@ -1,4 +1,5 @@ using System.Text.Json; +using System.Text.Utf8; namespace Discord.Serialization.Json.Converters { @@ -19,9 +20,8 @@ namespace Discord.Serialization.Json.Converters return model; if (reader.TokenType != JsonTokenType.PropertyName) throw new SerializationException("Bad input, expected PropertyName"); - - string key = reader.ParseString(); - if (_map.PropertiesByKey.TryGetValue(key, out var property)) + + if (_map.PropertiesByKey.TryGetValue(reader.Value, out var property)) (property as IJsonPropertyMap).Read(model, ref reader); else reader.Skip(); //Unknown property, skip @@ -31,7 +31,7 @@ namespace Discord.Serialization.Json.Converters public void Write(PropertyMap map, ref JsonWriter writer, T value, bool isTopLevel) { if (isTopLevel) - writer.WriteObjectStart(map.Key); + writer.WriteObjectStart(map.Utf16Key); else writer.WriteObjectStart(); for (int i = 0; i < _map.Properties.Length; i++) diff --git a/src/Discord.Net.Serialization/Json/Converters/Primitives.DateTime.cs b/src/Discord.Net.Serialization/Json/Converters/Primitives.DateTime.cs index e8e8e4ab8..e6bd2e764 100644 --- a/src/Discord.Net.Serialization/Json/Converters/Primitives.DateTime.cs +++ b/src/Discord.Net.Serialization/Json/Converters/Primitives.DateTime.cs @@ -16,7 +16,7 @@ namespace Discord.Serialization.Json.Converters public void Write(PropertyMap map, ref JsonWriter writer, DateTime value, bool isTopLevel) { if (isTopLevel) - writer.WriteAttribute(map.Key, value); + writer.WriteAttribute(map.Utf16Key, value); else writer.WriteValue(value); } @@ -35,7 +35,7 @@ namespace Discord.Serialization.Json.Converters public void Write(PropertyMap map, ref JsonWriter writer, DateTimeOffset value, bool isTopLevel) { if (isTopLevel) - writer.WriteAttribute(map.Key, value); + writer.WriteAttribute(map.Utf16Key, value); else writer.WriteValue(value); } diff --git a/src/Discord.Net.Serialization/Json/Converters/Primitives.Float.cs b/src/Discord.Net.Serialization/Json/Converters/Primitives.Float.cs index 1c5b33678..7eaaea74b 100644 --- a/src/Discord.Net.Serialization/Json/Converters/Primitives.Float.cs +++ b/src/Discord.Net.Serialization/Json/Converters/Primitives.Float.cs @@ -15,7 +15,7 @@ namespace Discord.Serialization.Json.Converters public void Write(PropertyMap map, ref JsonWriter writer, float value, bool isTopLevel) { if (isTopLevel) - writer.WriteAttribute(map.Key, value.ToString()); + writer.WriteAttribute(map.Utf16Key, value.ToString()); else writer.WriteValue(value.ToString()); } @@ -34,7 +34,7 @@ namespace Discord.Serialization.Json.Converters public void Write(PropertyMap map, ref JsonWriter writer, double value, bool isTopLevel) { if (isTopLevel) - writer.WriteAttribute(map.Key, value.ToString()); + writer.WriteAttribute(map.Utf16Key, value.ToString()); else writer.WriteValue(value.ToString()); } @@ -53,7 +53,7 @@ namespace Discord.Serialization.Json.Converters public void Write(PropertyMap map, ref JsonWriter writer, decimal value, bool isTopLevel) { if (isTopLevel) - writer.WriteAttribute(map.Key, value.ToString()); + writer.WriteAttribute(map.Utf16Key, value.ToString()); else writer.WriteValue(value.ToString()); } diff --git a/src/Discord.Net.Serialization/Json/Converters/Primitives.Other.cs b/src/Discord.Net.Serialization/Json/Converters/Primitives.Other.cs index 7bc5abe86..20aba58e5 100644 --- a/src/Discord.Net.Serialization/Json/Converters/Primitives.Other.cs +++ b/src/Discord.Net.Serialization/Json/Converters/Primitives.Other.cs @@ -19,7 +19,7 @@ namespace Discord.Serialization.Json.Converters public void Write(PropertyMap map, ref JsonWriter writer, bool value, bool isTopLevel) { if (isTopLevel) - writer.WriteAttribute(map.Key, value); + writer.WriteAttribute(map.Utf16Key, value); else writer.WriteValue(value); } @@ -33,12 +33,12 @@ namespace Discord.Serialization.Json.Converters reader.Read(); if (reader.ValueType != JsonValueType.String) throw new SerializationException("Bad input, expected String"); - return Guid.Parse(reader.ParseString()); + return Guid.Parse(reader.ParseString()); //TODO: Causes allocs } public void Write(PropertyMap map, ref JsonWriter writer, Guid value, bool isTopLevel) { if (isTopLevel) - writer.WriteAttribute(map.Key, value.ToString()); + writer.WriteAttribute(map.Utf16Key, value.ToString()); else writer.WriteValue(value.ToString()); } diff --git a/src/Discord.Net.Serialization/Json/Converters/Primitives.Signed.cs b/src/Discord.Net.Serialization/Json/Converters/Primitives.Signed.cs index dcf0cd946..bc3150e0b 100644 --- a/src/Discord.Net.Serialization/Json/Converters/Primitives.Signed.cs +++ b/src/Discord.Net.Serialization/Json/Converters/Primitives.Signed.cs @@ -15,7 +15,7 @@ namespace Discord.Serialization.Json.Converters public void Write(PropertyMap map, ref JsonWriter writer, sbyte value, bool isTopLevel) { if (isTopLevel) - writer.WriteAttribute(map.Key, value); + writer.WriteAttribute(map.Utf16Key, value); else writer.WriteValue(value); } @@ -34,7 +34,7 @@ namespace Discord.Serialization.Json.Converters public void Write(PropertyMap map, ref JsonWriter writer, short value, bool isTopLevel) { if (isTopLevel) - writer.WriteAttribute(map.Key, value); + writer.WriteAttribute(map.Utf16Key, value); else writer.WriteValue(value); } @@ -53,7 +53,7 @@ namespace Discord.Serialization.Json.Converters public void Write(PropertyMap map, ref JsonWriter writer, int value, bool isTopLevel) { if (isTopLevel) - writer.WriteAttribute(map.Key, value); + writer.WriteAttribute(map.Utf16Key, value); else writer.WriteValue(value); } @@ -72,7 +72,7 @@ namespace Discord.Serialization.Json.Converters public void Write(PropertyMap map, ref JsonWriter writer, long value, bool isTopLevel) { if (isTopLevel) - writer.WriteAttribute(map.Key, value.ToString()); + writer.WriteAttribute(map.Utf16Key, value.ToString()); else writer.WriteValue(value.ToString()); } diff --git a/src/Discord.Net.Serialization/Json/Converters/Primitives.String.cs b/src/Discord.Net.Serialization/Json/Converters/Primitives.String.cs index 8781d8a3c..243df1dd8 100644 --- a/src/Discord.Net.Serialization/Json/Converters/Primitives.String.cs +++ b/src/Discord.Net.Serialization/Json/Converters/Primitives.String.cs @@ -34,7 +34,7 @@ namespace Discord.Serialization.Json.Converters public void Write(PropertyMap map, ref JsonWriter writer, string value, bool isTopLevel) { if (isTopLevel) - writer.WriteAttribute(map.Key, value); + writer.WriteAttribute(map.Utf16Key, value); else writer.WriteValue(value); } diff --git a/src/Discord.Net.Serialization/Json/Converters/Primitives.Unsigned.cs b/src/Discord.Net.Serialization/Json/Converters/Primitives.Unsigned.cs index cd1267abb..539dfcaa0 100644 --- a/src/Discord.Net.Serialization/Json/Converters/Primitives.Unsigned.cs +++ b/src/Discord.Net.Serialization/Json/Converters/Primitives.Unsigned.cs @@ -15,7 +15,7 @@ namespace Discord.Serialization.Json.Converters public void Write(PropertyMap map, ref JsonWriter writer, byte value, bool isTopLevel) { if (isTopLevel) - writer.WriteAttribute(map.Key, value); + writer.WriteAttribute(map.Utf16Key, value); else writer.WriteValue(value); } @@ -34,7 +34,7 @@ namespace Discord.Serialization.Json.Converters public void Write(PropertyMap map, ref JsonWriter writer, ushort value, bool isTopLevel) { if (isTopLevel) - writer.WriteAttribute(map.Key, value); + writer.WriteAttribute(map.Utf16Key, value); else writer.WriteValue(value); } @@ -53,7 +53,7 @@ namespace Discord.Serialization.Json.Converters public void Write(PropertyMap map, ref JsonWriter writer, uint value, bool isTopLevel) { if (isTopLevel) - writer.WriteAttribute(map.Key, value); + writer.WriteAttribute(map.Utf16Key, value); else writer.WriteValue(value); } @@ -72,7 +72,7 @@ namespace Discord.Serialization.Json.Converters public void Write(PropertyMap map, ref JsonWriter writer, ulong value, bool isTopLevel) { if (isTopLevel) - writer.WriteAttribute(map.Key, value.ToString()); + writer.WriteAttribute(map.Utf16Key, value.ToString()); else writer.WriteValue(value.ToString()); } diff --git a/src/Discord.Net.Serialization/Json/IJsonPropertyMap.cs b/src/Discord.Net.Serialization/Json/IJsonPropertyMap.cs index 3ebc05b06..d0921dedb 100644 --- a/src/Discord.Net.Serialization/Json/IJsonPropertyMap.cs +++ b/src/Discord.Net.Serialization/Json/IJsonPropertyMap.cs @@ -1,10 +1,12 @@ using System.Text.Json; +using System.Text.Utf8; namespace Discord.Serialization { internal interface IJsonPropertyMap { - string Key { get; } + string Utf16Key { get; } + Utf8String Utf8Key { get; } void Write(TModel model, ref JsonWriter writer); void Read(TModel model, ref JsonReader reader); diff --git a/src/Discord.Net.Serialization/ModelMap.cs b/src/Discord.Net.Serialization/ModelMap.cs index bf3d8923d..d03bfe080 100644 --- a/src/Discord.Net.Serialization/ModelMap.cs +++ b/src/Discord.Net.Serialization/ModelMap.cs @@ -1,4 +1,5 @@ -using System.Collections.Generic; +using System; +using System.Collections.Generic; using System.Linq; namespace Discord.Serialization @@ -7,9 +8,9 @@ namespace Discord.Serialization where TModel : class, new() { public readonly PropertyMap[] Properties; - public readonly Dictionary PropertiesByKey; + public readonly Dictionary, PropertyMap> PropertiesByKey; - public ModelMap(Dictionary properties) + public ModelMap(Dictionary, PropertyMap> properties) { PropertiesByKey = properties; Properties = PropertiesByKey.Values.ToArray(); diff --git a/src/Discord.Net.Serialization/PropertyMap.cs b/src/Discord.Net.Serialization/PropertyMap.cs index b2f3e2ab3..fcffaa7a0 100644 --- a/src/Discord.Net.Serialization/PropertyMap.cs +++ b/src/Discord.Net.Serialization/PropertyMap.cs @@ -1,17 +1,20 @@ using System.Reflection; +using System.Text.Utf8; namespace Discord.Serialization { public abstract class PropertyMap { - public string Key { get; } + public string Utf16Key { get; } + public Utf8String Utf8Key { get; } public bool ExcludeNull { get; } public PropertyMap(PropertyInfo propInfo) { var jsonProperty = propInfo.GetCustomAttribute(); - Key = jsonProperty?.Key ?? propInfo.Name; + Utf16Key = jsonProperty?.Key ?? propInfo.Name; + Utf8Key = new Utf8String(Utf16Key); ExcludeNull = jsonProperty?.ExcludeNull ?? false; } } diff --git a/src/Discord.Net.Serialization/SerializationFormat.cs b/src/Discord.Net.Serialization/SerializationFormat.cs index afdd62de5..241ad6fca 100644 --- a/src/Discord.Net.Serialization/SerializationFormat.cs +++ b/src/Discord.Net.Serialization/SerializationFormat.cs @@ -25,17 +25,15 @@ namespace Discord.Serialization return _maps.GetOrAdd(typeof(TModel), _ => { var type = typeof(TModel).GetTypeInfo(); - var properties = new Dictionary(); + var propInfos = type.DeclaredProperties + .Where(x => x.CanRead && x.CanWrite) + .ToArray(); - var propInfos = type.DeclaredProperties.ToArray(); + var properties = new Dictionary, PropertyMap>(propInfos.Length, Utf8SpanComparer.Instance); for (int i = 0; i < propInfos.Length; i++) { - var propInfo = propInfos[i]; - if (!propInfo.CanRead || !propInfo.CanWrite) - continue; - - var propMap = MapProperty(propInfo); - properties.Add(propMap.Key, propMap); + var propMap = MapProperty(propInfos[i]); + properties.Add(propMap.Utf8Key, propMap); } return new ModelMap(properties); }) as ModelMap; diff --git a/src/Discord.Net.Serialization/Utf8SpanComparer.cs b/src/Discord.Net.Serialization/Utf8SpanComparer.cs new file mode 100644 index 000000000..b22763626 --- /dev/null +++ b/src/Discord.Net.Serialization/Utf8SpanComparer.cs @@ -0,0 +1,42 @@ +using System; +using System.Collections.Generic; + +namespace Discord.Serialization +{ + internal class Utf8SpanComparer : IEqualityComparer> + { + public static readonly Utf8SpanComparer Instance = new Utf8SpanComparer(); + + public bool Equals(ReadOnlySpan x, ReadOnlySpan y) => x.SequenceEqual(y); + public int GetHashCode(ReadOnlySpan obj) + { + //From Utf8String + //TODO: Replace when they do + unchecked + { + if (obj.Length <= 4) + { + int hash = obj.Length; + for (int i = 0; i < obj.Length; i++) + { + hash <<= 8; + hash ^= obj[i]; + } + return hash; + } + else + { + int hash = obj.Length; + hash ^= obj[0]; + hash <<= 8; + hash ^= obj[1]; + hash <<= 8; + hash ^= obj[obj.Length - 2]; + hash <<= 8; + hash ^= obj[obj.Length - 1]; + return hash; + } + } + } + } +}