@@ -28,7 +28,7 @@ namespace Discord.Serialization.Json.Converters | |||||
else | else | ||||
{ | { | ||||
if (isTopLevel) | if (isTopLevel) | ||||
writer.WriteAttribute(map.Key, value.Id); | |||||
writer.WriteAttribute(map.Utf16Key, value.Id); | |||||
else | else | ||||
writer.WriteValue(value.Id); | writer.WriteValue(value.Id); | ||||
} | } | ||||
@@ -15,7 +15,7 @@ namespace Discord.Serialization.Json.Converters | |||||
public void Write(PropertyMap map, ref JsonWriter writer, long value, bool isTopLevel) | public void Write(PropertyMap map, ref JsonWriter writer, long value, bool isTopLevel) | ||||
{ | { | ||||
if (isTopLevel) | if (isTopLevel) | ||||
writer.WriteAttribute(map.Key, value); | |||||
writer.WriteAttribute(map.Utf16Key, value); | |||||
else | else | ||||
writer.WriteValue(value.ToString()); | writer.WriteValue(value.ToString()); | ||||
} | } | ||||
@@ -15,7 +15,7 @@ namespace Discord.Serialization.Json.Converters | |||||
public void Write(PropertyMap map, ref JsonWriter writer, ulong value, bool isTopLevel) | public void Write(PropertyMap map, ref JsonWriter writer, ulong value, bool isTopLevel) | ||||
{ | { | ||||
if (isTopLevel) | if (isTopLevel) | ||||
writer.WriteAttribute(map.Key, value); | |||||
writer.WriteAttribute(map.Utf16Key, value); | |||||
else | else | ||||
writer.WriteValue(value.ToString()); | writer.WriteValue(value.ToString()); | ||||
} | } | ||||
@@ -65,42 +65,48 @@ namespace Discord.Serialization | |||||
public object Get<TType>(PropertyInfo propInfo = null) | public object Get<TType>(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<T> -> CollectionPropertyConverter<T>) | |||||
if (typeInfo.IsGenericType) | |||||
//Mapped generic converters (List<T> -> CollectionPropertyConverter<T>) | |||||
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<Model>) | |||||
//Generic converters (Model -> ObjectPropertyConverter<Model>) | |||||
{ | |||||
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) | private object GetInnerConverter(Type type, PropertyInfo propInfo) | ||||
=> _getConverterMethod.MakeGenericMethod(type).Invoke(this, new object[] { propInfo }); | => _getConverterMethod.MakeGenericMethod(type).Invoke(this, new object[] { propInfo }); | ||||
@@ -26,7 +26,7 @@ namespace Discord.Serialization.Json.Converters | |||||
public void Write(PropertyMap map, ref JsonWriter writer, List<T> value, bool isTopLevel) | public void Write(PropertyMap map, ref JsonWriter writer, List<T> value, bool isTopLevel) | ||||
{ | { | ||||
if (isTopLevel) | if (isTopLevel) | ||||
writer.WriteArrayStart(map.Key); | |||||
writer.WriteArrayStart(map.Utf16Key); | |||||
else | else | ||||
writer.WriteArrayStart(); | writer.WriteArrayStart(); | ||||
for (int i = 0; i < value.Count; i++) | for (int i = 0; i < value.Count; i++) | ||||
@@ -28,7 +28,7 @@ namespace Discord.Serialization.Json.Converters | |||||
else | else | ||||
{ | { | ||||
if (isTopLevel) | if (isTopLevel) | ||||
writer.WriteAttributeNull(map.Key); | |||||
writer.WriteAttributeNull(map.Utf16Key); | |||||
else | else | ||||
writer.WriteNull(); | writer.WriteNull(); | ||||
} | } | ||||
@@ -1,4 +1,5 @@ | |||||
using System.Text.Json; | using System.Text.Json; | ||||
using System.Text.Utf8; | |||||
namespace Discord.Serialization.Json.Converters | namespace Discord.Serialization.Json.Converters | ||||
{ | { | ||||
@@ -19,9 +20,8 @@ namespace Discord.Serialization.Json.Converters | |||||
return model; | return model; | ||||
if (reader.TokenType != JsonTokenType.PropertyName) | if (reader.TokenType != JsonTokenType.PropertyName) | ||||
throw new SerializationException("Bad input, expected 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<T>).Read(model, ref reader); | (property as IJsonPropertyMap<T>).Read(model, ref reader); | ||||
else | else | ||||
reader.Skip(); //Unknown property, skip | 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) | public void Write(PropertyMap map, ref JsonWriter writer, T value, bool isTopLevel) | ||||
{ | { | ||||
if (isTopLevel) | if (isTopLevel) | ||||
writer.WriteObjectStart(map.Key); | |||||
writer.WriteObjectStart(map.Utf16Key); | |||||
else | else | ||||
writer.WriteObjectStart(); | writer.WriteObjectStart(); | ||||
for (int i = 0; i < _map.Properties.Length; i++) | for (int i = 0; i < _map.Properties.Length; i++) | ||||
@@ -16,7 +16,7 @@ namespace Discord.Serialization.Json.Converters | |||||
public void Write(PropertyMap map, ref JsonWriter writer, DateTime value, bool isTopLevel) | public void Write(PropertyMap map, ref JsonWriter writer, DateTime value, bool isTopLevel) | ||||
{ | { | ||||
if (isTopLevel) | if (isTopLevel) | ||||
writer.WriteAttribute(map.Key, value); | |||||
writer.WriteAttribute(map.Utf16Key, value); | |||||
else | else | ||||
writer.WriteValue(value); | writer.WriteValue(value); | ||||
} | } | ||||
@@ -35,7 +35,7 @@ namespace Discord.Serialization.Json.Converters | |||||
public void Write(PropertyMap map, ref JsonWriter writer, DateTimeOffset value, bool isTopLevel) | public void Write(PropertyMap map, ref JsonWriter writer, DateTimeOffset value, bool isTopLevel) | ||||
{ | { | ||||
if (isTopLevel) | if (isTopLevel) | ||||
writer.WriteAttribute(map.Key, value); | |||||
writer.WriteAttribute(map.Utf16Key, value); | |||||
else | else | ||||
writer.WriteValue(value); | writer.WriteValue(value); | ||||
} | } | ||||
@@ -15,7 +15,7 @@ namespace Discord.Serialization.Json.Converters | |||||
public void Write(PropertyMap map, ref JsonWriter writer, float value, bool isTopLevel) | public void Write(PropertyMap map, ref JsonWriter writer, float value, bool isTopLevel) | ||||
{ | { | ||||
if (isTopLevel) | if (isTopLevel) | ||||
writer.WriteAttribute(map.Key, value.ToString()); | |||||
writer.WriteAttribute(map.Utf16Key, value.ToString()); | |||||
else | else | ||||
writer.WriteValue(value.ToString()); | 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) | public void Write(PropertyMap map, ref JsonWriter writer, double value, bool isTopLevel) | ||||
{ | { | ||||
if (isTopLevel) | if (isTopLevel) | ||||
writer.WriteAttribute(map.Key, value.ToString()); | |||||
writer.WriteAttribute(map.Utf16Key, value.ToString()); | |||||
else | else | ||||
writer.WriteValue(value.ToString()); | 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) | public void Write(PropertyMap map, ref JsonWriter writer, decimal value, bool isTopLevel) | ||||
{ | { | ||||
if (isTopLevel) | if (isTopLevel) | ||||
writer.WriteAttribute(map.Key, value.ToString()); | |||||
writer.WriteAttribute(map.Utf16Key, value.ToString()); | |||||
else | else | ||||
writer.WriteValue(value.ToString()); | writer.WriteValue(value.ToString()); | ||||
} | } | ||||
@@ -19,7 +19,7 @@ namespace Discord.Serialization.Json.Converters | |||||
public void Write(PropertyMap map, ref JsonWriter writer, bool value, bool isTopLevel) | public void Write(PropertyMap map, ref JsonWriter writer, bool value, bool isTopLevel) | ||||
{ | { | ||||
if (isTopLevel) | if (isTopLevel) | ||||
writer.WriteAttribute(map.Key, value); | |||||
writer.WriteAttribute(map.Utf16Key, value); | |||||
else | else | ||||
writer.WriteValue(value); | writer.WriteValue(value); | ||||
} | } | ||||
@@ -33,12 +33,12 @@ namespace Discord.Serialization.Json.Converters | |||||
reader.Read(); | reader.Read(); | ||||
if (reader.ValueType != JsonValueType.String) | if (reader.ValueType != JsonValueType.String) | ||||
throw new SerializationException("Bad input, expected 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) | public void Write(PropertyMap map, ref JsonWriter writer, Guid value, bool isTopLevel) | ||||
{ | { | ||||
if (isTopLevel) | if (isTopLevel) | ||||
writer.WriteAttribute(map.Key, value.ToString()); | |||||
writer.WriteAttribute(map.Utf16Key, value.ToString()); | |||||
else | else | ||||
writer.WriteValue(value.ToString()); | writer.WriteValue(value.ToString()); | ||||
} | } | ||||
@@ -15,7 +15,7 @@ namespace Discord.Serialization.Json.Converters | |||||
public void Write(PropertyMap map, ref JsonWriter writer, sbyte value, bool isTopLevel) | public void Write(PropertyMap map, ref JsonWriter writer, sbyte value, bool isTopLevel) | ||||
{ | { | ||||
if (isTopLevel) | if (isTopLevel) | ||||
writer.WriteAttribute(map.Key, value); | |||||
writer.WriteAttribute(map.Utf16Key, value); | |||||
else | else | ||||
writer.WriteValue(value); | writer.WriteValue(value); | ||||
} | } | ||||
@@ -34,7 +34,7 @@ namespace Discord.Serialization.Json.Converters | |||||
public void Write(PropertyMap map, ref JsonWriter writer, short value, bool isTopLevel) | public void Write(PropertyMap map, ref JsonWriter writer, short value, bool isTopLevel) | ||||
{ | { | ||||
if (isTopLevel) | if (isTopLevel) | ||||
writer.WriteAttribute(map.Key, value); | |||||
writer.WriteAttribute(map.Utf16Key, value); | |||||
else | else | ||||
writer.WriteValue(value); | writer.WriteValue(value); | ||||
} | } | ||||
@@ -53,7 +53,7 @@ namespace Discord.Serialization.Json.Converters | |||||
public void Write(PropertyMap map, ref JsonWriter writer, int value, bool isTopLevel) | public void Write(PropertyMap map, ref JsonWriter writer, int value, bool isTopLevel) | ||||
{ | { | ||||
if (isTopLevel) | if (isTopLevel) | ||||
writer.WriteAttribute(map.Key, value); | |||||
writer.WriteAttribute(map.Utf16Key, value); | |||||
else | else | ||||
writer.WriteValue(value); | writer.WriteValue(value); | ||||
} | } | ||||
@@ -72,7 +72,7 @@ namespace Discord.Serialization.Json.Converters | |||||
public void Write(PropertyMap map, ref JsonWriter writer, long value, bool isTopLevel) | public void Write(PropertyMap map, ref JsonWriter writer, long value, bool isTopLevel) | ||||
{ | { | ||||
if (isTopLevel) | if (isTopLevel) | ||||
writer.WriteAttribute(map.Key, value.ToString()); | |||||
writer.WriteAttribute(map.Utf16Key, value.ToString()); | |||||
else | else | ||||
writer.WriteValue(value.ToString()); | writer.WriteValue(value.ToString()); | ||||
} | } | ||||
@@ -34,7 +34,7 @@ namespace Discord.Serialization.Json.Converters | |||||
public void Write(PropertyMap map, ref JsonWriter writer, string value, bool isTopLevel) | public void Write(PropertyMap map, ref JsonWriter writer, string value, bool isTopLevel) | ||||
{ | { | ||||
if (isTopLevel) | if (isTopLevel) | ||||
writer.WriteAttribute(map.Key, value); | |||||
writer.WriteAttribute(map.Utf16Key, value); | |||||
else | else | ||||
writer.WriteValue(value); | writer.WriteValue(value); | ||||
} | } | ||||
@@ -15,7 +15,7 @@ namespace Discord.Serialization.Json.Converters | |||||
public void Write(PropertyMap map, ref JsonWriter writer, byte value, bool isTopLevel) | public void Write(PropertyMap map, ref JsonWriter writer, byte value, bool isTopLevel) | ||||
{ | { | ||||
if (isTopLevel) | if (isTopLevel) | ||||
writer.WriteAttribute(map.Key, value); | |||||
writer.WriteAttribute(map.Utf16Key, value); | |||||
else | else | ||||
writer.WriteValue(value); | writer.WriteValue(value); | ||||
} | } | ||||
@@ -34,7 +34,7 @@ namespace Discord.Serialization.Json.Converters | |||||
public void Write(PropertyMap map, ref JsonWriter writer, ushort value, bool isTopLevel) | public void Write(PropertyMap map, ref JsonWriter writer, ushort value, bool isTopLevel) | ||||
{ | { | ||||
if (isTopLevel) | if (isTopLevel) | ||||
writer.WriteAttribute(map.Key, value); | |||||
writer.WriteAttribute(map.Utf16Key, value); | |||||
else | else | ||||
writer.WriteValue(value); | writer.WriteValue(value); | ||||
} | } | ||||
@@ -53,7 +53,7 @@ namespace Discord.Serialization.Json.Converters | |||||
public void Write(PropertyMap map, ref JsonWriter writer, uint value, bool isTopLevel) | public void Write(PropertyMap map, ref JsonWriter writer, uint value, bool isTopLevel) | ||||
{ | { | ||||
if (isTopLevel) | if (isTopLevel) | ||||
writer.WriteAttribute(map.Key, value); | |||||
writer.WriteAttribute(map.Utf16Key, value); | |||||
else | else | ||||
writer.WriteValue(value); | writer.WriteValue(value); | ||||
} | } | ||||
@@ -72,7 +72,7 @@ namespace Discord.Serialization.Json.Converters | |||||
public void Write(PropertyMap map, ref JsonWriter writer, ulong value, bool isTopLevel) | public void Write(PropertyMap map, ref JsonWriter writer, ulong value, bool isTopLevel) | ||||
{ | { | ||||
if (isTopLevel) | if (isTopLevel) | ||||
writer.WriteAttribute(map.Key, value.ToString()); | |||||
writer.WriteAttribute(map.Utf16Key, value.ToString()); | |||||
else | else | ||||
writer.WriteValue(value.ToString()); | writer.WriteValue(value.ToString()); | ||||
} | } | ||||
@@ -1,10 +1,12 @@ | |||||
using System.Text.Json; | using System.Text.Json; | ||||
using System.Text.Utf8; | |||||
namespace Discord.Serialization | namespace Discord.Serialization | ||||
{ | { | ||||
internal interface IJsonPropertyMap<TModel> | internal interface IJsonPropertyMap<TModel> | ||||
{ | { | ||||
string Key { get; } | |||||
string Utf16Key { get; } | |||||
Utf8String Utf8Key { get; } | |||||
void Write(TModel model, ref JsonWriter writer); | void Write(TModel model, ref JsonWriter writer); | ||||
void Read(TModel model, ref JsonReader reader); | void Read(TModel model, ref JsonReader reader); | ||||
@@ -1,4 +1,5 @@ | |||||
using System.Collections.Generic; | |||||
using System; | |||||
using System.Collections.Generic; | |||||
using System.Linq; | using System.Linq; | ||||
namespace Discord.Serialization | namespace Discord.Serialization | ||||
@@ -7,9 +8,9 @@ namespace Discord.Serialization | |||||
where TModel : class, new() | where TModel : class, new() | ||||
{ | { | ||||
public readonly PropertyMap[] Properties; | public readonly PropertyMap[] Properties; | ||||
public readonly Dictionary<string, PropertyMap> PropertiesByKey; | |||||
public readonly Dictionary<ReadOnlySpan<byte>, PropertyMap> PropertiesByKey; | |||||
public ModelMap(Dictionary<string, PropertyMap> properties) | |||||
public ModelMap(Dictionary<ReadOnlySpan<byte>, PropertyMap> properties) | |||||
{ | { | ||||
PropertiesByKey = properties; | PropertiesByKey = properties; | ||||
Properties = PropertiesByKey.Values.ToArray(); | Properties = PropertiesByKey.Values.ToArray(); | ||||
@@ -1,17 +1,20 @@ | |||||
using System.Reflection; | using System.Reflection; | ||||
using System.Text.Utf8; | |||||
namespace Discord.Serialization | namespace Discord.Serialization | ||||
{ | { | ||||
public abstract class PropertyMap | public abstract class PropertyMap | ||||
{ | { | ||||
public string Key { get; } | |||||
public string Utf16Key { get; } | |||||
public Utf8String Utf8Key { get; } | |||||
public bool ExcludeNull { get; } | public bool ExcludeNull { get; } | ||||
public PropertyMap(PropertyInfo propInfo) | public PropertyMap(PropertyInfo propInfo) | ||||
{ | { | ||||
var jsonProperty = propInfo.GetCustomAttribute<ModelPropertyAttribute>(); | var jsonProperty = propInfo.GetCustomAttribute<ModelPropertyAttribute>(); | ||||
Key = jsonProperty?.Key ?? propInfo.Name; | |||||
Utf16Key = jsonProperty?.Key ?? propInfo.Name; | |||||
Utf8Key = new Utf8String(Utf16Key); | |||||
ExcludeNull = jsonProperty?.ExcludeNull ?? false; | ExcludeNull = jsonProperty?.ExcludeNull ?? false; | ||||
} | } | ||||
} | } | ||||
@@ -25,17 +25,15 @@ namespace Discord.Serialization | |||||
return _maps.GetOrAdd(typeof(TModel), _ => | return _maps.GetOrAdd(typeof(TModel), _ => | ||||
{ | { | ||||
var type = typeof(TModel).GetTypeInfo(); | var type = typeof(TModel).GetTypeInfo(); | ||||
var properties = new Dictionary<string, PropertyMap>(); | |||||
var propInfos = type.DeclaredProperties | |||||
.Where(x => x.CanRead && x.CanWrite) | |||||
.ToArray(); | |||||
var propInfos = type.DeclaredProperties.ToArray(); | |||||
var properties = new Dictionary<ReadOnlySpan<byte>, PropertyMap>(propInfos.Length, Utf8SpanComparer.Instance); | |||||
for (int i = 0; i < propInfos.Length; i++) | for (int i = 0; i < propInfos.Length; i++) | ||||
{ | { | ||||
var propInfo = propInfos[i]; | |||||
if (!propInfo.CanRead || !propInfo.CanWrite) | |||||
continue; | |||||
var propMap = MapProperty<TModel>(propInfo); | |||||
properties.Add(propMap.Key, propMap); | |||||
var propMap = MapProperty<TModel>(propInfos[i]); | |||||
properties.Add(propMap.Utf8Key, propMap); | |||||
} | } | ||||
return new ModelMap<TModel>(properties); | return new ModelMap<TModel>(properties); | ||||
}) as ModelMap<TModel>; | }) as ModelMap<TModel>; | ||||
@@ -0,0 +1,42 @@ | |||||
using System; | |||||
using System.Collections.Generic; | |||||
namespace Discord.Serialization | |||||
{ | |||||
internal class Utf8SpanComparer : IEqualityComparer<ReadOnlySpan<byte>> | |||||
{ | |||||
public static readonly Utf8SpanComparer Instance = new Utf8SpanComparer(); | |||||
public bool Equals(ReadOnlySpan<byte> x, ReadOnlySpan<byte> y) => x.SequenceEqual(y); | |||||
public int GetHashCode(ReadOnlySpan<byte> 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; | |||||
} | |||||
} | |||||
} | |||||
} | |||||
} |