Upstream NLog/NLog state of the touched files before cc54b6d1e1

This commit is contained in:
upstream committed 2026-09-02 01:50:51 +02:00
1 parent 44df43fabd
commit b78676e244
4 files changed
+1817

No files matched your search

+816
View File
@@ -0,0 +1,816 @@
//
// Copyright (c) 2004-2024 Jaroslaw Kowalski <jaak@jkowalski.net>, Kim Christensen, Julian Verdurmen
//
// All rights reserved.
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions
// are met:
//
// * Redistributions of source code must retain the above copyright notice,
// this list of conditions and the following disclaimer.
//
// * Redistributions in binary form must reproduce the above copyright notice,
// this list of conditions and the following disclaimer in the documentation
// and/or other materials provided with the distribution.
//
// * Neither the name of Jaroslaw Kowalski nor the names of its
// contributors may be used to endorse or promote products derived from this
// software without specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
// THE POSSIBILITY OF SUCH DAMAGE.
//
namespace NLog.Internal
{
using System;
using System.Collections;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.Linq;
using System.Reflection;
using NLog.Common;
using NLog.Config;
/// <summary>
/// Converts object into a List of property-names and -values using reflection
/// </summary>
internal sealed class ObjectReflectionCache : IObjectTypeTransformer
{
private MruCache<Type, ObjectPropertyInfos> ObjectTypeCache => _objectTypeCache ?? System.Threading.Interlocked.CompareExchange(ref _objectTypeCache, new MruCache<Type, ObjectPropertyInfos>(10000), null) ?? _objectTypeCache;
private MruCache<Type, ObjectPropertyInfos>? _objectTypeCache;
private readonly IServiceProvider _serviceProvider;
private IObjectTypeTransformer ObjectTypeTransformation => _objectTypeTransformation ?? (_objectTypeTransformation = _serviceProvider?.GetService<IObjectTypeTransformer>() ?? this);
private IObjectTypeTransformer? _objectTypeTransformation;
public ObjectReflectionCache(IServiceProvider serviceProvider)
{
_serviceProvider = serviceProvider;
}
object? IObjectTypeTransformer.TryTransformObject(object? obj)
{
return null;
}
public ObjectPropertyList LookupObjectProperties(object value)
{
if (TryLookupExpandoObject(value, out var propertyValues))
{
return propertyValues;
}
if (!ReferenceEquals(ObjectTypeTransformation, this))
{
var result = ObjectTypeTransformation.TryTransformObject(value);
if (result != null)
{
if (ConvertSimpleToString(result))
{
return new ObjectPropertyList(result, ObjectPropertyInfos.SimpleToString.Properties, ObjectPropertyInfos.SimpleToString.FastLookup);
}
if (TryLookupExpandoObject(result, out propertyValues))
{
return propertyValues;
}
value = result;
}
}
var objectType = value.GetType();
var propertyInfos = CheckForObjectProperties(value) ? BuildObjectPropertyInfos(value) : ObjectPropertyInfos.SimpleToString;
ObjectTypeCache.TryAddValue(objectType, propertyInfos);
return new ObjectPropertyList(value, propertyInfos.Properties, propertyInfos.FastLookup);
}
/// <summary>
/// Try get value from <paramref name="value"/>, using <paramref name="objectPath"/>, and set into <paramref name="foundValue"/>
/// </summary>
public bool TryGetObjectProperty(object? value, string[]? objectPath, out object? foundValue)
{
foundValue = null;
if (objectPath is null)
{
return false;
}
for (int i = 0; i < objectPath.Length; ++i)
{
if (value is null)
{
return false;
}
var propertyList = LookupObjectProperties(value);
if (propertyList.TryGetPropertyValue(objectPath[i], out var propertyValue))
{
value = propertyValue.Value;
}
else
{
foundValue = null;
return false; //Wrong, but done
}
}
foundValue = value;
return true;
}
public bool TryLookupExpandoObject(object value, out ObjectPropertyList objectPropertyList)
{
if (value is IDictionary<string, object> expando)
{
objectPropertyList = new ObjectPropertyList(expando);
return true;
}
#if !NET35 && !NET40 && NETFRAMEWORK
if (value is System.Dynamic.DynamicObject d)
{
var dictionary = DynamicObjectToDict(d);
objectPropertyList = new ObjectPropertyList(dictionary);
return true;
}
#endif
Type objectType = value.GetType();
if (ObjectTypeCache.TryGetValue(objectType, out var propertyInfos))
{
if (!propertyInfos.HasFastLookup)
{
var fastLookup = BuildFastLookup(propertyInfos.Properties, false);
propertyInfos = new ObjectPropertyInfos(propertyInfos.Properties, fastLookup);
ObjectTypeCache.TryAddValue(objectType, propertyInfos);
}
objectPropertyList = new ObjectPropertyList(value, propertyInfos.Properties, propertyInfos.FastLookup);
return true;
}
var dictionaryEnumerator = TryGetDictionaryEnumerator(value);
if (dictionaryEnumerator != null)
{
propertyInfos = new ObjectPropertyInfos(ArrayHelper.Empty<PropertyInfo>(), new[] { new FastPropertyLookup(string.Empty, TypeCode.Object, (o, p) => dictionaryEnumerator.GetEnumerator(o)) });
ObjectTypeCache.TryAddValue(objectType, propertyInfos);
objectPropertyList = new ObjectPropertyList(value, propertyInfos.Properties, propertyInfos.FastLookup);
return true;
}
objectPropertyList = default(ObjectPropertyList);
return false;
}
[UnconditionalSuppressMessage("Trimming - Allow reflection of message args", "IL2072")]
private static ObjectPropertyInfos BuildObjectPropertyInfos(object value)
{
var properties = GetPublicProperties(value.GetType());
if (value is Exception)
{
// Special handling of Exception (Include Exception-Type as artificial first property)
var fastLookup = BuildFastLookup(properties, true);
return new ObjectPropertyInfos(properties, fastLookup);
}
else if (properties.Length == 0)
{
return ObjectPropertyInfos.SimpleToString;
}
else
{
return new ObjectPropertyInfos(properties, null);
}
}
private static bool CheckForObjectProperties(object objectValue)
{
if (ConvertSimpleToString(objectValue))
return false;
if (objectValue is Uri)
return false;
if (objectValue is Delegate)
return false; // Skip serializing all types in the application
if (objectValue is MemberInfo)
return false; // Skip serializing all types in the application
if (objectValue is Assembly)
return false; // Skip serializing all types in the application
if (objectValue is Module)
return false; // Skip serializing all types in the application
if (objectValue is System.IO.Stream)
return false; // Skip serializing properties that often throws exceptions
return true;
}
private static bool ConvertSimpleToString(object objectValue)
{
if (objectValue is IFormattable)
return true;
if (objectValue is IConvertible convertible)
return convertible.GetTypeCode() != TypeCode.Object;
return false;
}
private static PropertyInfo[] GetPublicProperties([DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicProperties)] Type type)
{
PropertyInfo[]? properties = null;
try
{
properties = type.GetProperties(BindingFlags.Instance | BindingFlags.Public);
}
catch (Exception ex)
{
InternalLogger.Warn(ex, "Failed to get object properties for type: {0}", type);
}
// Skip Index-Item-Properties (Ex. public string this[int Index])
if (properties != null)
{
foreach (var prop in properties)
{
if (!prop.IsValidPublicProperty())
{
properties = properties.Where(p => p.IsValidPublicProperty()).ToArray();
break;
}
}
}
return properties ?? ArrayHelper.Empty<PropertyInfo>();
}
private static FastPropertyLookup[] BuildFastLookup(PropertyInfo[] properties, bool includeType)
{
int fastAccessIndex = includeType ? 1 : 0;
FastPropertyLookup[] fastLookup = new FastPropertyLookup[properties.Length + fastAccessIndex];
if (includeType)
{
fastLookup[0] = new FastPropertyLookup("Type", TypeCode.String, (o, p) => o.GetType().ToString());
}
foreach (var prop in properties)
{
var getterMethod = prop.GetGetMethod();
var propertyType = getterMethod?.ReturnType;
if (getterMethod is null || propertyType is null)
{
// Should never happen due to IsValidPublicProperty check
fastLookup[fastAccessIndex++] = new FastPropertyLookup(prop.Name, TypeCode.String, (o, p) => null);
}
else
{
ReflectionHelpers.LateBoundMethod valueLookup = ReflectionHelpers.CreateLateBoundMethod(getterMethod);
TypeCode typeCode = Type.GetTypeCode(propertyType); // Skip cyclic-reference checks when not TypeCode.Object
fastLookup[fastAccessIndex++] = new FastPropertyLookup(prop.Name, typeCode, valueLookup);
}
}
return fastLookup;
}
internal
#if !NETFRAMEWORK
readonly
#endif
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;
public
#if !NETFRAMEWORK
readonly
#endif
struct PropertyValue
{
public readonly string Name;
public readonly object? Value;
public TypeCode TypeCode => Value is null ? TypeCode.Empty : _typecode;
private readonly TypeCode _typecode;
public bool HasNameAndValue => Name != null && Value != null;
public PropertyValue(string name, object value, TypeCode typeCode)
{
Name = name;
Value = value;
_typecode = typeCode;
}
public PropertyValue(object owner, PropertyInfo propertyInfo)
{
Name = propertyInfo.Name;
_typecode = TypeCode.Object;
try
{
Value = propertyInfo.GetValue(owner, null);
}
catch
{
Value = null;
}
}
public PropertyValue(object owner, FastPropertyLookup fastProperty)
{
Name = fastProperty.Name;
_typecode = fastProperty.TypeCode;
try
{
Value = fastProperty.ValueLookup(owner, ArrayHelper.Empty<object>());
}
catch
{
Value = null;
}
}
public override string ToString() => $"{Name}={Value}";
}
public bool IsSimpleValue => _properties.Length == 0 && (_fastLookup is null || _fastLookup.Length == 0);
public object ObjectValue => _object;
internal ObjectPropertyList(object value, PropertyInfo[] properties, FastPropertyLookup[]? fastLookup)
{
_object = value;
_properties = properties;
_fastLookup = fastLookup;
}
public ObjectPropertyList(IDictionary<string, object> value)
{
_object = value; // Expando objects
_properties = ArrayHelper.Empty<PropertyInfo>();
_fastLookup = CreateIDictionaryEnumerator;
}
public bool TryGetPropertyValue(string name, out PropertyValue propertyValue)
{
if (_properties.Length == 0)
{
if (_object is IDictionary<string, object> expandoObject)
{
if (expandoObject.TryGetValue(name, out var objectValue))
{
propertyValue = new PropertyValue(name, objectValue, TypeCode.Object);
return true;
}
propertyValue = default(PropertyValue);
return false;
}
return TryListLookupPropertyValue(name, out propertyValue);
}
if (_fastLookup != null)
{
// Scans properties for name (Skips string-compare and value-lookup until finding match)
int nameHashCode = NameComparer.GetHashCode(name);
foreach (var fastProperty in _fastLookup)
{
if (fastProperty.NameHashCode == nameHashCode && NameComparer.Equals(fastProperty.Name, name))
{
propertyValue = new PropertyValue(_object, fastProperty);
return true;
}
}
propertyValue = default(PropertyValue);
return false;
}
return TrySlowLookupPropertyValue(name, out propertyValue);
}
/// <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;
}
public override string ToString()
{
return _object?.ToString() ?? "null";
}
public Enumerator GetEnumerator()
{
if (_properties.Length > 0 || _fastLookup is null || _fastLookup.Length == 0)
return new Enumerator(_object, _properties, _fastLookup);
var expandoObject = _fastLookup[0].ValueLookup(_object, ArrayHelper.Empty<object>());
return expandoObject is null ? default(Enumerator) : new Enumerator((IEnumerator<KeyValuePair<string, object>>)expandoObject);
}
IEnumerator<PropertyValue> IEnumerable<PropertyValue>.GetEnumerator() => GetEnumerator();
IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
public struct Enumerator : IEnumerator<PropertyValue>
{
private readonly object _owner;
private readonly PropertyInfo[]? _properties;
private readonly FastPropertyLookup[]? _fastLookup;
private readonly IEnumerator<KeyValuePair<string, object>>? _enumerator;
private int _index;
internal Enumerator(object owner, PropertyInfo[] properties, FastPropertyLookup[]? fastLookup)
{
_owner = owner;
_properties = properties;
_fastLookup = fastLookup;
_index = -1;
_enumerator = null;
}
internal Enumerator(IEnumerator<KeyValuePair<string, object>> enumerator)
{
_owner = enumerator;
_properties = null;
_fastLookup = null;
_index = 0;
_enumerator = enumerator;
}
public PropertyValue Current
{
get
{
try
{
if (_fastLookup != null)
return new PropertyValue(_owner, _fastLookup[_index]);
else if (_properties != null)
return new PropertyValue(_owner, _properties[_index]);
else if (_enumerator != null)
return new PropertyValue(_enumerator.Current.Key, _enumerator.Current.Value, TypeCode.Object);
else
return default(PropertyValue);
}
catch (Exception ex)
{
InternalLogger.Debug(ex, "Failed to get property value for object: {0}", _owner);
return default(PropertyValue);
}
}
}
object IEnumerator.Current => Current;
public void Dispose()
{
_enumerator?.Dispose();
}
public bool MoveNext()
{
if (_properties != null)
return ++_index < (_fastLookup?.Length ?? _properties.Length);
else
return _enumerator?.MoveNext() == true;
}
public void Reset()
{
if (_properties != null)
_index = -1;
else
_enumerator?.Reset();
}
}
}
internal
#if !NETFRAMEWORK
readonly
#endif
struct FastPropertyLookup
{
public readonly string Name;
public readonly ReflectionHelpers.LateBoundMethod ValueLookup;
public readonly TypeCode TypeCode;
public readonly int NameHashCode;
public FastPropertyLookup(string name, TypeCode typeCode, ReflectionHelpers.LateBoundMethod valueLookup)
{
Name = name;
ValueLookup = valueLookup;
TypeCode = typeCode;
NameHashCode = ObjectPropertyList.NameComparer.GetHashCode(name);
}
}
private
#if !NETFRAMEWORK
readonly
#endif
struct ObjectPropertyInfos : IEquatable<ObjectPropertyInfos>
{
public readonly PropertyInfo[] Properties;
public readonly FastPropertyLookup[]? FastLookup;
public static readonly ObjectPropertyInfos SimpleToString = new ObjectPropertyInfos(ArrayHelper.Empty<PropertyInfo>(), ArrayHelper.Empty<FastPropertyLookup>());
public ObjectPropertyInfos(PropertyInfo[] properties, FastPropertyLookup[]? fastLookup)
{
Properties = properties;
FastLookup = fastLookup;
}
public bool HasFastLookup => FastLookup != null;
public bool Equals(ObjectPropertyInfos other)
{
return ReferenceEquals(Properties, other.Properties) && FastLookup?.Length == other.FastLookup?.Length;
}
}
#if !NET35 && !NET40 && NETFRAMEWORK
private static Dictionary<string, object> DynamicObjectToDict(System.Dynamic.DynamicObject d)
{
var newVal = new Dictionary<string, object>();
foreach (var propName in d.GetDynamicMemberNames())
{
if (d.TryGetMember(new GetBinderAdapter(propName), out var result))
{
newVal[propName] = result;
}
}
return newVal;
}
/// <summary>
/// Binder for retrieving value of <see cref="System.Dynamic.DynamicObject"/>
/// </summary>
private sealed class GetBinderAdapter : System.Dynamic.GetMemberBinder
{
internal GetBinderAdapter(string name)
: base(name, false)
{
}
/// <inheritdoc/>
public override System.Dynamic.DynamicMetaObject FallbackGetMember(System.Dynamic.DynamicMetaObject target, System.Dynamic.DynamicMetaObject errorSuggestion)
{
return target;
}
}
#endif
private static IDictionaryEnumerator? TryGetDictionaryEnumerator(object value)
{
if (!(value is IEnumerable) || value is string)
return null;
if (value is IDictionary<string, object>)
return new DictionaryEnumerator<string, object>();
if (value is IDictionary<string, string>)
return new DictionaryEnumerator<string, string>();
#if !NET35 && !NET40
if (value is IReadOnlyDictionary<string, object>)
return new DictionaryEnumerator<string, object>();
if (value is IReadOnlyDictionary<string, string>)
return new DictionaryEnumerator<string, string>();
#endif
if (value is IDictionary && value.GetType().IsGenericType)
{
if (value.GetType().GetGenericArguments()[0] == typeof(string))
return new DictionaryEnumerator(DictionaryEnumerator.BuildDictionaryEnumerator);
else
return null;
}
return TryBuildDictionaryEnumerator(value);
}
[UnconditionalSuppressMessage("Trimming - Allow reflection of message args", "IL2075")]
private static IDictionaryEnumerator? TryBuildDictionaryEnumerator(object value)
{
foreach (var interfaceType in value.GetType().GetInterfaces())
{
if (interfaceType.IsGenericType
&& (interfaceType.GetGenericTypeDefinition() == typeof(IDictionary<,>)
#if !NET35 && !NET40
|| interfaceType.GetGenericTypeDefinition() == typeof(IReadOnlyDictionary<,>)
#endif
)
&& interfaceType.GetGenericArguments()[0] == typeof(string))
{
#if NETFRAMEWORK
return (IDictionaryEnumerator)Activator.CreateInstance(typeof(DictionaryEnumerator<,>).MakeGenericType(interfaceType.GetGenericArguments()));
#else
// AOT does not support creating new types at runtime
foreach (var subInterfaceType in value.GetType().GetInterfaces())
{
if (subInterfaceType.IsGenericType
&& subInterfaceType.GetGenericTypeDefinition() == typeof(IEnumerable<>)
&& subInterfaceType.GetGenericArguments()[0].IsGenericType
&& subInterfaceType.GetGenericArguments()[0].GetGenericTypeDefinition() == typeof(KeyValuePair<,>)
&& subInterfaceType.GetGenericArguments()[0].GetGenericArguments()[0] == typeof(string))
{
var enumerator = DictionaryEnumerator.BuildEnumerator(subInterfaceType, subInterfaceType.GetGenericArguments()[0]);
if (enumerator != null)
return new DictionaryEnumerator(enumerator);
}
}
#endif
}
}
return null;
}
private interface IDictionaryEnumerator
{
IEnumerator<KeyValuePair<string, object?>> GetEnumerator(object value);
}
private sealed class DictionaryEnumerator : IDictionaryEnumerator
{
private readonly Func<object, IEnumerator<KeyValuePair<string, object?>>> _enumerateCollection;
public DictionaryEnumerator(Func<object, IEnumerator<KeyValuePair<string, object?>>> enumerateCollection)
{
_enumerateCollection = enumerateCollection;
}
IEnumerator<KeyValuePair<string, object?>> IDictionaryEnumerator.GetEnumerator(object value)
{
return _enumerateCollection(value);
}
public static IEnumerator<KeyValuePair<string, object?>> BuildDictionaryEnumerator(object collection)
{
if (collection is IDictionary dictionary)
{
if (dictionary.Count == 0)
return EmptyDictionaryEnumerator.Default;
return YieldEnumerator(dictionary);
}
return EmptyDictionaryEnumerator.Default;
}
private static IEnumerator<KeyValuePair<string, object?>> YieldEnumerator(IDictionary dictionary)
{
foreach (var item in new DictionaryEntryEnumerable(dictionary))
yield return new KeyValuePair<string, object?>(item.Key?.ToString() ?? string.Empty, item.Value);
}
#if !NETFRAMEWORK
[UnconditionalSuppressMessage("Trimming - Allow reflection of message args", "IL2075")]
[UnconditionalSuppressMessage("Trimming - Allow reflection of message args", "IL2070")]
public static Func<object, IEnumerator<KeyValuePair<string, object?>>>? BuildEnumerator(Type dictionaryType, Type dictionaryItemType)
{
var enumeratorMethod = dictionaryType.GetMethod(nameof(IEnumerable.GetEnumerator));
var getKeyMethod = dictionaryItemType.GetProperty(nameof(KeyValuePair<string, object>.Key))?.GetGetMethod();
var getValueMethod = dictionaryItemType.GetProperty(nameof(KeyValuePair<string, object>.Value))?.GetGetMethod();
if (enumeratorMethod != null && getKeyMethod != null && getValueMethod != null)
{
var enumeratorGetter = ReflectionHelpers.CreateLateBoundMethod(enumeratorMethod);
var keyGetter = ReflectionHelpers.CreateLateBoundMethod(getKeyMethod);
var valueGetter = ReflectionHelpers.CreateLateBoundMethod(getValueMethod);
return (collection) =>
{
if (collection is IEnumerable && dictionaryType.IsInstanceOfType(collection))
{
var enumerator = (IEnumerator?)enumeratorGetter.Invoke(collection, ArrayHelper.Empty<object>());
if (enumerator is null || !enumerator.MoveNext())
{
(enumerator as IDisposable)?.Dispose();
return EmptyDictionaryEnumerator.Default;
}
return EnumerateItems(enumerator, keyGetter, valueGetter);
}
return EmptyDictionaryEnumerator.Default;
};
}
return null;
}
private static IEnumerator<KeyValuePair<string, object?>> EnumerateItems(IEnumerator enumerator, ReflectionHelpers.LateBoundMethod getKeyProperty, ReflectionHelpers.LateBoundMethod getValueProperty)
{
try
{
do
{
var item = enumerator.Current;
var keyString = getKeyProperty.Invoke(item, ArrayHelper.Empty<object>());
var valueObject = getValueProperty.Invoke(item, ArrayHelper.Empty<object>());
yield return new KeyValuePair<string, object?>(keyString?.ToString() ?? string.Empty, valueObject);
} while (enumerator.MoveNext());
}
finally
{
(enumerator as IDisposable)?.Dispose();
}
}
#endif
}
private sealed class DictionaryEnumerator<TKey, TValue> : IDictionaryEnumerator
{
public IEnumerator<KeyValuePair<string, object?>> GetEnumerator(object value)
{
if (value is IDictionary<TKey, TValue> dictionary)
{
if (dictionary.Count > 0)
return YieldEnumerator(dictionary);
}
#if !NET35 && !NET40
else if (value is IReadOnlyDictionary<TKey, TValue> readonlyDictionary)
{
if (readonlyDictionary.Count > 0)
return YieldEnumerator(readonlyDictionary);
}
#endif
return EmptyDictionaryEnumerator.Default;
}
private static IEnumerator<KeyValuePair<string, object?>> YieldEnumerator(IDictionary<TKey, TValue> dictionary)
{
foreach (var item in dictionary)
yield return new KeyValuePair<string, object?>(item.Key?.ToString() ?? string.Empty, item.Value);
}
#if !NET35 && !NET40
private static IEnumerator<KeyValuePair<string, object?>> YieldEnumerator(IReadOnlyDictionary<TKey, TValue> dictionary)
{
foreach (var item in dictionary)
yield return new KeyValuePair<string, object?>(item.Key?.ToString() ?? string.Empty, item.Value);
}
#endif
}
private sealed class EmptyDictionaryEnumerator : IEnumerator<KeyValuePair<string, object?>>
{
public static readonly EmptyDictionaryEnumerator Default = new EmptyDictionaryEnumerator();
KeyValuePair<string, object?> IEnumerator<KeyValuePair<string, object?>>.Current => default(KeyValuePair<string, object?>);
object IEnumerator.Current => default(KeyValuePair<string, object?>);
bool IEnumerator.MoveNext() => false;
void IDisposable.Dispose()
{
// Nothing here on purpose
}
void IEnumerator.Reset()
{
// Nothing here on purpose
}
}
}
}
+758
View File
@@ -0,0 +1,758 @@
//
// Copyright (c) 2004-2024 Jaroslaw Kowalski <jaak@jkowalski.net>, Kim Christensen, Julian Verdurmen
//
// All rights reserved.
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions
// are met:
//
// * Redistributions of source code must retain the above copyright notice,
// this list of conditions and the following disclaimer.
//
// * Redistributions in binary form must reproduce the above copyright notice,
// this list of conditions and the following disclaimer in the documentation
// and/or other materials provided with the distribution.
//
// * Neither the name of Jaroslaw Kowalski nor the names of its
// contributors may be used to endorse or promote products derived from this
// software without specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
// THE POSSIBILITY OF SUCH DAMAGE.
//
namespace NLog.Targets
{
using System;
using System.Collections;
using System.ComponentModel;
using System.Globalization;
using System.Text;
using NLog.Internal;
/// <summary>
/// Default class for serialization of values to JSON format.
/// </summary>
public class DefaultJsonSerializer : IJsonConverter
{
private readonly ObjectReflectionCache _objectReflectionCache;
private readonly MruCache<Enum, string> _enumCache = new MruCache<Enum, string>(4000);
private const int MaxJsonLength = 512 * 1024;
private static JsonSerializeOptions DefaultSerializerOptions = new JsonSerializeOptions();
private static JsonSerializeOptions DefaultExceptionSerializerOptions = new JsonSerializeOptions() { SanitizeDictionaryKeys = true };
/// <summary>
/// Singleton instance of the serializer.
/// </summary>
[Obsolete("Instead use ResolveService<IJsonConverter>() in Layout / Target. Marked obsolete on NLog 5.0")]
[EditorBrowsable(EditorBrowsableState.Never)]
public static DefaultJsonSerializer Instance => _instance ?? (_instance = new DefaultJsonSerializer(LogManager.LogFactory.ServiceRepository));
private static DefaultJsonSerializer? _instance;
/// <summary>
/// Private. Use <see cref="Instance"/>
/// </summary>
internal DefaultJsonSerializer(IServiceProvider serviceProvider)
{
_objectReflectionCache = new ObjectReflectionCache(serviceProvider);
}
/// <summary>
/// Returns a serialization of an object into JSON format.
/// </summary>
/// <param name="value">The object to serialize to JSON.</param>
/// <returns>Serialized value.</returns>
public string SerializeObject(object value)
{
return SerializeObject(value, DefaultSerializerOptions);
}
/// <summary>
/// Returns a serialization of an object into JSON format.
/// </summary>
/// <param name="value">The object to serialize to JSON.</param>
/// <param name="options">serialization options</param>
/// <returns>Serialized value.</returns>
public string SerializeObject(object value, JsonSerializeOptions options)
{
if (value is null)
{
return "null";
}
else if (value is string str)
{
var escapeUnicode = options.EscapeUnicode;
foreach (var chr in str)
{
if (RequiresJsonEscape(chr, escapeUnicode))
{
StringBuilder sb = new StringBuilder(str.Length + 4);
sb.Append('"');
AppendStringEscape(sb, str, options);
sb.Append('"');
return sb.ToString();
}
}
return QuoteValue(str);
}
else
{
if (value is IConvertible convertibleValue)
{
TypeCode objTypeCode = convertibleValue.GetTypeCode();
if (objTypeCode != TypeCode.Object)
{
if (!options.EnumAsInteger && IsNumericTypeCode(objTypeCode, false) && convertibleValue is Enum enumValue)
{
return QuoteValue(EnumAsString(enumValue));
}
else
{
string xmlStr = XmlHelper.XmlConvertToString(convertibleValue, objTypeCode);
if (SkipQuotes(convertibleValue, objTypeCode))
{
return xmlStr;
}
else
{
return QuoteValue(xmlStr);
}
}
}
}
StringBuilder sb = new StringBuilder();
if (!SerializeObject(value, sb, options))
{
return string.Empty;
}
return sb.ToString();
}
}
/// <summary>
/// Serialization of the object in JSON format to the destination StringBuilder
/// </summary>
/// <param name="value">The object to serialize to JSON.</param>
/// <param name="destination">Write the resulting JSON to this destination.</param>
/// <returns>Object serialized successfully (true/false).</returns>
public bool SerializeObject(object? value, StringBuilder destination)
{
return SerializeObject(value, destination, DefaultSerializerOptions);
}
/// <summary>
/// Serialization of the object in JSON format to the destination StringBuilder
/// </summary>
/// <param name="value">The object to serialize to JSON.</param>
/// <param name="destination">Write the resulting JSON to this destination.</param>
/// <param name="options">serialization options</param>
/// <returns>Object serialized successfully (true/false).</returns>
public bool SerializeObject(object? value, StringBuilder destination, JsonSerializeOptions options)
{
return SerializeObject(value, destination, options, default(SingleItemOptimizedHashSet<object>), 0);
}
/// <summary>
/// Serialization of the object in JSON format to the destination StringBuilder
/// </summary>
/// <param name="value">The object to serialize to JSON.</param>
/// <param name="destination">Write the resulting JSON to this destination.</param>
/// <param name="options">serialization options</param>
/// <param name="objectsInPath">The objects in path (Avoid cyclic reference loop).</param>
/// <param name="depth">The current depth (level) of recursion.</param>
/// <returns>Object serialized successfully (true/false).</returns>
private bool SerializeObject(object? value, StringBuilder destination, JsonSerializeOptions options, SingleItemOptimizedHashSet<object> objectsInPath, int depth)
{
int originalLength = destination.Length;
try
{
if (SerializeSimpleObjectValue(value, destination, options))
{
return true;
}
return SerializeObjectWithReflection(value, destination, options, ref objectsInPath, depth);
}
catch
{
destination.Length = originalLength;
return false;
}
}
private bool SerializeObjectWithReflection(object? value, StringBuilder destination, JsonSerializeOptions options, ref SingleItemOptimizedHashSet<object> objectsInPath, int depth)
{
int originalLength = destination.Length;
if (originalLength > MaxJsonLength)
{
return false;
}
if (value is null || objectsInPath.Contains(value))
{
return false;
}
if (value is IDictionary dict)
{
using (StartCollectionScope(ref objectsInPath, dict))
{
SerializeDictionaryObject(dict, destination, options, objectsInPath, depth);
return true;
}
}
if (value is IEnumerable collection)
{
if (_objectReflectionCache.TryLookupExpandoObject(value, out var objectPropertyList))
{
return SerializeObjectPropertyList(value, ref objectPropertyList, destination, options, ref objectsInPath, depth);
}
else
{
using (StartCollectionScope(ref objectsInPath, value))
{
SerializeCollectionObject(collection, destination, options, objectsInPath, depth);
return true;
}
}
}
else
{
var objectPropertyList = _objectReflectionCache.LookupObjectProperties(value);
return SerializeObjectPropertyList(value, ref objectPropertyList, destination, options, ref objectsInPath, depth);
}
}
private bool SerializeSimpleObjectValue(object? value, StringBuilder destination, JsonSerializeOptions options, bool forceToString = false)
{
if (value is IConvertible convertibleValue)
{
var objTypeCode = convertibleValue.GetTypeCode();
if (objTypeCode != TypeCode.Object)
{
SerializeSimpleTypeCodeValue(convertibleValue, objTypeCode, destination, options, forceToString);
return true;
}
}
if (value is IFormattable formattable)
{
if (value is DateTimeOffset dateTimeOffset)
{
QuoteValue(destination, dateTimeOffset.ToString("yyyy-MM-dd HH:mm:ss zzz", CultureInfo.InvariantCulture));
return true;
}
destination.Append('"');
#if NETFRAMEWORK
var str = formattable.ToString(null, CultureInfo.InvariantCulture) ?? string.Empty;
AppendStringEscape(destination, str, options);
#else
int startPos = destination.Length;
destination.AppendFormat(CultureInfo.InvariantCulture, "{0}", formattable); // Support ISpanFormattable
PerformJsonEscapeWhenNeeded(destination, startPos, options.EscapeUnicode);
#endif
destination.Append('"');
return true;
}
if (value is null)
{
SerializeSimpleTypeCodeValue(null, TypeCode.Empty, destination, options, forceToString);
return true;
}
return false; // Not simple
}
private static SingleItemOptimizedHashSet<object>.SingleItemScopedInsert StartCollectionScope(ref SingleItemOptimizedHashSet<object> objectsInPath, object value)
{
return new SingleItemOptimizedHashSet<object>.SingleItemScopedInsert(value, ref objectsInPath, true);
}
private void SerializeDictionaryObject(IDictionary dictionary, StringBuilder destination, JsonSerializeOptions options, SingleItemOptimizedHashSet<object> objectsInPath, int depth)
{
bool first = true;
int nextDepth = objectsInPath.Count <= 1 ? depth : (depth + 1);
if (nextDepth > options.MaxRecursionLimit)
{
destination.Append("{}");
return;
}
destination.Append('{');
foreach (var item in new DictionaryEntryEnumerable(dictionary))
{
var originalLength = destination.Length;
if (originalLength > MaxJsonLength)
{
break;
}
if (!first)
{
destination.Append(',');
}
var itemKey = item.Key;
if (!SerializeObjectAsString(itemKey, destination, options))
{
destination.Length = originalLength;
continue;
}
if (options.SanitizeDictionaryKeys)
{
int keyEndIndex = destination.Length - 1;
int keyStartIndex = originalLength + (first ? 0 : 1) + 1;
if (!SanitizeDictionaryKey(destination, keyStartIndex, keyEndIndex - keyStartIndex))
{
destination.Length = originalLength; // Empty keys are not allowed
continue;
}
}
destination.Append(':');
//only serialize, if key and value are serialized without error (e.g. due to reference loop)
var itemValue = item.Value;
if (!SerializeObject(itemValue, destination, options, objectsInPath, nextDepth))
{
destination.Length = originalLength;
}
else
{
first = false;
}
}
destination.Append('}');
}
private static bool SanitizeDictionaryKey(StringBuilder destination, int keyStartIndex, int keyLength)
{
if (keyLength == 0)
{
return false; // Empty keys are not allowed
}
int keyEndIndex = keyStartIndex + keyLength;
for (int i = keyStartIndex; i < keyEndIndex; ++i)
{
char keyChar = destination[i];
if (keyChar == '_' || char.IsLetterOrDigit(keyChar))
continue;
destination[i] = '_';
}
return true;
}
private void SerializeCollectionObject(IEnumerable value, StringBuilder destination, JsonSerializeOptions options, SingleItemOptimizedHashSet<object> objectsInPath, int depth)
{
bool first = true;
int nextDepth = objectsInPath.Count <= 1 ? depth : (depth + 1); // Allow serialization of list-items
if (nextDepth > options.MaxRecursionLimit)
{
destination.Append("[]");
return;
}
int originalLength;
destination.Append('[');
foreach (var val in value)
{
originalLength = destination.Length;
if (originalLength > MaxJsonLength)
{
break;
}
if (!first)
{
destination.Append(',');
}
if (!SerializeObject(val, destination, options, objectsInPath, nextDepth))
{
destination.Length = originalLength;
}
else
{
first = false;
}
}
destination.Append(']');
}
private bool SerializeObjectPropertyList(object value, ref ObjectReflectionCache.ObjectPropertyList objectPropertyList, StringBuilder destination, JsonSerializeOptions options, ref SingleItemOptimizedHashSet<object> objectsInPath, int depth)
{
if (objectPropertyList.IsSimpleValue)
{
value = objectPropertyList.ObjectValue;
if (SerializeSimpleObjectValue(value, destination, options))
{
return true;
}
}
else if (depth < options.MaxRecursionLimit)
{
if (ReferenceEquals(options, DefaultSerializerOptions) && value is Exception)
{
// Exceptions are seldom under control, and can include random Data-Dictionary-keys, so we sanitize by default
options = DefaultExceptionSerializerOptions;
}
using (new SingleItemOptimizedHashSet<object>.SingleItemScopedInsert(value, ref objectsInPath, false))
{
return SerializeObjectProperties(objectPropertyList, destination, options, objectsInPath, depth);
}
}
return SerializeObjectAsString(value, destination, options);
}
private void SerializeSimpleTypeCodeValue(IConvertible? value, TypeCode objTypeCode, StringBuilder destination, JsonSerializeOptions options, bool forceToString = false)
{
if (objTypeCode == TypeCode.Empty || value is null)
{
destination.Append(forceToString ? "\"\"" : "null");
}
else if (objTypeCode == TypeCode.String || objTypeCode == TypeCode.Char)
{
destination.Append('"');
AppendStringEscape(destination, value.ToString() ?? string.Empty, options);
destination.Append('"');
}
else
{
SerializeSimpleTypeCodeValueNoEscape(value, objTypeCode, destination, options, forceToString);
}
}
private void SerializeSimpleTypeCodeValueNoEscape(IConvertible value, TypeCode objTypeCode, StringBuilder destination, JsonSerializeOptions options, bool forceToString)
{
if (IsNumericTypeCode(objTypeCode, false))
{
if (!options.EnumAsInteger && value is Enum enumValue)
{
QuoteValue(destination, EnumAsString(enumValue));
}
else
{
SerializeNumericValue(value, objTypeCode, destination, forceToString);
}
}
else if (objTypeCode == TypeCode.DateTime)
{
destination.Append('"');
destination.AppendXmlDateTimeUtcRoundTrip(value.ToDateTime(CultureInfo.InvariantCulture));
destination.Append('"');
}
else if (IsNumericTypeCode(objTypeCode, true) && SkipQuotes(value, objTypeCode))
{
SerializeNumericValue(value, objTypeCode, destination, forceToString);
}
else
{
string str = XmlHelper.XmlConvertToString(value, objTypeCode);
if (!forceToString && !string.IsNullOrEmpty(str) && SkipQuotes(value, objTypeCode))
{
destination.Append(str);
}
else
{
QuoteValue(destination, str);
}
}
}
private static void SerializeNumericValue(IConvertible value, TypeCode objTypeCode, StringBuilder destination, bool forceToString)
{
if (forceToString)
destination.Append('"');
destination.AppendNumericInvariant(value, objTypeCode);
if (forceToString)
destination.Append('"');
}
private static string QuoteValue(string value)
{
return string.Concat("\"", value, "\"");
}
private static void QuoteValue(StringBuilder destination, string value)
{
destination.Append('"');
destination.Append(value);
destination.Append('"');
}
private string EnumAsString(Enum value)
{
if (!_enumCache.TryGetValue(value, out var textValue))
{
textValue = Convert.ToString(value, CultureInfo.InvariantCulture) ?? string.Empty;
_enumCache.TryAddValue(value, textValue);
}
return textValue ?? string.Empty;
}
/// <summary>
/// No quotes needed for this type?
/// </summary>
private static bool SkipQuotes(IConvertible value, TypeCode objTypeCode)
{
switch (objTypeCode)
{
case TypeCode.String: return false;
case TypeCode.Char: return false;
case TypeCode.DateTime: return false;
case TypeCode.Empty: return true;
case TypeCode.Boolean: return true;
case TypeCode.Decimal: return true;
case TypeCode.Double:
{
double dblValue = value.ToDouble(CultureInfo.InvariantCulture);
return !double.IsNaN(dblValue) && !double.IsInfinity(dblValue);
}
case TypeCode.Single:
{
float floatValue = value.ToSingle(CultureInfo.InvariantCulture);
return !float.IsNaN(floatValue) && !float.IsInfinity(floatValue);
}
default:
return IsNumericTypeCode(objTypeCode, false);
}
}
/// <summary>
/// Checks the object <see cref="TypeCode" /> if it is numeric
/// </summary>
/// <param name="objTypeCode">TypeCode for the object</param>
/// <param name="includeDecimals">Accept fractional types as numeric type.</param>
/// <returns></returns>
private static bool IsNumericTypeCode(TypeCode objTypeCode, bool includeDecimals)
{
switch (objTypeCode)
{
case TypeCode.Byte:
case TypeCode.SByte:
case TypeCode.Int16:
case TypeCode.Int32:
case TypeCode.Int64:
case TypeCode.UInt16:
case TypeCode.UInt32:
case TypeCode.UInt64:
return true;
case TypeCode.Single:
case TypeCode.Double:
case TypeCode.Decimal:
return includeDecimals;
}
return false;
}
/// <summary>
/// Checks input string if it needs JSON escaping, and makes necessary conversion
/// </summary>
/// <param name="destination">Destination Builder</param>
/// <param name="text">Input string</param>
/// <param name="options">all options</param>
/// <returns>JSON escaped string</returns>
private static void AppendStringEscape(StringBuilder destination, string text, JsonSerializeOptions options)
{
AppendStringEscape(destination, text, options.EscapeUnicode);
}
/// <summary>
/// Checks input string if it needs JSON escaping, and makes necessary conversion
/// </summary>
/// <param name="destination">Destination Builder</param>
/// <param name="text">Input string</param>
/// <param name="escapeUnicode">Should non-ASCII characters be encoded</param>
///
/// <returns>JSON escaped string</returns>
internal static void AppendStringEscape(StringBuilder destination, string text, bool escapeUnicode)
{
int i = 0;
foreach (var chr in text)
{
if (RequiresJsonEscape(chr, escapeUnicode))
{
AppendStringEscape(destination, text, escapeUnicode, i);
return;
}
++i;
}
destination.Append(text);
}
private static void AppendStringEscape(StringBuilder destination, string text, bool escapeUnicode, int startIndex)
{
destination.Append(text, 0, startIndex);
for (int i = startIndex; i < text.Length; ++i)
{
char ch = text[i];
if (!RequiresJsonEscape(ch, escapeUnicode))
{
destination.Append(ch);
continue;
}
switch (ch)
{
case '"':
destination.Append("\\\"");
break;
case '\\':
destination.Append("\\\\");
break;
case '\b':
destination.Append("\\b");
break;
case '\r':
destination.Append("\\r");
break;
case '\n':
destination.Append("\\n");
break;
case '\f':
destination.Append("\\f");
break;
case '\t':
destination.Append("\\t");
break;
default:
destination.AppendFormat(CultureInfo.InvariantCulture, "\\u{0:x4}", (int)ch);
break;
}
}
}
internal static void PerformJsonEscapeWhenNeeded(StringBuilder builder, int startPos, bool escapeUnicode)
{
var builderLength = builder.Length;
for (int i = startPos; i < builderLength; ++i)
{
if (RequiresJsonEscape(builder[i], escapeUnicode))
{
var str = builder.ToString(i, builderLength - i);
builder.Length = i;
AppendStringEscape(builder, str, escapeUnicode, 0);
break;
}
}
}
private static bool RequiresJsonEscape(char ch, bool escapeUnicode)
{
if (ch < 32)
return true;
if (ch > 127)
return escapeUnicode;
return ch == '"' || ch == '\\';
}
private bool SerializeObjectProperties(ObjectReflectionCache.ObjectPropertyList objectPropertyList, StringBuilder destination, JsonSerializeOptions options,
SingleItemOptimizedHashSet<object> objectsInPath, int depth)
{
destination.Append('{');
string? jsonPropertyDelimeter = null;
foreach (var propertyValue in objectPropertyList)
{
var originalLength = destination.Length;
try
{
if (!propertyValue.HasNameAndValue)
continue;
destination.Append(jsonPropertyDelimeter);
QuoteValue(destination, propertyValue.Name);
destination.Append(':');
var objTypeCode = propertyValue.TypeCode;
if (objTypeCode != TypeCode.Object)
{
SerializeSimpleTypeCodeValue((IConvertible?)propertyValue.Value, objTypeCode, destination, options);
}
else
{
if (!SerializeObject(propertyValue.Value, destination, options, objectsInPath, depth + 1))
{
destination.Length = originalLength;
continue;
}
}
if (jsonPropertyDelimeter is null)
jsonPropertyDelimeter = options.SuppressSpaces ? "," : ", ";
}
catch
{
// skip single property
destination.Length = originalLength;
}
}
destination.Append('}');
return true;
}
private bool SerializeObjectAsString(object value, StringBuilder destination, JsonSerializeOptions options)
{
var originalLength = destination.Length;
try
{
if (SerializeSimpleObjectValue(value, destination, options, true))
{
return true;
}
var str = Convert.ToString(value, CultureInfo.InvariantCulture) ?? string.Empty;
destination.Append('"');
AppendStringEscape(destination, str, options);
destination.Append('"');
return true;
}
catch
{
// skip bad object
destination.Length = originalLength;
return false;
}
}
}
}
+108
View File
@@ -0,0 +1,108 @@
//
// Copyright (c) 2004-2024 Jaroslaw Kowalski <jaak@jkowalski.net>, Kim Christensen, Julian Verdurmen
//
// All rights reserved.
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions
// are met:
//
// * Redistributions of source code must retain the above copyright notice,
// this list of conditions and the following disclaimer.
//
// * Redistributions in binary form must reproduce the above copyright notice,
// this list of conditions and the following disclaimer in the documentation
// and/or other materials provided with the distribution.
//
// * Neither the name of Jaroslaw Kowalski nor the names of its
// contributors may be used to endorse or promote products derived from this
// software without specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
// THE POSSIBILITY OF SUCH DAMAGE.
//
namespace NLog.Targets
{
using System;
using System.ComponentModel;
/// <summary>
/// Options for JSON serialization
/// </summary>
public class JsonSerializeOptions
{
/// <summary>
/// Add quotes around object keys?
/// </summary>
/// <remarks>Default: <see langword="true"/></remarks>
[Obsolete("Marked obsolete with NLog 5.0.")]
[EditorBrowsable(EditorBrowsableState.Never)]
public bool QuoteKeys { get; set; } = true;
/// <summary>
/// Format provider for value
/// </summary>
/// <remarks>Default: <see langword="null"/></remarks>
[Obsolete("Marked obsolete with NLog 5.0. Should always be InvariantCulture.")]
[EditorBrowsable(EditorBrowsableState.Never)]
public IFormatProvider? FormatProvider { get; set; }
/// <summary>
/// Format string for value
/// </summary>
/// <remarks>Default: <see langword="null"/></remarks>
[Obsolete("Marked obsolete with NLog 5.0. Should always be InvariantCulture.")]
[EditorBrowsable(EditorBrowsableState.Never)]
public string? Format { get; set; }
/// <summary>
/// Should non-ascii characters be encoded.
/// </summary>
/// <remarks>Default: <see langword="false"/></remarks>
public bool EscapeUnicode { get; set; }
/// <summary>
/// Should forward slashes be escaped? If <see langword="true"/>, / will be converted to \/
/// </summary>
/// <remarks>Default: <see langword="false"/></remarks>
[Obsolete("Marked obsolete with NLog 5.5. Should never escape forward slash")]
[EditorBrowsable(EditorBrowsableState.Never)]
public bool EscapeForwardSlash { get; set; }
/// <summary>
/// Serialize enum as integer value.
/// </summary>
/// <remarks>Default: <see langword="false"/></remarks>
public bool EnumAsInteger { get; set; }
/// <summary>
/// Gets or sets the option to suppress the extra spaces in the output json.
/// </summary>
/// <remarks>Default: <see langword="true"/></remarks>
public bool SuppressSpaces { get; set; } = true;
/// <summary>
/// Should dictionary keys be sanitized. All characters must either be letters, numbers or underscore character (_).
///
/// Any other characters will be converted to underscore character (_)
/// </summary>
/// <remarks>Default: <see langword="false"/></remarks>
public bool SanitizeDictionaryKeys { get; set; }
/// <summary>
/// How far down the rabbit hole should the Json Serializer go with object-reflection before stopping
/// </summary>
/// <remarks>Default: <see langword="10"/></remarks>
public int MaxRecursionLimit { get; set; } = 10;
}
}
@@ -0,0 +1,135 @@
//
// Copyright (c) 2004-2024 Jaroslaw Kowalski <jaak@jkowalski.net>, Kim Christensen, Julian Verdurmen
//
// All rights reserved.
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions
// are met:
//
// * Redistributions of source code must retain the above copyright notice,
// this list of conditions and the following disclaimer.
//
// * Redistributions in binary form must reproduce the above copyright notice,
// this list of conditions and the following disclaimer in the documentation
// and/or other materials provided with the distribution.
//
// * Neither the name of Jaroslaw Kowalski nor the names of its
// contributors may be used to endorse or promote products derived from this
// software without specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
// THE POSSIBILITY OF SUCH DAMAGE.
//
using System.Text;
using NLog.Targets;
using Xunit;
namespace NLog.UnitTests.Targets
{
/// <summary>
/// Test via <see cref="IJsonConverter" /> path
/// </summary>
public class DefaultJsonSerializerClassTests : NLogTestBase
{
private static readonly object _testSyncObject = new object();
private interface IExcludedInterface
{
}
private sealed class ExcludedClass : IExcludedInterface
{
public string ExcludedString { get; set; }
public override string ToString()
{
return "Skipped";
}
}
private sealed class IncludedClass
{
public string IncludedString { get; set; }
}
private sealed class ContainerClass
{
public string S { get; set; }
public ExcludedClass Excluded { get; set; }
public IncludedClass Included { get; set; }
}
private static ContainerClass BuildSampleObject()
{
var testObject = new ContainerClass
{
S = "sample",
Excluded = new ExcludedClass { ExcludedString = "shouldn't be serialized" },
Included = new IncludedClass { IncludedString = "serialized" }
};
return testObject;
}
[Fact]
public void SimpleValue_RegistersSerializeAsToString_ConvertsValue()
{
var logFactory = new LogFactory();
logFactory.Setup().SetupSerialization(s => s.RegisterObjectTransformation<System.IO.MemoryStream>(o => o.Capacity));
var testObject = new System.IO.MemoryStream(42);
var sb = new StringBuilder();
var options = new JsonSerializeOptions();
var jsonSerializer = new DefaultJsonSerializer(logFactory.ServiceRepository);
jsonSerializer.SerializeObject(testObject, sb, options);
Assert.Equal($"{testObject.Capacity}", sb.ToString());
}
[Fact]
public void IExcludedInterfaceSerializer_RegistersSerializeAsToString_InvokesToString()
{
var testObject = BuildSampleObject();
var sb = new StringBuilder();
var options = new JsonSerializeOptions();
var logFactory = new LogFactory();
logFactory.Setup().SetupSerialization(s => s.RegisterObjectTransformation<IExcludedInterface>(o => o.ToString()));
var jsonSerializer = new DefaultJsonSerializer(logFactory.ServiceRepository);
jsonSerializer.SerializeObject(testObject, sb, options);
const string expectedValue =
@"{""S"":""sample"",""Excluded"":""Skipped"",""Included"":{""IncludedString"":""serialized""}}";
Assert.Equal(expectedValue, sb.ToString());
}
[Fact]
public void ExcludedClassSerializer_RegistersSerializeAsToString_InvokesToString()
{
var testObject = BuildSampleObject();
var sb = new StringBuilder();
var options = new JsonSerializeOptions();
var logFactory = new LogFactory();
logFactory.Setup().SetupSerialization(s => s.RegisterObjectTransformation(typeof(ExcludedClass), o => o.ToString()));
var jsonSerializer = new DefaultJsonSerializer(logFactory.ServiceRepository);
jsonSerializer.SerializeObject(testObject, sb, options);
const string expectedValue =
@"{""S"":""sample"",""Excluded"":""Skipped"",""Included"":{""IncludedString"":""serialized""}}";
Assert.Equal(expectedValue, sb.ToString());
}
}
}