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() {