Skip to content

Commit

Permalink
Serialization: improved support for custom expando objects
Browse files Browse the repository at this point in the history
  • Loading branch information
snakefoot authored and 304NotModified committed May 28, 2019
1 parent 04cb3a0 commit 458feb4
Show file tree
Hide file tree
Showing 9 changed files with 628 additions and 329 deletions.
201 changes: 156 additions & 45 deletions src/NLog/Internal/ObjectReflectionCache.cs
Expand Up @@ -53,6 +53,34 @@ internal class ObjectReflectionCache

public ObjectPropertyList LookupObjectProperties(object value)
{
if (TryLookupExpandoObject(value, out var propertyValues))
{
return propertyValues;
}

var objectType = value.GetType();
var propertyInfos = BuildObjectPropertyInfos(value, objectType);
_objectTypeCache.TryAddValue(objectType, propertyInfos);
return new ObjectPropertyList(value, propertyInfos.Properties, propertyInfos.FastLookup);
}

public bool TryLookupExpandoObject(object value, out ObjectPropertyList objectPropertyList)
{
if (value is IDictionary<string, object> expando)
{
objectPropertyList = new ObjectPropertyList(expando);
return true;
}

#if DYNAMIC_OBJECT
if (value is DynamicObject d)
{
var dictionary = DynamicObjectToDict(d);
objectPropertyList = new ObjectPropertyList(dictionary);
return true;
}
#endif

Type objectType = value.GetType();
if (_objectTypeCache.TryGetValue(objectType, out var propertyInfos))
{
Expand All @@ -62,7 +90,8 @@ public ObjectPropertyList LookupObjectProperties(object value)
propertyInfos = new ObjectPropertyInfos(propertyInfos.Properties, fastLookup);
_objectTypeCache.TryAddValue(objectType, propertyInfos);
}
return new ObjectPropertyList(value, propertyInfos.Properties, propertyInfos.FastLookup);
objectPropertyList = new ObjectPropertyList(value, propertyInfos.Properties, propertyInfos.FastLookup);
return true;
}

if (_objectTypeOverride.Count > 0)
Expand All @@ -74,26 +103,47 @@ public ObjectPropertyList LookupObjectProperties(object value)
{
var objectProperties = objectReflection.Invoke(value);
if (objectProperties?.Count > 0)
return new ObjectPropertyList(objectProperties);
{
objectPropertyList = new ObjectPropertyList(objectProperties);
return true;
}

// object.ToString() since no properties
propertyInfos = ObjectPropertyInfos.SimpleToString;
return new ObjectPropertyList(value, propertyInfos.Properties, propertyInfos.FastLookup);
objectPropertyList = new ObjectPropertyList(value, propertyInfos.Properties, propertyInfos.FastLookup);
return true;
}
}
}

#if DYNAMIC_OBJECT
if (value is DynamicObject d)
if (TryExtractExpandoObject(objectType, out propertyInfos))
{
var dictionary = DynamicObjectToDict(d);
return new ObjectPropertyList(dictionary);
_objectTypeCache.TryAddValue(objectType, propertyInfos);
objectPropertyList = new ObjectPropertyList(value, propertyInfos.Properties, propertyInfos.FastLookup);
return true;
}
#endif

propertyInfos = BuildObjectPropertyInfos(value, objectType);
_objectTypeCache.TryAddValue(objectType, propertyInfos);
return new ObjectPropertyList(value, propertyInfos.Properties, propertyInfos.FastLookup);
objectPropertyList = default(ObjectPropertyList);
return false;
}

private static bool TryExtractExpandoObject(Type objectType, out ObjectPropertyInfos propertyInfos)
{
foreach (var interfaceType in objectType.GetInterfaces())
{
if (interfaceType.IsGenericType() && interfaceType.GetGenericTypeDefinition() == typeof(IDictionary<,>))
{
if (interfaceType.GetGenericArguments()[0] == typeof(string))
{
var dictionaryEnumerator = (IDictionaryEnumerator)Activator.CreateInstance(typeof(DictionaryEnumerator<,>).MakeGenericType(interfaceType.GetGenericArguments()));
propertyInfos = new ObjectPropertyInfos(null, new[] { new FastPropertyLookup(string.Empty, TypeCode.Object, (o, p) => dictionaryEnumerator.GetEnumerator(o)) });
return true;
}
}
}

propertyInfos = default(ObjectPropertyInfos);
return false;
}

