diff --git a/src/Kuddle.Net.Tests/Serialization/ObjectMapperTests.cs b/src/Kuddle.Net.Tests/Serialization/ObjectMapperTests.cs index 9c9d28e..7034aa8 100644 --- a/src/Kuddle.Net.Tests/Serialization/ObjectMapperTests.cs +++ b/src/Kuddle.Net.Tests/Serialization/ObjectMapperTests.cs @@ -701,6 +701,104 @@ public async Task Deserialize_FlattenedCollection_CollectsAllMatchingNodes() await Assert.That(result.FlattenedServers[1].Host).IsEqualTo("remote"); } + [Test] + public async Task Serialize_WithSimpleCollectionNodeNames_True_UseDashNodeNames() + { + // Arrange + var model = new CollectionModel + { + WrappedPlugins = [new() { Name = "Auth" }], + FlattenedServers = [new() { Host = "localhost" }], + }; + + var options = new KdlSerializerOptions { SimpleCollectionNodeNames = true }; + + // Act + var kdl = KdlSerializer.Serialize(model, options); + + // Assert - should use dash (-) for collection items + await Assert.That(kdl).Contains("plugins {"); + await Assert.That(kdl).Contains("- Auth"); + } + + [Test] + public async Task Serialize_WithSimpleCollectionNodeNames_False_UseTypedNodeNames() + { + // Arrange + var model = new CollectionModel + { + WrappedPlugins = [new() { Name = "Auth" }], + FlattenedServers = [new() { Host = "localhost" }], + }; + + var options = new KdlSerializerOptions { SimpleCollectionNodeNames = false }; + + // Act + var kdl = KdlSerializer.Serialize(model, options); + + // Assert - should use typed node names + await Assert.That(kdl).Contains("plugins {"); + await Assert.That(kdl).Contains("plugin-info Auth"); + } + + [Test] + public async Task RoundTrip_SerializeAndDeserialize_WithSimpleCollectionNodeNames_True() + { + // Arrange + var originalModel = new CollectionModel + { + WrappedPlugins = [new() { Name = "Auth" }, new() { Name = "Logging" }], + FlattenedServers = [new() { Host = "localhost" }, new() { Host = "remote" }], + }; + + var options = new KdlSerializerOptions + { + SimpleCollectionNodeNames = true, + UnwrapRoot = true, + }; + + // Act + var kdl = KdlSerializer.Serialize(originalModel, options); + var deserializedModel = KdlSerializer.Deserialize(kdl, options); + + // Assert + await Assert.That(deserializedModel.WrappedPlugins).Count().IsEqualTo(2); + await Assert.That(deserializedModel.WrappedPlugins[0].Name).IsEqualTo("Auth"); + await Assert.That(deserializedModel.WrappedPlugins[1].Name).IsEqualTo("Logging"); + await Assert.That(deserializedModel.FlattenedServers).Count().IsEqualTo(2); + await Assert.That(deserializedModel.FlattenedServers[0].Host).IsEqualTo("localhost"); + await Assert.That(deserializedModel.FlattenedServers[1].Host).IsEqualTo("remote"); + } + + [Test] + public async Task RoundTrip_SerializeAndDeserialize_WithSimpleCollectionNodeNames_False() + { + // Arrange + var originalModel = new CollectionModel + { + WrappedPlugins = [new() { Name = "Auth" }, new() { Name = "Logging" }], + FlattenedServers = [new() { Host = "localhost" }, new() { Host = "remote" }], + }; + + var options = new KdlSerializerOptions + { + SimpleCollectionNodeNames = false, + UnwrapRoot = false, + }; + + // Act + var kdl = KdlSerializer.Serialize(originalModel, options); + var deserializedModel = KdlSerializer.Deserialize(kdl, options); + + // Assert + await Assert.That(deserializedModel.WrappedPlugins).Count().IsEqualTo(2); + await Assert.That(deserializedModel.WrappedPlugins[0].Name).IsEqualTo("Auth"); + await Assert.That(deserializedModel.WrappedPlugins[1].Name).IsEqualTo("Logging"); + await Assert.That(deserializedModel.FlattenedServers).Count().IsEqualTo(2); + await Assert.That(deserializedModel.FlattenedServers[0].Host).IsEqualTo("localhost"); + await Assert.That(deserializedModel.FlattenedServers[1].Host).IsEqualTo("remote"); + } + #endregion #region Test Models diff --git a/src/Kuddle.Net/Serialization/KdlSerializerOptions.cs b/src/Kuddle.Net/Serialization/KdlSerializerOptions.cs index 0195db9..ff3575b 100644 --- a/src/Kuddle.Net/Serialization/KdlSerializerOptions.cs +++ b/src/Kuddle.Net/Serialization/KdlSerializerOptions.cs @@ -5,6 +5,11 @@ namespace Kuddle.Serialization; /// public record KdlSerializerOptions { + /// + /// Default options instance. + /// + public static KdlSerializerOptions Default { get; } = new(); + /// /// Whether to ignore null values when serializing. Default is true. /// @@ -21,8 +26,8 @@ public record KdlSerializerOptions public bool WriteTypeAnnotations { get; init; } = true; /// - /// Default options instance. + /// Whether to use simple dash (-) for collection item node names instead of the mapped type name. Default is true. /// - public static KdlSerializerOptions Default { get; } = new(); + public bool SimpleCollectionNodeNames { get; init; } = true; public bool UnwrapRoot { get; init; } = false; } diff --git a/src/Kuddle.Net/Serialization/ObjectDeserializer.cs b/src/Kuddle.Net/Serialization/ObjectDeserializer.cs index 6dff304..4f08b8b 100644 --- a/src/Kuddle.Net/Serialization/ObjectDeserializer.cs +++ b/src/Kuddle.Net/Serialization/ObjectDeserializer.cs @@ -25,6 +25,17 @@ internal static T DeserializeDocument(KdlDocument doc, KdlSerializerOptions? var mapping = KdlTypeMapping.For(); var instance = new T(); + if (options?.UnwrapRoot == false) + { + if (doc.Nodes.Count != 1) + { + throw new KuddleSerializationException( + $"Expected exactly 1 root node, but found {doc.Nodes.Count}." + ); + } + worker.MapNodeToObject(doc.Nodes.First(), instance, mapping); + return instance; + } if (mapping.Arguments.Count > 0 || mapping.Properties.Count > 0) { diff --git a/src/Kuddle.Net/Serialization/ObjectSerializer.cs b/src/Kuddle.Net/Serialization/ObjectSerializer.cs index 43d39d9..a29c973 100644 --- a/src/Kuddle.Net/Serialization/ObjectSerializer.cs +++ b/src/Kuddle.Net/Serialization/ObjectSerializer.cs @@ -60,10 +60,10 @@ internal static KdlDocument SerializeDocument(T? instance, KdlSerializerOptio // Map positional arguments to anonymous nodes doc.Nodes.Add(new KdlNode(KdlValue.From("-")) { Entries = [arg] }); } - if (rootNode.Children != null) - { - doc.Nodes.AddRange(rootNode.Children.Nodes); - } + } + if (rootNode.Children != null) + { + doc.Nodes.AddRange(rootNode.Children.Nodes); } } else @@ -198,8 +198,15 @@ private IEnumerable SerializeCollection(IEnumerable enumerable, KdlMemb } } - private static string GetDefaultNodeName(object item) => - item.GetType().IsKdlScalar ? "-" : KdlTypeMapping.For(item.GetType()).NodeName; + private string GetDefaultNodeName(object item) + { + if (item.GetType().IsKdlScalar) + return "-"; + + return _options.SimpleCollectionNodeNames + ? "-" + : KdlTypeMapping.For(item.GetType()).NodeName; + } private KdlNode MapToNode(object item, string kdlName, string? typeAnnotation) {