diff --git a/src/NLog/Internal/ObjectReflectionCache.cs b/src/NLog/Internal/ObjectReflectionCache.cs
index 09ecf16..59b170d 100644
--- a/src/NLog/Internal/ObjectReflectionCache.cs
+++ b/src/NLog/Internal/ObjectReflectionCache.cs
@@ -298,15 +298,32 @@ namespace NLog.Internal
private static FastPropertyLookup[] BuildFieldLookup(FieldInfo[] fields)
{
FastPropertyLookup[] fastLookup = new FastPropertyLookup[fields.Length];
+ int count = 0;
for (int i = 0; i < fields.Length; ++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);
- 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;
}
+ 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)
{
int fastAccessIndex = includeType ? 1 : 0;
diff --git a/src/NLog/Targets/JsonSerializeOptions.cs b/src/NLog/Targets/JsonSerializeOptions.cs
index 545b77f..4d25292 100644
--- a/src/NLog/Targets/JsonSerializeOptions.cs
+++ b/src/NLog/Targets/JsonSerializeOptions.cs
@@ -106,7 +106,7 @@ namespace NLog.Targets
public int MaxRecursionLimit { get; set; } = 10;
///
- /// 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.
///
/// Default:
public bool IncludePublicFields { get; set; }
diff --git a/tests/NLog.UnitTests/Targets/DefaultJsonSerializerClassTests.cs b/tests/NLog.UnitTests/Targets/DefaultJsonSerializerClassTests.cs
index f047d04..fd4cf7a 100644
--- a/tests/NLog.UnitTests/Targets/DefaultJsonSerializerClassTests.cs
+++ b/tests/NLog.UnitTests/Targets/DefaultJsonSerializerClassTests.cs
@@ -95,6 +95,16 @@ namespace NLog.UnitTests.Targets
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()
{
var testObject = new ContainerClass
@@ -236,6 +246,12 @@ namespace NLog.UnitTests.Targets
Assert.Equal(@"{""Name"":""John"",""Age"":42}", withFieldsAgain.ToString());
}
+ [Fact]
+ public void IncludePublicFields_ShadowedField_IsWrittenOnce()
+ {
+ Assert.Equal(@"{""Name"":""derived""}", SerializeWithFields(new ClassWithShadowingField()));
+ }
+
[Fact]
public void IncludePublicFields_ExpandoObject_StillEnumeratesEntries()
{