From 951b086ffa98597fea11d06fe36ca2e1574115d6 Mon Sep 17 00:00:00 2001 From: glucaci Date: Mon, 12 Jan 2026 06:35:43 +0100 Subject: [PATCH] fix: implement YamlIgnoreCondition handling for serialization and deserialization --- .../YamlSourceGenerator.cs | 108 ++- .../Serialization/YamlIgnoreConditionTests.cs | 693 ++++++++++++++++++ 2 files changed, 792 insertions(+), 9 deletions(-) create mode 100644 test/Yamlify.Tests/Serialization/YamlIgnoreConditionTests.cs diff --git a/src/Yamlify.SourceGenerator/YamlSourceGenerator.cs b/src/Yamlify.SourceGenerator/YamlSourceGenerator.cs index 2ad407f..b465faf 100644 --- a/src/Yamlify.SourceGenerator/YamlSourceGenerator.cs +++ b/src/Yamlify.SourceGenerator/YamlSourceGenerator.cs @@ -1750,6 +1750,14 @@ private static void GenerateWriteMethod(StringBuilder sb, TypeToGenerate type, I } } + // Check for YamlIgnore with conditional conditions (WhenWritingNull, WhenWritingDefault) + var writeIgnoreCondition = GetWriteIgnoreCondition(prop); + if (writeIgnoreCondition == IgnoreCondition.Always) + { + // Already filtered out in the allProperties list, but double-check + continue; + } + // Check for sibling discriminator // For dictionaries, we want sibling discriminator to apply to the dictionary values // For regular lists/arrays, sibling discriminator doesn't make sense (each element should determine its own type) @@ -1825,8 +1833,20 @@ private static void GenerateWriteMethod(StringBuilder sb, TypeToGenerate type, I } } - // Wrap nullable properties with IgnoreNullValues and IgnoreEmptyObjects checks - sb.AppendLine($" if (!options.IgnoreNullValues || ({string.Join(" && ", conditions)}))"); + // Handle YamlIgnore conditions: + // - WhenWritingNull: Skip if null (property-level, always respected) + // - WhenWritingDefault: Skip if null (for nullable types, null is the default) + if (writeIgnoreCondition == IgnoreCondition.WhenWritingNull || + writeIgnoreCondition == IgnoreCondition.WhenWritingDefault) + { + // Always skip if null, regardless of options.IgnoreNullValues + sb.AppendLine($" if ({string.Join(" && ", conditions)})"); + } + else + { + // Wrap nullable properties with IgnoreNullValues and IgnoreEmptyObjects checks + sb.AppendLine($" if (!options.IgnoreNullValues || ({string.Join(" && ", conditions)}))"); + } sb.AppendLine(" {"); sb.AppendLine($" writer.WritePropertyName({propertyNameCode});"); GeneratePropertyWrite(sb, propName, prop.Type, allTypes, " ", siblingInfo); @@ -1834,9 +1854,23 @@ private static void GenerateWriteMethod(StringBuilder sb, TypeToGenerate type, I } else { - // Non-nullable properties are always written - sb.AppendLine($" writer.WritePropertyName({propertyNameCode});"); - GeneratePropertyWrite(sb, propName, prop.Type, allTypes, "", siblingInfo); + // Non-nullable properties - check for WhenWritingDefault + if (writeIgnoreCondition == IgnoreCondition.WhenWritingDefault) + { + // Skip if value equals default for the type + var defaultValue = GetDefaultValue(prop.Type); + sb.AppendLine($" if (!EqualityComparer<{prop.Type.ToDisplayString()}>.Default.Equals(value.{propName}, {defaultValue}))"); + sb.AppendLine(" {"); + sb.AppendLine($" writer.WritePropertyName({propertyNameCode});"); + GeneratePropertyWrite(sb, propName, prop.Type, allTypes, " ", siblingInfo); + sb.AppendLine(" }"); + } + else + { + // Non-nullable properties are always written + sb.AppendLine($" writer.WritePropertyName({propertyNameCode});"); + GeneratePropertyWrite(sb, propName, prop.Type, allTypes, "", siblingInfo); + } } sb.AppendLine(); } @@ -2537,18 +2571,74 @@ private static string GetYamlPropertyName(IPropertySymbol property) return null; } - private static bool ShouldIgnoreProperty(IPropertySymbol property) + /// + /// Represents the YamlIgnoreCondition enum values from the runtime. + /// + private enum IgnoreCondition + { + Always = 0, + WhenWritingNull = 1, + WhenWritingDefault = 2, + Never = 3 + } + + /// + /// Gets the ignore condition for a property, or null if no YamlIgnore attribute is present. + /// + private static IgnoreCondition? GetIgnoreCondition(IPropertySymbol property) { - // Check for YamlIgnore attribute foreach (var attr in property.GetAttributes()) { if (attr.AttributeClass?.Name == "YamlIgnoreAttribute" || attr.AttributeClass?.ToDisplayString() == "Yamlify.Serialization.YamlIgnoreAttribute") { - return true; + // Check for the Condition named argument + foreach (var namedArg in attr.NamedArguments) + { + if (namedArg.Key == "Condition" && namedArg.Value.Value is int conditionValue) + { + return (IgnoreCondition)conditionValue; + } + } + // Default is Always if Condition is not specified + return IgnoreCondition.Always; } } - return false; + return null; + } + + /// + /// Determines if a property should be ignored during deserialization (reading). + /// Only properties with YamlIgnore(Condition = Always) are ignored during reading. + /// Properties with WhenWritingNull/WhenWritingDefault are still read but may be skipped during writing. + /// + private static bool ShouldIgnorePropertyForReading(IPropertySymbol property) + { + var condition = GetIgnoreCondition(property); + return condition == IgnoreCondition.Always; + } + + /// + /// Determines if a property should be ignored during serialization (writing). + /// Returns the ignore condition, or null if the property should always be written. + /// + private static IgnoreCondition? GetWriteIgnoreCondition(IPropertySymbol property) + { + var condition = GetIgnoreCondition(property); + if (condition == IgnoreCondition.Never) + { + return null; // Never ignore = always write + } + return condition; + } + + /// + /// Legacy method for backward compatibility - use ShouldIgnorePropertyForReading for reading. + /// + private static bool ShouldIgnoreProperty(IPropertySymbol property) + { + // For reading, only ignore if condition is Always + return ShouldIgnorePropertyForReading(property); } private static int GetPropertyOrder(IPropertySymbol property) diff --git a/test/Yamlify.Tests/Serialization/YamlIgnoreConditionTests.cs b/test/Yamlify.Tests/Serialization/YamlIgnoreConditionTests.cs new file mode 100644 index 0000000..4791d81 --- /dev/null +++ b/test/Yamlify.Tests/Serialization/YamlIgnoreConditionTests.cs @@ -0,0 +1,693 @@ +using Yamlify; +using Yamlify.Serialization; + +namespace Yamlify.Tests.Serialization; + +/// +/// Tests for YamlIgnore attribute with different YamlIgnoreCondition values. +/// +public class YamlIgnoreConditionTests +{ + #region YamlIgnoreCondition.Always Tests + + [Fact] + public void Serialize_PropertyWithIgnoreAlways_PropertyNotWritten() + { + // Arrange + var obj = new ClassWithIgnoreAlways + { + Name = "Test", + AlwaysIgnored = "Should not appear", + Value = 42 + }; + + // Act + var yaml = YamlSerializer.Serialize(obj, YamlIgnoreConditionContext.Default.ClassWithIgnoreAlways); + + // Assert + Assert.Contains("name: Test", yaml); + Assert.Contains("value: 42", yaml); + Assert.DoesNotContain("always-ignored:", yaml); + Assert.DoesNotContain("Should not appear", yaml); + } + + [Fact] + public void Deserialize_PropertyWithIgnoreAlways_PropertyNotRead() + { + // Arrange + const string yaml = """ + name: Test + always-ignored: This value exists in yaml + value: 42 + """; + + // Act + var result = YamlSerializer.Deserialize(yaml, YamlIgnoreConditionContext.Default.ClassWithIgnoreAlways); + + // Assert + Assert.NotNull(result); + Assert.Equal("Test", result.Name); + Assert.Null(result.AlwaysIgnored); // Should remain default (null) + Assert.Equal(42, result.Value); + } + + [Fact] + public void RoundTrip_PropertyWithIgnoreAlways_PropertyLost() + { + // Arrange + var original = new ClassWithIgnoreAlways + { + Name = "Test", + AlwaysIgnored = "This will be lost", + Value = 42 + }; + + // Act + var yaml = YamlSerializer.Serialize(original, YamlIgnoreConditionContext.Default.ClassWithIgnoreAlways); + var result = YamlSerializer.Deserialize(yaml, YamlIgnoreConditionContext.Default.ClassWithIgnoreAlways); + + // Assert + Assert.NotNull(result); + Assert.Equal("Test", result.Name); + Assert.Null(result.AlwaysIgnored); // Lost during round trip + Assert.Equal(42, result.Value); + } + + #endregion + + #region YamlIgnoreCondition.WhenWritingNull Tests + + [Fact] + public void Serialize_PropertyWithWhenWritingNull_NullValue_PropertyNotWritten() + { + // Arrange + var obj = new ClassWithIgnoreWhenWritingNull + { + Name = "Test", + OptionalValue = null, + RequiredValue = 42 + }; + + // Act + var yaml = YamlSerializer.Serialize(obj, YamlIgnoreConditionContext.Default.ClassWithIgnoreWhenWritingNull); + + // Assert + Assert.Contains("name: Test", yaml); + Assert.Contains("required-value: 42", yaml); + Assert.DoesNotContain("optional-value:", yaml); + } + + [Fact] + public void Serialize_PropertyWithWhenWritingNull_NonNullValue_PropertyWritten() + { + // Arrange + var obj = new ClassWithIgnoreWhenWritingNull + { + Name = "Test", + OptionalValue = "Has a value", + RequiredValue = 42 + }; + + // Act + var yaml = YamlSerializer.Serialize(obj, YamlIgnoreConditionContext.Default.ClassWithIgnoreWhenWritingNull); + + // Assert + Assert.Contains("name: Test", yaml); + Assert.Contains("optional-value: Has a value", yaml); + Assert.Contains("required-value: 42", yaml); + } + + [Fact] + public void Deserialize_PropertyWithWhenWritingNull_PropertyPresent_PropertyRead() + { + // Arrange - WhenWritingNull only affects writing, not reading + const string yaml = """ + name: Test + optional-value: Value from YAML + required-value: 42 + """; + + // Act + var result = YamlSerializer.Deserialize(yaml, YamlIgnoreConditionContext.Default.ClassWithIgnoreWhenWritingNull); + + // Assert + Assert.NotNull(result); + Assert.Equal("Test", result.Name); + Assert.Equal("Value from YAML", result.OptionalValue); + Assert.Equal(42, result.RequiredValue); + } + + [Fact] + public void RoundTrip_PropertyWithWhenWritingNull_NonNullValue_Preserved() + { + // Arrange + var original = new ClassWithIgnoreWhenWritingNull + { + Name = "Test", + OptionalValue = "Keep this", + RequiredValue = 42 + }; + + // Act + var yaml = YamlSerializer.Serialize(original, YamlIgnoreConditionContext.Default.ClassWithIgnoreWhenWritingNull); + var result = YamlSerializer.Deserialize(yaml, YamlIgnoreConditionContext.Default.ClassWithIgnoreWhenWritingNull); + + // Assert + Assert.NotNull(result); + Assert.Equal("Test", result.Name); + Assert.Equal("Keep this", result.OptionalValue); + Assert.Equal(42, result.RequiredValue); + } + + [Fact] + public void Serialize_NullableBoolWithWhenWritingNull_NullValue_PropertyNotWritten() + { + // Arrange + var obj = new ClassWithNullableBoolIgnoreWhenNull + { + Name = "Test", + OptionalFlag = null + }; + + // Act + var yaml = YamlSerializer.Serialize(obj, YamlIgnoreConditionContext.Default.ClassWithNullableBoolIgnoreWhenNull); + + // Assert + Assert.Contains("name: Test", yaml); + Assert.DoesNotContain("optional-flag:", yaml); + } + + [Fact] + public void Serialize_NullableBoolWithWhenWritingNull_FalseValue_PropertyWritten() + { + // Arrange - false is not null, so it should be written + var obj = new ClassWithNullableBoolIgnoreWhenNull + { + Name = "Test", + OptionalFlag = false + }; + + // Act + var yaml = YamlSerializer.Serialize(obj, YamlIgnoreConditionContext.Default.ClassWithNullableBoolIgnoreWhenNull); + + // Assert + Assert.Contains("name: Test", yaml); + Assert.Contains("optional-flag: false", yaml); + } + + [Fact] + public void Serialize_NullableBoolWithWhenWritingNull_TrueValue_PropertyWritten() + { + // Arrange + var obj = new ClassWithNullableBoolIgnoreWhenNull + { + Name = "Test", + OptionalFlag = true + }; + + // Act + var yaml = YamlSerializer.Serialize(obj, YamlIgnoreConditionContext.Default.ClassWithNullableBoolIgnoreWhenNull); + + // Assert + Assert.Contains("name: Test", yaml); + Assert.Contains("optional-flag: true", yaml); + } + + #endregion + + #region YamlIgnoreCondition.WhenWritingDefault Tests + + [Fact] + public void Serialize_IntWithWhenWritingDefault_ZeroValue_PropertyNotWritten() + { + // Arrange + var obj = new ClassWithIgnoreWhenWritingDefault + { + Name = "Test", + Counter = 0 // default for int + }; + + // Act + var yaml = YamlSerializer.Serialize(obj, YamlIgnoreConditionContext.Default.ClassWithIgnoreWhenWritingDefault); + + // Assert + Assert.Contains("name: Test", yaml); + Assert.DoesNotContain("counter:", yaml); + } + + [Fact] + public void Serialize_IntWithWhenWritingDefault_NonZeroValue_PropertyWritten() + { + // Arrange + var obj = new ClassWithIgnoreWhenWritingDefault + { + Name = "Test", + Counter = 5 + }; + + // Act + var yaml = YamlSerializer.Serialize(obj, YamlIgnoreConditionContext.Default.ClassWithIgnoreWhenWritingDefault); + + // Assert + Assert.Contains("name: Test", yaml); + Assert.Contains("counter: 5", yaml); + } + + [Fact] + public void Serialize_BoolWithWhenWritingDefault_FalseValue_PropertyNotWritten() + { + // Arrange + var obj = new ClassWithBoolIgnoreWhenDefault + { + Name = "Test", + IsEnabled = false // default for bool + }; + + // Act + var yaml = YamlSerializer.Serialize(obj, YamlIgnoreConditionContext.Default.ClassWithBoolIgnoreWhenDefault); + + // Assert + Assert.Contains("name: Test", yaml); + Assert.DoesNotContain("is-enabled:", yaml); + } + + [Fact] + public void Serialize_BoolWithWhenWritingDefault_TrueValue_PropertyWritten() + { + // Arrange + var obj = new ClassWithBoolIgnoreWhenDefault + { + Name = "Test", + IsEnabled = true + }; + + // Act + var yaml = YamlSerializer.Serialize(obj, YamlIgnoreConditionContext.Default.ClassWithBoolIgnoreWhenDefault); + + // Assert + Assert.Contains("name: Test", yaml); + Assert.Contains("is-enabled: true", yaml); + } + + [Fact] + public void Serialize_StringWithWhenWritingDefault_NullValue_PropertyNotWritten() + { + // Arrange - default for string? is null + var obj = new ClassWithStringIgnoreWhenDefault + { + Name = "Test", + Description = null + }; + + // Act + var yaml = YamlSerializer.Serialize(obj, YamlIgnoreConditionContext.Default.ClassWithStringIgnoreWhenDefault); + + // Assert + Assert.Contains("name: Test", yaml); + Assert.DoesNotContain("description:", yaml); + } + + [Fact] + public void Serialize_StringWithWhenWritingDefault_EmptyString_PropertyWritten() + { + // Arrange - empty string is not null (the default), so it should be written + var obj = new ClassWithStringIgnoreWhenDefault + { + Name = "Test", + Description = "" + }; + + // Act + var yaml = YamlSerializer.Serialize(obj, YamlIgnoreConditionContext.Default.ClassWithStringIgnoreWhenDefault); + + // Assert + Assert.Contains("name: Test", yaml); + Assert.Contains("description:", yaml); + } + + [Fact] + public void Deserialize_PropertyWithWhenWritingDefault_PropertyPresent_PropertyRead() + { + // Arrange - WhenWritingDefault only affects writing, not reading + const string yaml = """ + name: Test + counter: 0 + """; + + // Act + var result = YamlSerializer.Deserialize(yaml, YamlIgnoreConditionContext.Default.ClassWithIgnoreWhenWritingDefault); + + // Assert + Assert.NotNull(result); + Assert.Equal("Test", result.Name); + Assert.Equal(0, result.Counter); + } + + #endregion + + #region YamlIgnoreCondition.Never Tests + + [Fact] + public void Serialize_PropertyWithIgnoreNever_NullValue_PropertyWritten() + { + // Arrange - Never means always write, even if null + var obj = new ClassWithIgnoreNever + { + Name = "Test", + AlwaysWritten = null + }; + + // Act + var yaml = YamlSerializer.Serialize(obj, YamlIgnoreConditionContext.Default.ClassWithIgnoreNever); + + // Assert + Assert.Contains("name: Test", yaml); + Assert.Contains("always-written:", yaml); + } + + [Fact] + public void Serialize_PropertyWithIgnoreNever_NonNullValue_PropertyWritten() + { + // Arrange + var obj = new ClassWithIgnoreNever + { + Name = "Test", + AlwaysWritten = "Has a value" + }; + + // Act + var yaml = YamlSerializer.Serialize(obj, YamlIgnoreConditionContext.Default.ClassWithIgnoreNever); + + // Assert + Assert.Contains("name: Test", yaml); + Assert.Contains("always-written: Has a value", yaml); + } + + [Fact] + public void Deserialize_PropertyWithIgnoreNever_PropertyPresent_PropertyRead() + { + // Arrange + const string yaml = """ + name: Test + always-written: Value from YAML + """; + + // Act + var result = YamlSerializer.Deserialize(yaml, YamlIgnoreConditionContext.Default.ClassWithIgnoreNever); + + // Assert + Assert.NotNull(result); + Assert.Equal("Test", result.Name); + Assert.Equal("Value from YAML", result.AlwaysWritten); + } + + #endregion + + #region Legacy Migration Scenario Tests + + [Fact] + public void Deserialize_LegacyProperty_WithWhenWritingNull_PropertyRead() + { + // Arrange - Simulates reading a legacy YAML format + const string yaml = """ + name: my-feature + legacy-enabled: true + """; + + // Act + var result = YamlSerializer.Deserialize(yaml, YamlIgnoreConditionContext.Default.FeatureConfig); + + // Assert + Assert.NotNull(result); + Assert.Equal("my-feature", result.Name); + Assert.True(result.LegacyEnabled); // Should be read from YAML + } + + [Fact] + public void Serialize_LegacyProperty_WithWhenWritingNull_WhenNull_PropertyNotWritten() + { + // Arrange - Legacy property is null (not migrated or already migrated) + var obj = new FeatureConfig + { + Name = "my-feature", + LegacyEnabled = null, + Settings = new FeatureConfigSettings { Enabled = true } + }; + + // Act + var yaml = YamlSerializer.Serialize(obj, YamlIgnoreConditionContext.Default.FeatureConfig); + + // Assert + Assert.Contains("name: my-feature", yaml); + Assert.Contains("settings:", yaml); + Assert.Contains("enabled: true", yaml); + Assert.DoesNotContain("legacy-enabled:", yaml); // Should not be written + } + + [Fact] + public void RoundTrip_LegacyPropertyRead_ThenNotWritten() + { + // Arrange - Simulates reading legacy YAML + const string legacyYaml = """ + name: my-feature + legacy-enabled: false + """; + + // Act - Read the legacy format + var config = YamlSerializer.Deserialize(legacyYaml, YamlIgnoreConditionContext.Default.FeatureConfig); + + // Simulate migration: use legacy value to populate new format, then clear legacy + if (config != null && config.LegacyEnabled.HasValue) + { + config.Settings = new FeatureConfigSettings { Enabled = config.LegacyEnabled.Value }; + config.LegacyEnabled = null; + } + + // Serialize back + var newYaml = YamlSerializer.Serialize(config!, YamlIgnoreConditionContext.Default.FeatureConfig); + + // Assert - Legacy property should not appear in output + Assert.Contains("name: my-feature", newYaml); + Assert.Contains("settings:", newYaml); + Assert.Contains("enabled: false", newYaml); + Assert.DoesNotContain("legacy-enabled:", newYaml); + } + + #endregion + + #region Multiple Conditions on Different Properties + + [Fact] + public void Serialize_MixedConditions_CorrectPropertiesWritten() + { + // Arrange + var obj = new ClassWithMixedConditions + { + Name = "Test", + AlwaysIgnored = "should not appear", + NullIgnored = null, + DefaultIgnored = 0, + NeverIgnored = null + }; + + // Act + var yaml = YamlSerializer.Serialize(obj, YamlIgnoreConditionContext.Default.ClassWithMixedConditions); + + // Assert + Assert.Contains("name: Test", yaml); + Assert.DoesNotContain("always-ignored:", yaml); // Always ignored + Assert.DoesNotContain("null-ignored:", yaml); // WhenWritingNull + null value + Assert.DoesNotContain("default-ignored:", yaml); // WhenWritingDefault + 0 + Assert.Contains("never-ignored:", yaml); // Never - always written even when null + } + + [Fact] + public void Serialize_MixedConditions_AllNonDefault_AllWritten() + { + // Arrange + var obj = new ClassWithMixedConditions + { + Name = "Test", + AlwaysIgnored = "still ignored", + NullIgnored = "has value", + DefaultIgnored = 5, + NeverIgnored = "has value" + }; + + // Act + var yaml = YamlSerializer.Serialize(obj, YamlIgnoreConditionContext.Default.ClassWithMixedConditions); + + // Assert + Assert.Contains("name: Test", yaml); + Assert.DoesNotContain("always-ignored:", yaml); // Still ignored + Assert.Contains("null-ignored: has value", yaml); // Written because not null + Assert.Contains("default-ignored: 5", yaml); // Written because not default + Assert.Contains("never-ignored: has value", yaml); // Always written + } + + #endregion +} + +#region Test Models + +/// +/// Class with YamlIgnore(Condition = Always) - property completely ignored. +/// +public class ClassWithIgnoreAlways +{ + public string? Name { get; set; } + + [YamlPropertyName("always-ignored")] + [YamlIgnore(Condition = YamlIgnoreCondition.Always)] + public string? AlwaysIgnored { get; set; } + + public int Value { get; set; } +} + +/// +/// Class with YamlIgnore(Condition = WhenWritingNull) - only ignored during serialization when null. +/// +public class ClassWithIgnoreWhenWritingNull +{ + public string? Name { get; set; } + + [YamlPropertyName("optional-value")] + [YamlIgnore(Condition = YamlIgnoreCondition.WhenWritingNull)] + public string? OptionalValue { get; set; } + + [YamlPropertyName("required-value")] + public int RequiredValue { get; set; } +} + +/// +/// Class with nullable bool and WhenWritingNull condition. +/// +public class ClassWithNullableBoolIgnoreWhenNull +{ + public string? Name { get; set; } + + [YamlPropertyName("optional-flag")] + [YamlIgnore(Condition = YamlIgnoreCondition.WhenWritingNull)] + public bool? OptionalFlag { get; set; } +} + +/// +/// Class with YamlIgnore(Condition = WhenWritingDefault) on an int property. +/// +public class ClassWithIgnoreWhenWritingDefault +{ + public string? Name { get; set; } + + [YamlIgnore(Condition = YamlIgnoreCondition.WhenWritingDefault)] + public int Counter { get; set; } +} + +/// +/// Class with YamlIgnore(Condition = WhenWritingDefault) on a bool property. +/// +public class ClassWithBoolIgnoreWhenDefault +{ + public string? Name { get; set; } + + [YamlPropertyName("is-enabled")] + [YamlIgnore(Condition = YamlIgnoreCondition.WhenWritingDefault)] + public bool IsEnabled { get; set; } +} + +/// +/// Class with YamlIgnore(Condition = WhenWritingDefault) on a string property. +/// +public class ClassWithStringIgnoreWhenDefault +{ + public string? Name { get; set; } + + [YamlIgnore(Condition = YamlIgnoreCondition.WhenWritingDefault)] + public string? Description { get; set; } +} + +/// +/// Class with YamlIgnore(Condition = Never) - always written even when null. +/// +public class ClassWithIgnoreNever +{ + public string? Name { get; set; } + + [YamlPropertyName("always-written")] + [YamlIgnore(Condition = YamlIgnoreCondition.Never)] + public string? AlwaysWritten { get; set; } +} + +/// +/// Simulates a legacy migration scenario where a property is read but not written back. +/// +public class FeatureConfig +{ + public string? Name { get; set; } + + /// + /// Legacy property - read from old YAML but not written back. + /// + [YamlPropertyName("legacy-enabled")] + [YamlIgnore(Condition = YamlIgnoreCondition.WhenWritingNull)] + public bool? LegacyEnabled { get; set; } + + /// + /// New format settings object. + /// + public FeatureConfigSettings? Settings { get; set; } +} + +/// +/// Settings object for the new format. +/// +public class FeatureConfigSettings +{ + public bool Enabled { get; set; } +} + +/// +/// Class with different YamlIgnoreCondition values on different properties. +/// +public class ClassWithMixedConditions +{ + public string? Name { get; set; } + + [YamlPropertyName("always-ignored")] + [YamlIgnore(Condition = YamlIgnoreCondition.Always)] + public string? AlwaysIgnored { get; set; } + + [YamlPropertyName("null-ignored")] + [YamlIgnore(Condition = YamlIgnoreCondition.WhenWritingNull)] + public string? NullIgnored { get; set; } + + [YamlPropertyName("default-ignored")] + [YamlIgnore(Condition = YamlIgnoreCondition.WhenWritingDefault)] + public int DefaultIgnored { get; set; } + + [YamlPropertyName("never-ignored")] + [YamlIgnore(Condition = YamlIgnoreCondition.Never)] + public string? NeverIgnored { get; set; } +} + +#endregion + +#region Serializer Context + +/// +/// Serializer context with IgnoreNullValues disabled to properly test YamlIgnoreCondition. +/// +[YamlSerializable] +[YamlSerializable] +[YamlSerializable] +[YamlSerializable] +[YamlSerializable] +[YamlSerializable] +[YamlSerializable] +[YamlSerializable] +[YamlSerializable] +[YamlSerializable] +public partial class YamlIgnoreConditionContext : YamlSerializerContext +{ +} + +#endregion