From 09c6466de418b8bbdf45554b121fd2d4904261f5 Mon Sep 17 00:00:00 2001 From: Daniel West Date: Thu, 7 Mar 2024 16:48:08 +1100 Subject: [PATCH] Allow properties to be included even when they are the default value (#1141) --- docs/serializer-settings.md | 66 ++++++++++++++----- ...ationTests.AlwaysIncludeType.verified.json | 4 ++ ...zationTests.AlwaysIncludeType.verified.txt | 4 ++ .../Serialization/SerializationTests.cs | 13 ++++ .../Serialization/CustomContractResolver.cs | 19 ++++-- src/Verify/Serialization/ScrubOrIgnore.cs | 3 +- .../SerializationSettings_IgnoreType.cs | 7 ++ .../VerifierSettings_SerializationMaps.cs | 13 ++++ .../VerifySettings_SerializationMaps.cs | 13 ++++ src/Verify/SettingsTask_SerializationMaps.cs | 15 +++++ 10 files changed, 134 insertions(+), 23 deletions(-) create mode 100644 src/StrictJsonTests/SerializationTests.AlwaysIncludeType.verified.json create mode 100644 src/Verify.Tests/Serialization/SerializationTests.AlwaysIncludeType.verified.txt diff --git a/docs/serializer-settings.md b/docs/serializer-settings.md index 88bf23fe8..886551055 100644 --- a/docs/serializer-settings.md +++ b/docs/serializer-settings.md @@ -308,7 +308,7 @@ public Task ScopedSerializerFluent() .AddExtraSettings(_ => _.TypeNameHandling = TypeNameHandling.All); } ``` -snippet source | anchor +snippet source | anchor Result: @@ -799,7 +799,7 @@ public Task WithObsoleteProp() return Verify(target); } ``` -snippet source | anchor +snippet source | anchor Result: @@ -847,7 +847,7 @@ public Task WithObsoletePropIncludedFluent() .IncludeObsoletes(); } ``` -snippet source | anchor +snippet source | anchor Or globally: @@ -857,7 +857,7 @@ Or globally: ```cs VerifierSettings.IncludeObsoletes(); ``` -snippet source | anchor +snippet source | anchor Result: @@ -918,7 +918,7 @@ public Task IgnoreMemberByExpressionFluent() _ => _.PropertyThatThrows); } ``` -snippet source | anchor +snippet source | anchor Or globally @@ -933,7 +933,7 @@ VerifierSettings.IgnoreMembers( _ => _.GetOnlyProperty, _ => _.PropertyThatThrows); ``` -snippet source | anchor +snippet source | anchor Result: @@ -993,7 +993,7 @@ public Task ScrubMemberByExpressionFluent() _ => _.PropertyThatThrows); } ``` -snippet source | anchor +snippet source | anchor Or globally @@ -1008,7 +1008,7 @@ VerifierSettings.ScrubMembers( _ => _.GetOnlyProperty, _ => _.PropertyThatThrows); ``` -snippet source | anchor +snippet source | anchor Result: @@ -1087,7 +1087,7 @@ public Task IgnoreMemberByNameFluent() .IgnoreMember(_ => _.PropertyThatThrows); } ``` -snippet source | anchor +snippet source | anchor Or globally: @@ -1107,7 +1107,7 @@ VerifierSettings.IgnoreMember("Field"); // For a specific type with expression VerifierSettings.IgnoreMember(_ => _.PropertyThatThrows); ``` -snippet source | anchor +snippet source | anchor Result: @@ -1182,7 +1182,7 @@ public Task ScrubMemberByNameFluent() .ScrubMember(_ => _.PropertyThatThrows); } ``` -snippet source | anchor +snippet source | anchor Or globally: @@ -1202,7 +1202,7 @@ VerifierSettings.ScrubMember("Field"); // For a specific type with expression VerifierSettings.ScrubMember(_ => _.PropertyThatThrows); ``` -snippet source | anchor +snippet source | anchor Result: @@ -1253,7 +1253,7 @@ public Task CustomExceptionPropFluent() .IgnoreMembersThatThrow(); } ``` -snippet source | anchor +snippet source | anchor Or globally: @@ -1263,7 +1263,7 @@ Or globally: ```cs VerifierSettings.IgnoreMembersThatThrow(); ``` -snippet source | anchor +snippet source | anchor Result: @@ -1323,6 +1323,42 @@ Result: snippet source | anchor +## Always Include Members + +Sometimes it's convenient for certain members/types to always be included in the Verify output, even when they are equal to their default value. +The biggest example is numeric types e.g. `int` or `double` - output could be more consistent if values might fluctuate between zero and non-zero. + + + +```cs +[Fact] +public Task AlwaysIncludeType() +{ + var target = new + { + A = 0.0, + B = 1e-26 + }; + + return Verify(target) + .AlwaysIncludeMembersWithType(); +} +``` +snippet source | anchor + + +Result: + + + +```txt +{ + A: 0.0, + B: 1E-26 +} +``` +snippet source | anchor + ## TreatAsString @@ -1468,7 +1504,7 @@ public Task MemberConverterByExpression() return Verify(input); } ``` -snippet source | anchor +snippet source | anchor diff --git a/src/StrictJsonTests/SerializationTests.AlwaysIncludeType.verified.json b/src/StrictJsonTests/SerializationTests.AlwaysIncludeType.verified.json new file mode 100644 index 000000000..73214ded1 --- /dev/null +++ b/src/StrictJsonTests/SerializationTests.AlwaysIncludeType.verified.json @@ -0,0 +1,4 @@ +{ + "A": 0.0, + "B": 1E-26 +} \ No newline at end of file diff --git a/src/Verify.Tests/Serialization/SerializationTests.AlwaysIncludeType.verified.txt b/src/Verify.Tests/Serialization/SerializationTests.AlwaysIncludeType.verified.txt new file mode 100644 index 000000000..495416c94 --- /dev/null +++ b/src/Verify.Tests/Serialization/SerializationTests.AlwaysIncludeType.verified.txt @@ -0,0 +1,4 @@ +{ + A: 0.0, + B: 1E-26 +} \ No newline at end of file diff --git a/src/Verify.Tests/Serialization/SerializationTests.cs b/src/Verify.Tests/Serialization/SerializationTests.cs index 806d1de91..a3568ec2e 100644 --- a/src/Verify.Tests/Serialization/SerializationTests.cs +++ b/src/Verify.Tests/Serialization/SerializationTests.cs @@ -2985,6 +2985,19 @@ class IgnoreMembersNullableNestedTarget public ToIgnoreStruct? ToIgnoreStruct { get; set; } } + [Fact] + public Task AlwaysIncludeType() + { + var target = new + { + A = 0.0, + B = 1e-26 + }; + + return Verify(target) + .AlwaysIncludeMembersWithType(); + } + [Fact] public Task Type() => Verify(GetType()); diff --git a/src/Verify/Serialization/CustomContractResolver.cs b/src/Verify/Serialization/CustomContractResolver.cs index de2e3d005..0ed69ebe2 100644 --- a/src/Verify/Serialization/CustomContractResolver.cs +++ b/src/Verify/Serialization/CustomContractResolver.cs @@ -158,14 +158,19 @@ protected override JsonProperty CreateProperty(MemberInfo member, MemberSerializ if (settings.TryGetScrubOrIgnore(member, out var scrubOrIgnore)) { - if (scrubOrIgnore == ScrubOrIgnore.Ignore) + switch (scrubOrIgnore) { - property.Ignored = true; - } - else - { - property.PropertyType = typeof(string); - property.ValueProvider = new ScrubbedProvider(); + case ScrubOrIgnore.AlwaysInclude: + property.Ignored = false; + property.DefaultValueHandling = DefaultValueHandling.Include; + break; + case ScrubOrIgnore.Ignore: + property.Ignored = true; + break; + case ScrubOrIgnore.Scrub: + property.PropertyType = typeof(string); + property.ValueProvider = new ScrubbedProvider(); + break; } return property; diff --git a/src/Verify/Serialization/ScrubOrIgnore.cs b/src/Verify/Serialization/ScrubOrIgnore.cs index 08bcc7fa6..c0f6a9114 100644 --- a/src/Verify/Serialization/ScrubOrIgnore.cs +++ b/src/Verify/Serialization/ScrubOrIgnore.cs @@ -1,5 +1,6 @@ enum ScrubOrIgnore { Scrub, - Ignore + Ignore, + AlwaysInclude } \ No newline at end of file diff --git a/src/Verify/Serialization/SerializationSettings_IgnoreType.cs b/src/Verify/Serialization/SerializationSettings_IgnoreType.cs index 05b533713..badceca74 100644 --- a/src/Verify/Serialization/SerializationSettings_IgnoreType.cs +++ b/src/Verify/Serialization/SerializationSettings_IgnoreType.cs @@ -18,6 +18,13 @@ public void IgnoreMembersWithType() public void IgnoreMembersWithType(Type type) => Add(type, ScrubOrIgnore.Ignore); + public void AlwaysIncludeMembersWithType() + where T : notnull => + AlwaysIncludeMembersWithType(typeof(T)); + + public void AlwaysIncludeMembersWithType(Type type) => + Add(type, ScrubOrIgnore.AlwaysInclude); + void Add(Type type, ScrubOrIgnore scrubOrIgnore) { ignoredTypes[type] = scrubOrIgnore; diff --git a/src/Verify/Serialization/VerifierSettings_SerializationMaps.cs b/src/Verify/Serialization/VerifierSettings_SerializationMaps.cs index 3e7bd15c2..ecdc78f22 100644 --- a/src/Verify/Serialization/VerifierSettings_SerializationMaps.cs +++ b/src/Verify/Serialization/VerifierSettings_SerializationMaps.cs @@ -224,6 +224,13 @@ public static void ScrubMembersWithType() serialization.ScrubMembersWithType(); } + public static void AlwaysIncludeMembersWithType() + where T : notnull + { + InnerVerifier.ThrowIfVerifyHasBeenRun(); + serialization.AlwaysIncludeMembersWithType(); + } + public static void IgnoreMembersWithType(Type type) { InnerVerifier.ThrowIfVerifyHasBeenRun(); @@ -236,6 +243,12 @@ public static void ScrubMembersWithType(Type type) serialization.ScrubMembersWithType(type); } + public static void AlwaysIncludeMembersWithType(Type type) + { + InnerVerifier.ThrowIfVerifyHasBeenRun(); + serialization.AlwaysIncludeMembersWithType(type); + } + public static void IgnoreMembersThatThrow() where T : Exception { diff --git a/src/Verify/Serialization/VerifySettings_SerializationMaps.cs b/src/Verify/Serialization/VerifySettings_SerializationMaps.cs index 58e7f3ae1..203c14773 100644 --- a/src/Verify/Serialization/VerifySettings_SerializationMaps.cs +++ b/src/Verify/Serialization/VerifySettings_SerializationMaps.cs @@ -194,6 +194,13 @@ public void ScrubMembersWithType() serialization.ScrubMembersWithType(); } + public void AlwaysIncludeMembersWithType() + where T : notnull + { + CloneSettings(); + serialization.AlwaysIncludeMembersWithType(); + } + public void IgnoreMembersWithType(Type type) { CloneSettings(); @@ -206,6 +213,12 @@ public void ScrubMembersWithType(Type type) serialization.ScrubMembersWithType(type); } + public void AlwaysIncludeMembersWithType(Type type) + { + CloneSettings(); + serialization.AlwaysIncludeMembersWithType(type); + } + public void IgnoreMembersThatThrow() where T : Exception { diff --git a/src/Verify/SettingsTask_SerializationMaps.cs b/src/Verify/SettingsTask_SerializationMaps.cs index 43eea162c..5639a668f 100644 --- a/src/Verify/SettingsTask_SerializationMaps.cs +++ b/src/Verify/SettingsTask_SerializationMaps.cs @@ -217,6 +217,14 @@ public SettingsTask ScrubMembersWithType() return this; } + [Pure] + public SettingsTask AlwaysIncludeMembersWithType() + where T : notnull + { + CurrentSettings.AlwaysIncludeMembersWithType(); + return this; + } + [Pure] public SettingsTask IgnoreMembersWithType(Type type) { @@ -231,6 +239,13 @@ public SettingsTask ScrubMembersWithType(Type type) return this; } + [Pure] + public SettingsTask AlwaysIncludeMembersWithType(Type type) + { + CurrentSettings.AlwaysIncludeMembersWithType(type); + return this; + } + [Pure] public SettingsTask IgnoreMembersThatThrow() where T : Exception