private static ObjectPropertyInfos BuildObjectPropertyInfos(object value, Type objectType)
Expand Down Expand Up @@ -207,6 +257,7 @@ private static FastPropertyLookup[] BuildFastLookup(PropertyInfo[] properties, b
public struct ObjectPropertyList : IEnumerable<ObjectPropertyList.PropertyValue>
{
internal static readonly StringComparer NameComparer = StringComparer.Ordinal;
private static readonly FastPropertyLookup[] CreateIDictionaryEnumerator = new[] { new FastPropertyLookup(string.Empty, TypeCode.Object, (o, p) => ((IDictionary<string, object>)o).GetEnumerator()) };
private readonly object _object;
private readonly PropertyInfo[] _properties;
private readonly FastPropertyLookup[] _fastLookup;
Expand All @@ -215,19 +266,7 @@ public struct PropertyValue
{
public readonly string Name;
public readonly object Value;
public TypeCode TypeCode
{
get
{
if (_typecode == TypeCode.Object)
{
return Convert.GetTypeCode(Value);
}

return Value == null ? TypeCode.Empty : _typecode;
}
}

public TypeCode TypeCode => Value == null ? TypeCode.Empty : _typecode;
private readonly TypeCode _typecode;

public PropertyValue(string name, object value, TypeCode typeCode)
Expand All @@ -252,7 +291,7 @@ public PropertyValue(object owner, FastPropertyLookup fastProperty)
}
}

public int Count => _fastLookup?.Length ?? _properties?.Length ?? (_object as ICollection)?.Count ?? (_object as ICollection<KeyValuePair<string, object>>)?.Count ?? 0;
public bool ConvertToString => _properties?.Length == 0;

internal ObjectPropertyList(object value, PropertyInfo[] properties, FastPropertyLookup[] fastLookup)
{
Expand All @@ -265,38 +304,85 @@ public ObjectPropertyList(IDictionary<string, object> value)
{
_object = value; // Expando objects
_properties = null;
_fastLookup = null;
_fastLookup = CreateIDictionaryEnumerator;
}

public bool TryGetPropertyValue(string name, out PropertyValue propertyValue)
{
if (_fastLookup != null)
if (_properties != null)
{
int nameHashCode = NameComparer.GetHashCode(name);
foreach (var fastProperty in _fastLookup)
if (_fastLookup != null)
{
if (fastProperty.NameHashCode==nameHashCode && NameComparer.Equals(fastProperty.Name, name))
{
propertyValue = new PropertyValue(_object, fastProperty);
return true;
}
return TryFastLookupPropertyValue(name, out propertyValue);
}
else
{
return TrySlowLookupPropertyValue(name, out propertyValue);
}
}
else if (_properties != null)
else if (_object is IDictionary<string, object> expandoObject)
{
foreach (var propInfo in _properties)
if (expandoObject.TryGetValue(name, out var objectValue))
{
if (NameComparer.Equals(propInfo.Name, name))
{
propertyValue = new PropertyValue(_object, propInfo);
return true;
}
propertyValue = new PropertyValue(name, objectValue, TypeCode.Object);
return true;
}
propertyValue = default(PropertyValue);
return false;
}
else
{
return TryListLookupPropertyValue(name, out propertyValue);
}
else if (_object is IDictionary<string, object> expandoObject && expandoObject.TryGetValue(name, out var objectValue))
}

/// <summary>
/// Scans properties for name (Skips string-compare and value-lookup until finding match)
/// </summary>
private bool TryFastLookupPropertyValue(string name, out PropertyValue propertyValue)
{
int nameHashCode = NameComparer.GetHashCode(name);
foreach (var fastProperty in _fastLookup)
{
propertyValue = new PropertyValue(name, objectValue, TypeCode.Object);
return true;
if (fastProperty.NameHashCode == nameHashCode && NameComparer.Equals(fastProperty.Name, name))
{
propertyValue = new PropertyValue(_object, fastProperty);
return true;
}
}
propertyValue = default(PropertyValue);
return false;
}

/// <summary>
/// Scans properties for name (Skips property value lookup until finding match)
/// </summary>
private bool TrySlowLookupPropertyValue(string name, out PropertyValue propertyValue)
{
foreach (var propInfo in _properties)
{
if (NameComparer.Equals(propInfo.Name, name))
{
propertyValue = new PropertyValue(_object, propInfo);
return true;
}
}
propertyValue = default(PropertyValue);
return false;
}

/// <summary>
/// Scans properties for name
/// </summary>
private bool TryListLookupPropertyValue(string name, out PropertyValue propertyValue)
{
foreach (var item in this)
{
if (NameComparer.Equals(item.Name, name))
{
propertyValue = item;
return true;
}
}
propertyValue = default(PropertyValue);
return false;
Expand All @@ -312,7 +398,7 @@ public Enumerator GetEnumerator()
if (_properties != null)
return new Enumerator(_object, _properties, _fastLookup);
else
return new Enumerator((_object as IDictionary<string, object>).GetEnumerator());
return new Enumerator((IEnumerator<KeyValuePair<string, object>>)_fastLookup[0].ValueLookup(_object, null));
}

IEnumerator<PropertyValue> IEnumerable<PropertyValue>.GetEnumerator() => GetEnumerator();
Expand Down Expand Up @@ -459,5 +545,30 @@ public override DynamicMetaObject FallbackGetMember(DynamicMetaObject target, Dy
}
}
#endif

private interface IDictionaryEnumerator
{
IEnumerator<KeyValuePair<string,object>> GetEnumerator(object value);
}

private static readonly IDictionary<string, object> EmptyDictionary = new NLog.Internal.SortHelpers.ReadOnlySingleBucketDictionary<string, object>();

internal sealed class DictionaryEnumerator<TKey, TValue> : IDictionaryEnumerator
{
public IEnumerator<KeyValuePair<string, object>> GetEnumerator(object value)
{
if (value is IDictionary<TKey, TValue> dictionary)
{
return YieldEnumerator(dictionary);
}
return EmptyDictionary.GetEnumerator();
}

private IEnumerator<KeyValuePair<string, object>> YieldEnumerator(IDictionary<TKey,TValue> dictionary)
{
foreach (var item in dictionary)
yield return new KeyValuePair<string, object>(item.Key.ToString(), item.Value);
}
}
}
}
10 changes: 1 addition & 9 deletions src/NLog/Internal/PropertiesDictionary.cs
Expand Up @@ -466,15 +466,7 @@ protected MessageTemplateParameter CurrentParameter
}
if (_eventEnumeratorCreated)
{
string parameterName;
try
{
parameterName = XmlHelper.XmlConvertToString(_eventEnumerator.Current.Key ?? string.Empty);
}
catch
{
parameterName = "";
}
string parameterName = XmlHelper.XmlConvertToString(_eventEnumerator.Current.Key ?? string.Empty) ?? string.Empty;
return new MessageTemplateParameter(parameterName, _eventEnumerator.Current.Value.Value, null, CaptureType.Unknown);
}
throw new InvalidOperationException();
Expand Down
19 changes: 10 additions & 9 deletions src/NLog/Internal/StringBuilderExt.cs
Expand Up @@ -32,6 +32,7 @@
//

