@@ -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); | |||
} | |||
@@ -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()); | |||
} | |||
@@ -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()); | |||
} | |||
@@ -65,42 +65,48 @@ namespace Discord.Serialization | |||
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) | |||
=> _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) | |||
{ | |||
if (isTopLevel) | |||
writer.WriteArrayStart(map.Key); | |||
writer.WriteArrayStart(map.Utf16Key); | |||
else | |||
writer.WriteArrayStart(); | |||
for (int i = 0; i < value.Count; i++) | |||
@@ -28,7 +28,7 @@ namespace Discord.Serialization.Json.Converters | |||
else | |||
{ | |||
if (isTopLevel) | |||
writer.WriteAttributeNull(map.Key); | |||
writer.WriteAttributeNull(map.Utf16Key); | |||
else | |||
writer.WriteNull(); | |||
} | |||
@@ -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<T>).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++) | |||
@@ -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); | |||
} | |||
@@ -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()); | |||
} | |||
@@ -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()); | |||
} | |||
@@ -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()); | |||
} | |||
@@ -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); | |||
} | |||
@@ -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()); | |||
} | |||
@@ -1,10 +1,12 @@ | |||
using System.Text.Json; | |||
using System.Text.Utf8; | |||
namespace Discord.Serialization | |||
{ | |||
internal interface IJsonPropertyMap<TModel> | |||
{ | |||
string Key { get; } | |||
string Utf16Key { get; } | |||
Utf8String Utf8Key { get; } | |||
void Write(TModel model, ref JsonWriter writer); | |||
void Read(TModel model, ref JsonReader reader); | |||
@@ -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<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; | |||
Properties = PropertiesByKey.Values.ToArray(); | |||
@@ -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<ModelPropertyAttribute>(); | |||
Key = jsonProperty?.Key ?? propInfo.Name; | |||
Utf16Key = jsonProperty?.Key ?? propInfo.Name; | |||
Utf8Key = new Utf8String(Utf16Key); | |||
ExcludeNull = jsonProperty?.ExcludeNull ?? false; | |||
} | |||
} | |||
@@ -25,17 +25,15 @@ namespace Discord.Serialization | |||
return _maps.GetOrAdd(typeof(TModel), _ => | |||
{ | |||
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++) | |||
{ | |||
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); | |||
}) 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; | |||
} | |||
} | |||
} | |||
} | |||
} |