Skip to content

Commit d42e439

Browse files
authored
fix: handle empty root-level collections with flow style in serialization (#4)
1 parent ee6cb37 commit d42e439

File tree

3 files changed

+76
-4
lines changed

3 files changed

+76
-4
lines changed

src/Yamlify.SourceGenerator/YamlSourceGenerator.cs

Lines changed: 38 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2900,14 +2900,48 @@ private static void GenerateRootDictionaryRead(StringBuilder sb, ITypeSymbol dic
29002900
/// </summary>
29012901
private static void GenerateRootCollectionWrite(StringBuilder sb, ITypeSymbol collectionType, ITypeSymbol elementType, IReadOnlyList<TypeToGenerate> allTypes)
29022902
{
2903-
sb.AppendLine(" writer.WriteSequenceStart();");
2904-
sb.AppendLine(" foreach (var item in value)");
2903+
// Use flow style for empty collections to output [] instead of nothing.
2904+
// For IEnumerable<T> that doesn't implement ICollection, we use TryGetNonEnumeratedCount or enumerate.
2905+
// Since we need to enumerate anyway for writing, we just check ICollection first for efficiency.
2906+
sb.AppendLine(" var hasItems = false;");
2907+
sb.AppendLine(" if (value is System.Collections.ICollection collection)");
29052908
sb.AppendLine(" {");
2909+
sb.AppendLine(" hasItems = collection.Count > 0;");
2910+
sb.AppendLine(" }");
2911+
sb.AppendLine(" else");
2912+
sb.AppendLine(" {");
2913+
sb.AppendLine(" // For pure IEnumerable, we need to start iterating to know if it's empty");
2914+
sb.AppendLine(" using var enumerator = value.GetEnumerator();");
2915+
sb.AppendLine(" hasItems = enumerator.MoveNext();");
2916+
sb.AppendLine(" if (hasItems)");
2917+
sb.AppendLine(" {");
2918+
sb.AppendLine(" // Non-empty IEnumerable - write sequence with first item, then rest");
2919+
sb.AppendLine(" writer.WriteSequenceStart();");
2920+
sb.AppendLine(" do");
2921+
sb.AppendLine(" {");
2922+
GenerateElementWrite(sb, "enumerator.Current", elementType, allTypes, " ");
2923+
sb.AppendLine(" } while (enumerator.MoveNext());");
2924+
sb.AppendLine(" writer.WriteSequenceEnd();");
2925+
sb.AppendLine(" return;");
2926+
sb.AppendLine(" }");
2927+
sb.AppendLine(" }");
2928+
sb.AppendLine();
2929+
sb.AppendLine(" if (!hasItems)");
2930+
sb.AppendLine(" {");
2931+
sb.AppendLine(" writer.WriteSequenceStart(Yamlify.CollectionStyle.Flow);");
2932+
sb.AppendLine(" writer.WriteSequenceEnd();");
2933+
sb.AppendLine(" }");
2934+
sb.AppendLine(" else");
2935+
sb.AppendLine(" {");
2936+
sb.AppendLine(" writer.WriteSequenceStart();");
2937+
sb.AppendLine(" foreach (var item in value)");
2938+
sb.AppendLine(" {");
29062939

2907-
GenerateElementWrite(sb, "item", elementType, allTypes, " ");
2940+
GenerateElementWrite(sb, "item", elementType, allTypes, " ");
29082941

2942+
sb.AppendLine(" }");
2943+
sb.AppendLine(" writer.WriteSequenceEnd();");
29092944
sb.AppendLine(" }");
2910-
sb.AppendLine(" writer.WriteSequenceEnd();");
29112945
sb.AppendLine(" }");
29122946
}
29132947

test/Yamlify.Tests/Serialization/CollectionSerializationTests.cs

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -779,6 +779,43 @@ public void RoundTripRootLevelList()
779779
}
780780
}
781781

782+
[Fact]
783+
public void SerializeEmptyRootLevelList_ProducesFlowStyleEmptySequence()
784+
{
785+
var list = new List<SimpleClass>();
786+
787+
var yaml = YamlSerializer.Serialize(list, TestSerializerContext.Default.ListSimpleClass);
788+
789+
// Empty root-level list should produce flow-style empty sequence []
790+
Assert.Equal("[]", yaml.Trim());
791+
}
792+
793+
[Fact]
794+
public void SerializeEmptyRootLevelIEnumerable_ProducesFlowStyleEmptySequence()
795+
{
796+
IEnumerable<SimpleClass> items = Array.Empty<SimpleClass>();
797+
798+
var yaml = YamlSerializer.Serialize(items, TestSerializerContext.Default.IEnumerableSimpleClass);
799+
800+
// Empty root-level IEnumerable should produce flow-style empty sequence []
801+
Assert.Equal("[]", yaml.Trim());
802+
}
803+
804+
[Fact]
805+
public void SerializeRootLevelIEnumerable_WithItems_ProducesBlockStyleSequence()
806+
{
807+
IEnumerable<SimpleClass> items = new[]
808+
{
809+
new SimpleClass { Name = "First", Value = 1, IsActive = true },
810+
new SimpleClass { Name = "Second", Value = 2, IsActive = false }
811+
};
812+
813+
var yaml = YamlSerializer.Serialize(items, TestSerializerContext.Default.IEnumerableSimpleClass);
814+
815+
Assert.Contains("name: First", yaml);
816+
Assert.Contains("name: Second", yaml);
817+
}
818+
782819
[Fact]
783820
public void SerializeRootLevelDictionary()
784821
{

test/Yamlify.Tests/Serialization/TestSerializerContext.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -95,6 +95,7 @@ namespace Yamlify.Tests.Serialization;
9595
[YamlSerializable(typeof(TypeCollision.NamespaceB.Config))]
9696
// Root-level collection types
9797
[YamlSerializable(typeof(List<SimpleClass>))]
98+
[YamlSerializable(typeof(IEnumerable<SimpleClass>))]
9899
[YamlSerializable(typeof(Dictionary<string, SimpleClass>))]
99100
// Default value preservation test types
100101
[YamlSerializable(typeof(ClassWithPropertyDefaults))]

0 commit comments

Comments
 (0)