using System;
using System.Globalization;
using System.IO;
using System.Text;
using NLog.MessageTemplates;
Expand Down Expand Up @@ -333,28 +334,28 @@ internal static void Append4DigitsZeroPadded(this StringBuilder builder, int num
/// <summary>
/// Apend a int type (byte, int) as string
/// </summary>
internal static void AppendIntegerAsString(this StringBuilder sb, object value, TypeCode objTypeCode)
internal static void AppendIntegerAsString(this StringBuilder sb, IConvertible value, TypeCode objTypeCode)
{
switch (objTypeCode)
{
case TypeCode.Byte: sb.AppendInvariant((byte)value); break;
case TypeCode.SByte: sb.AppendInvariant((sbyte)value); break;
case TypeCode.Int16: sb.AppendInvariant((short)value); break;
case TypeCode.Int32: sb.AppendInvariant((int)value); break;
case TypeCode.Byte: sb.AppendInvariant(value.ToByte(CultureInfo.InvariantCulture)); break;
case TypeCode.SByte: sb.AppendInvariant(value.ToSByte(CultureInfo.InvariantCulture)); break;
case TypeCode.Int16: sb.AppendInvariant(value.ToInt16(CultureInfo.InvariantCulture)); break;
case TypeCode.Int32: sb.AppendInvariant(value.ToInt32(CultureInfo.InvariantCulture)); break;
case TypeCode.Int64:
{
long int64 = (long)value;
long int64 = value.ToInt64(CultureInfo.InvariantCulture);
if (int64 < int.MaxValue && int64 > int.MinValue)
sb.AppendInvariant((int)int64);
else
sb.Append(int64);
}
break;
case TypeCode.UInt16: sb.AppendInvariant((ushort)value); break;
case TypeCode.UInt32: sb.AppendInvariant((uint)value); break;
case TypeCode.UInt16: sb.AppendInvariant(value.ToUInt16(CultureInfo.InvariantCulture)); break;
case TypeCode.UInt32: sb.AppendInvariant(value.ToUInt32(CultureInfo.InvariantCulture)); break;
case TypeCode.UInt64:
{
ulong uint64 = (ulong)value;
ulong uint64 = value.ToUInt64(CultureInfo.InvariantCulture);
if (uint64 < uint.MaxValue)
sb.AppendInvariant((uint)uint64);
else
Expand Down

0 comments on commit 458feb4

Please sign in to comment.