Describe IncludePublicFields as a fallback and write a shadowed field once

This commit is contained in:
dualfroz committed 2026-09-23 11:51:34 +02:00
1 parent f078e2a2c0
commit 05f5fc4dc9
3 files changed
+35 -2

No files matched your search

+18 -1
View File
@@ -298,15 +298,32 @@ namespace NLog.Internal
private static FastPropertyLookup[] BuildFieldLookup(FieldInfo[] fields) private static FastPropertyLookup[] BuildFieldLookup(FieldInfo[] fields)
{ {
FastPropertyLookup[] fastLookup = new FastPropertyLookup[fields.Length]; FastPropertyLookup[] fastLookup = new FastPropertyLookup[fields.Length];
int count = 0;
for (int i = 0; i < fields.Length; ++i) for (int i = 0; i < fields.Length; ++i)
{ {
var field = fields[i]; var field = fields[i];
if (IsHiddenByDerivedField(field, fields))
continue; // A derived type redeclared the name with "new", so the member is written once
TypeCode typeCode = Type.GetTypeCode(field.FieldType); TypeCode typeCode = Type.GetTypeCode(field.FieldType);
fastLookup[i] = new FastPropertyLookup(field.Name, typeCode, (o, p) => field.GetValue(o)); fastLookup[count++] = new FastPropertyLookup(field.Name, typeCode, (o, p) => field.GetValue(o));
} }
if (count != fastLookup.Length)
Array.Resize(ref fastLookup, count);
return fastLookup; return fastLookup;
} }
private static bool IsHiddenByDerivedField(FieldInfo field, FieldInfo[] fields)
{
foreach (var other in fields)
{
if (!ReferenceEquals(other, field) && other.Name == field.Name && other.DeclaringType != null && field.DeclaringType != null && other.DeclaringType.IsSubclassOf(field.DeclaringType))
return true;
}
return false;
}
private static FastPropertyLookup[] BuildFastLookup(PropertyInfo[] properties, bool includeType) private static FastPropertyLookup[] BuildFastLookup(PropertyInfo[] properties, bool includeType)
{ {
int fastAccessIndex = includeType ? 1 : 0; int fastAccessIndex = includeType ? 1 : 0;
+1 -1
View File
@@ -106,7 +106,7 @@ namespace NLog.Targets
public int MaxRecursionLimit { get; set; } = 10; public int MaxRecursionLimit { get; set; } = 10;
/// <summary> /// <summary>
/// Should public fields be included in the JSON output, in addition to public properties. /// Should public fields be included in the JSON output, for objects that have no public properties.
/// </summary> /// </summary>
/// <remarks>Default: <see langword="false"/></remarks> /// <remarks>Default: <see langword="false"/></remarks>
public bool IncludePublicFields { get; set; } public bool IncludePublicFields { get; set; }
@@ -95,6 +95,16 @@ namespace NLog.UnitTests.Targets
public string ReadPrivate() => _private; public string ReadPrivate() => _private;
} }
private class ClassWithBaseField
{
public string Name = "base";
}
private sealed class ClassWithShadowingField : ClassWithBaseField
{
public new string Name = "derived";
}
private static ContainerClass BuildSampleObject() private static ContainerClass BuildSampleObject()
{ {
var testObject = new ContainerClass var testObject = new ContainerClass
@@ -236,6 +246,12 @@ namespace NLog.UnitTests.Targets
Assert.Equal(@"{""Name"":""John"",""Age"":42}", withFieldsAgain.ToString()); Assert.Equal(@"{""Name"":""John"",""Age"":42}", withFieldsAgain.ToString());
} }
[Fact]
public void IncludePublicFields_ShadowedField_IsWrittenOnce()
{
Assert.Equal(@"{""Name"":""derived""}", SerializeWithFields(new ClassWithShadowingField()));
}
[Fact] [Fact]
public void IncludePublicFields_ExpandoObject_StillEnumeratesEntries() public void IncludePublicFields_ExpandoObject_StillEnumeratesEntries()
{ {