diff --git a/src/Directory.Packages.props b/src/Directory.Packages.props
index 1c44b45d..daf3c7fd 100644
--- a/src/Directory.Packages.props
+++ b/src/Directory.Packages.props
@@ -31,7 +31,7 @@
-
+
diff --git a/src/PAModel/packages.lock.json b/src/PAModel/packages.lock.json
index 0464a0b4..7b4250db 100644
--- a/src/PAModel/packages.lock.json
+++ b/src/PAModel/packages.lock.json
@@ -42,9 +42,9 @@
},
"YamlDotNet": {
"type": "Direct",
- "requested": "[15.1.6, )",
- "resolved": "15.1.6",
- "contentHash": "T/cQEK/KHK96Q8kytJ4iUGDXg1/fj2Qtk6rCQeIlHYU1zTeyGVHW0QNZgREQyxZpygGMDMmrXNWt0sj5TsQnjA=="
+ "requested": "[16.1.3, )",
+ "resolved": "16.1.3",
+ "contentHash": "gtHGiDvU9VTtWte8f0thIM38cL1oowOjStKpeAEKKfA+Rc4AvekJzqFDZiiPcc4kw00ZiwR4OTJS56L16q98DQ=="
},
"Microsoft.Bcl.AsyncInterfaces": {
"type": "Transitive",
@@ -116,9 +116,9 @@
},
"YamlDotNet": {
"type": "Direct",
- "requested": "[15.1.6, )",
- "resolved": "15.1.6",
- "contentHash": "T/cQEK/KHK96Q8kytJ4iUGDXg1/fj2Qtk6rCQeIlHYU1zTeyGVHW0QNZgREQyxZpygGMDMmrXNWt0sj5TsQnjA=="
+ "requested": "[16.1.3, )",
+ "resolved": "16.1.3",
+ "contentHash": "gtHGiDvU9VTtWte8f0thIM38cL1oowOjStKpeAEKKfA+Rc4AvekJzqFDZiiPcc4kw00ZiwR4OTJS56L16q98DQ=="
}
},
"net8.0": {
@@ -142,9 +142,9 @@
},
"YamlDotNet": {
"type": "Direct",
- "requested": "[15.1.6, )",
- "resolved": "15.1.6",
- "contentHash": "T/cQEK/KHK96Q8kytJ4iUGDXg1/fj2Qtk6rCQeIlHYU1zTeyGVHW0QNZgREQyxZpygGMDMmrXNWt0sj5TsQnjA=="
+ "requested": "[16.1.3, )",
+ "resolved": "16.1.3",
+ "contentHash": "gtHGiDvU9VTtWte8f0thIM38cL1oowOjStKpeAEKKfA+Rc4AvekJzqFDZiiPcc4kw00ZiwR4OTJS56L16q98DQ=="
}
}
}
diff --git a/src/Persistence.Tests/PaYaml/Serialization/NamedObjectMappingSerializationTests.cs b/src/Persistence.Tests/PaYaml/Serialization/NamedObjectMappingSerializationTests.cs
index d4d2c89e..58cace34 100644
--- a/src/Persistence.Tests/PaYaml/Serialization/NamedObjectMappingSerializationTests.cs
+++ b/src/Persistence.Tests/PaYaml/Serialization/NamedObjectMappingSerializationTests.cs
@@ -2,12 +2,26 @@
// Licensed under the MIT License.
using Microsoft.PowerPlatform.PowerApps.Persistence.PaYaml.Models;
+using Microsoft.PowerPlatform.PowerApps.Persistence.PaYaml.Serialization;
+using YamlDotNet.Serialization;
namespace Persistence.Tests.PaYaml.Serialization;
[TestClass]
public class NamedObjectMappingSerializationTests : SerializationTestBase
{
+ protected override void ConfigureYamlDotNetDeserializer(DeserializerBuilder builder, PaYamlSerializationContext context)
+ {
+ base.ConfigureYamlDotNetDeserializer(builder, context);
+ builder.WithTypeConverter(new NamedObjectMappingYamlConverter());
+ }
+
+ protected override void ConfigureYamlDotNetSerializer(SerializerBuilder builder, PaYamlSerializationContext context)
+ {
+ base.ConfigureYamlDotNetSerializer(builder, context);
+ builder.WithTypeConverter(new NamedObjectMappingYamlConverter());
+ }
+
[TestMethod]
// Null literals
[DataRow("TheMapping: ~", null)]
diff --git a/src/Persistence.Tests/PaYaml/Serialization/NamedObjectSequenceSerializationTests.cs b/src/Persistence.Tests/PaYaml/Serialization/NamedObjectSequenceSerializationTests.cs
index 4f979c9f..686fb4c4 100644
--- a/src/Persistence.Tests/PaYaml/Serialization/NamedObjectSequenceSerializationTests.cs
+++ b/src/Persistence.Tests/PaYaml/Serialization/NamedObjectSequenceSerializationTests.cs
@@ -13,13 +13,13 @@ public class NamedObjectSequenceSerializationTests : SerializationTestBase
protected override void ConfigureYamlDotNetDeserializer(DeserializerBuilder builder, PaYamlSerializationContext context)
{
base.ConfigureYamlDotNetDeserializer(builder, context);
- builder.WithTypeConverter(new NamedObjectYamlConverter(context));
+ builder.WithTypeConverter(new NamedObjectYamlConverter());
}
protected override void ConfigureYamlDotNetSerializer(SerializerBuilder builder, PaYamlSerializationContext context)
{
base.ConfigureYamlDotNetSerializer(builder, context);
- builder.WithTypeConverter(new NamedObjectYamlConverter(context));
+ builder.WithTypeConverter(new NamedObjectYamlConverter());
}
[TestMethod]
@@ -75,7 +75,7 @@ public void ReadYamlMappingSetsNamedObjectStart()
var testObject = DeserializeViaYamlDotNet>(yaml);
testObject.ShouldNotBeNull();
testObject.TheSequence.ShouldNotBeNull();
- testObject.TheSequence.Names.Should().Equal(new[] { "n1", "n3", "n2" }, "ordering of a sequence is by code order");
+ testObject.TheSequence.Names.Should().Equal(["n1", "n3", "n2"], "ordering of a sequence is by code order");
testObject.TheSequence.GetNamedObject("n1").Should()
.HaveValueEqual("v1")
.And.HaveStartEqual(2, 5);
@@ -92,7 +92,7 @@ public void SerializeAsPropertyBeingNullOrEmpty()
{
SerializeViaYamlDotNet(new TestOM { TheSequence = null })
.Should().Be("{}" + DefaultOptions.NewLine);
- SerializeViaYamlDotNet(new TestOM { TheSequence = new() })
+ SerializeViaYamlDotNet(new TestOM { TheSequence = [] })
.Should().Be("{}" + DefaultOptions.NewLine);
}
diff --git a/src/Persistence.Tests/PaYaml/Serialization/SerializationTestBase.cs b/src/Persistence.Tests/PaYaml/Serialization/SerializationTestBase.cs
index 3f23f310..e074229a 100644
--- a/src/Persistence.Tests/PaYaml/Serialization/SerializationTestBase.cs
+++ b/src/Persistence.Tests/PaYaml/Serialization/SerializationTestBase.cs
@@ -21,7 +21,6 @@ protected string SerializeViaYamlDotNet(T? testObject, PaYamlSerializerOption
using var serializationContext = new PaYamlSerializationContext(options);
var builder = new SerializerBuilder();
ConfigureYamlDotNetSerializer(builder, serializationContext);
- serializationContext.ValueSerializer = builder.BuildValueSerializer();
var serializer = builder.Build();
return serializer.Serialize(testObject);
@@ -40,11 +39,9 @@ protected virtual void ConfigureYamlDotNetSerializer(SerializerBuilder builder,
using var serializationContext = new PaYamlSerializationContext(options);
var builder = new DeserializerBuilder();
ConfigureYamlDotNetDeserializer(builder, serializationContext);
- serializationContext.ValueDeserializer = builder.BuildValueDeserializer();
var deserializer = builder.Build();
var value = deserializer.Deserialize(yaml);
- serializationContext.OnDeserialization();
return value;
}
diff --git a/src/Persistence/PaYaml/Models/NamedObjectMapping.cs b/src/Persistence/PaYaml/Models/NamedObjectMapping.cs
index b0ba4a99..0d65a4de 100644
--- a/src/Persistence/PaYaml/Models/NamedObjectMapping.cs
+++ b/src/Persistence/PaYaml/Models/NamedObjectMapping.cs
@@ -1,10 +1,6 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
-using Microsoft.PowerPlatform.PowerApps.Persistence.PaYaml.Serialization;
-using YamlDotNet.Core;
-using YamlDotNet.Serialization;
-
namespace Microsoft.PowerPlatform.PowerApps.Persistence.PaYaml.Models;
///
@@ -42,14 +38,4 @@ protected override NamedObject CreateNamedObject(string name, TValue val
return new NamedObject(name, value);
}
-
- protected override NamedObject ReadNamedObjectFromMappingEntryEvents(IParser parser, ObjectDeserializer nestedObjectDeserializer)
- {
- return NamedObjectYamlConverter.ReadNameAndValueEventsCore(parser, nestedObjectDeserializer);
- }
-
- protected override void WriteNamedObjectToMappingEntryEvents(IEmitter emitter, NamedObject namedObject, ObjectSerializer nestedObjectSerializer)
- {
- NamedObjectYamlConverter.WriteNameAndValueEventsCore(emitter, namedObject, nestedObjectSerializer);
- }
}
diff --git a/src/Persistence/PaYaml/Models/NamedObjectMappingBase.cs b/src/Persistence/PaYaml/Models/NamedObjectMappingBase.cs
index 3f5db8da..e167942d 100644
--- a/src/Persistence/PaYaml/Models/NamedObjectMappingBase.cs
+++ b/src/Persistence/PaYaml/Models/NamedObjectMappingBase.cs
@@ -3,17 +3,13 @@
using System.Collections;
using System.Diagnostics.CodeAnalysis;
-using Microsoft.PowerPlatform.PowerApps.Persistence.PaYaml.Serialization;
-using YamlDotNet.Core;
-using YamlDotNet.Core.Events;
-using YamlDotNet.Serialization;
namespace Microsoft.PowerPlatform.PowerApps.Persistence.PaYaml.Models;
///
/// Base implementation for an yaml mapping.
///
-public abstract class NamedObjectMappingBase : INamedObjectCollection, IYamlConvertible
+public abstract class NamedObjectMappingBase : INamedObjectCollection
where TName : notnull
where TValue : notnull
where TNamedObject : INamedObject
@@ -157,59 +153,4 @@ public bool TryGetValue(TName name, [MaybeNullWhen(false)] out TValue value)
return false;
}
}
-
- #region IYamlConvertible
-
- private void Read(IParser parser, Type expectedType, ObjectDeserializer nestedObjectDeserializer)
- {
- Debug.Assert(expectedType.IsAssignableTo(typeof(NamedObjectMappingBase)));
-
- if (parser.TryConsumeNull())
- {
- // REVIEW: We may not want to support null scalars when reading named object mappings.
- // This will require us to use a custom converter to be able to have access to return a null value.
- // For now, we think all uses would be benign currently to just treat null inputs as an empty collection:
- return;
- }
-
- parser.Consume();
- while (!parser.TryConsume(out _))
- {
- var itemStartEvent = parser.Current!;
- var namedObject = ReadNamedObjectFromMappingEntryEvents(parser, nestedObjectDeserializer);
-
- if (!TryAdd(namedObject))
- {
- var existingNamedObject = GetNamedObject(namedObject.Name);
- throw new YamlException(itemStartEvent.Start, itemStartEvent.End, $"Duplicate name '{namedObject.Name}' used at {itemStartEvent}. First use is located at {existingNamedObject.Start}.");
- }
- }
- }
-
- private void Write(IEmitter emitter, ObjectSerializer nestedObjectSerializer)
- {
- emitter.Emit(new MappingStart(AnchorName.Empty, TagName.Empty, isImplicit: true, MappingStyle.Block));
- foreach (var namedObject in InnerCollection.Values)
- {
- WriteNamedObjectToMappingEntryEvents(emitter, namedObject, nestedObjectSerializer);
- }
-
- emitter.Emit(new MappingEnd());
- }
-
- protected abstract TNamedObject ReadNamedObjectFromMappingEntryEvents(IParser parser, ObjectDeserializer nestedObjectDeserializer);
-
- protected abstract void WriteNamedObjectToMappingEntryEvents(IEmitter emitter, TNamedObject namedObject, ObjectSerializer nestedObjectSerializer);
-
- void IYamlConvertible.Read(IParser parser, Type expectedType, ObjectDeserializer nestedObjectDeserializer)
- {
- Read(parser, expectedType, nestedObjectDeserializer);
- }
-
- void IYamlConvertible.Write(IEmitter emitter, ObjectSerializer nestedObjectSerializer)
- {
- Write(emitter, nestedObjectSerializer);
- }
-
- #endregion
}
diff --git a/src/Persistence/PaYaml/Models/PaYamlLocation.cs b/src/Persistence/PaYaml/Models/PaYamlLocation.cs
index d703f8a5..1956bb16 100644
--- a/src/Persistence/PaYaml/Models/PaYamlLocation.cs
+++ b/src/Persistence/PaYaml/Models/PaYamlLocation.cs
@@ -5,7 +5,7 @@
namespace Microsoft.PowerPlatform.PowerApps.Persistence.PaYaml.Models;
-public record PaYamlLocation(int Line, int Column)
+public record PaYamlLocation(long Line, long Column)
{
internal static PaYamlLocation? FromMark(Mark mark)
{
diff --git a/src/Persistence/PaYaml/Serialization/NamedObjectMappingYamlConverter.cs b/src/Persistence/PaYaml/Serialization/NamedObjectMappingYamlConverter.cs
new file mode 100644
index 00000000..6c6d1759
--- /dev/null
+++ b/src/Persistence/PaYaml/Serialization/NamedObjectMappingYamlConverter.cs
@@ -0,0 +1,43 @@
+// Copyright (c) Microsoft Corporation.
+// Licensed under the MIT License.
+
+using System.Collections.Concurrent;
+using Microsoft.PowerPlatform.PowerApps.Persistence.PaYaml.Models;
+using YamlDotNet.Core;
+using YamlDotNet.Serialization;
+
+namespace Microsoft.PowerPlatform.PowerApps.Persistence.PaYaml.Serialization;
+
+///
+/// Converts any to and from YAML.
+/// The converter is non-generic so a single registration handles all closed TValue types.
+///
+internal sealed class NamedObjectMappingYamlConverter : IYamlTypeConverter
+{
+ private static readonly ConcurrentDictionary ConverterCache = new();
+
+ public bool Accepts(Type type)
+ {
+ return type.IsGenericType && type.GetGenericTypeDefinition() == typeof(NamedObjectMapping<>);
+ }
+
+ public object? ReadYaml(IParser parser, Type type, ObjectDeserializer rootDeserializer)
+ {
+ return GetConverter(type).ReadYaml(parser, type, rootDeserializer);
+ }
+
+ public void WriteYaml(IEmitter emitter, object? value, Type type, ObjectSerializer serializer)
+ {
+ GetConverter(type).WriteYaml(emitter, value, type, serializer);
+ }
+
+ private static IYamlTypeConverter GetConverter(Type closedMappingType)
+ {
+ return ConverterCache.GetOrAdd(closedMappingType, static t =>
+ {
+ var valueType = t.GetGenericArguments()[0];
+ var converterType = typeof(NamedObjectMappingYamlConverter<>).MakeGenericType(valueType);
+ return (IYamlTypeConverter)Activator.CreateInstance(converterType)!;
+ });
+ }
+}
diff --git a/src/Persistence/PaYaml/Serialization/NamedObjectMappingYamlConverterOfT.cs b/src/Persistence/PaYaml/Serialization/NamedObjectMappingYamlConverterOfT.cs
new file mode 100644
index 00000000..792f88e7
--- /dev/null
+++ b/src/Persistence/PaYaml/Serialization/NamedObjectMappingYamlConverterOfT.cs
@@ -0,0 +1,62 @@
+// Copyright (c) Microsoft Corporation.
+// Licensed under the MIT License.
+
+using Microsoft.PowerPlatform.PowerApps.Persistence.PaYaml.Models;
+using YamlDotNet.Core;
+using YamlDotNet.Core.Events;
+using YamlDotNet.Serialization;
+
+namespace Microsoft.PowerPlatform.PowerApps.Persistence.PaYaml.Serialization;
+
+///
+/// Strongly-typed converter for .
+/// Typically discovered and dispatched to by the non-generic
+/// , but can also be registered directly.
+///
+internal sealed class NamedObjectMappingYamlConverter : YamlConverter>
+ where TValue : notnull
+{
+ public override NamedObjectMapping ReadYaml(IParser parser, Type typeToConvert, ObjectDeserializer rootDeserializer)
+ {
+ var mapping = new NamedObjectMapping();
+
+ if (parser.TryConsumeNull())
+ {
+ // REVIEW: We may not want to support null scalars when reading named object mappings.
+ // For now, treat null inputs as an empty collection (matches prior IYamlConvertible behavior).
+ return mapping;
+ }
+
+ parser.Consume();
+ while (!parser.TryConsume(out _))
+ {
+ var itemStartEvent = parser.Current!;
+ var namedObject = NamedObjectYamlConverter.ReadNameAndValueEventsCore(parser, rootDeserializer);
+
+ if (!mapping.TryAdd(namedObject))
+ {
+ var existingNamedObject = mapping.GetNamedObject(namedObject.Name);
+ throw new YamlException(itemStartEvent.Start, itemStartEvent.End, $"Duplicate name '{namedObject.Name}' used at {itemStartEvent}. First use is located at {existingNamedObject.Start}.");
+ }
+ }
+
+ return mapping;
+ }
+
+ public override void WriteYaml(IEmitter emitter, NamedObjectMapping? value, Type typeToConvert, ObjectSerializer serializer)
+ {
+ if (value is null)
+ {
+ emitter.EmitNull();
+ return;
+ }
+
+ emitter.Emit(new MappingStart(AnchorName.Empty, TagName.Empty, isImplicit: true, MappingStyle.Block));
+ foreach (var namedObject in value)
+ {
+ NamedObjectYamlConverter.WriteNameAndValueEventsCore(emitter, namedObject, serializer);
+ }
+
+ emitter.Emit(new MappingEnd());
+ }
+}
diff --git a/src/Persistence/PaYaml/Serialization/NamedObjectYamlConverter.cs b/src/Persistence/PaYaml/Serialization/NamedObjectYamlConverter.cs
index 9d960103..f9f9677d 100644
--- a/src/Persistence/PaYaml/Serialization/NamedObjectYamlConverter.cs
+++ b/src/Persistence/PaYaml/Serialization/NamedObjectYamlConverter.cs
@@ -1,70 +1,43 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
-using YamlDotNet.Core;
-using YamlDotNet.Core.Events;
+using System.Collections.Concurrent;
using Microsoft.PowerPlatform.PowerApps.Persistence.PaYaml.Models;
+using YamlDotNet.Core;
using YamlDotNet.Serialization;
namespace Microsoft.PowerPlatform.PowerApps.Persistence.PaYaml.Serialization;
-internal class NamedObjectYamlConverter : YamlConverter>
- where TValue : notnull
+///
+/// Converts any to and from YAML.
+/// The converter is non-generic so a single registration handles all closed TValue types.
+///
+internal sealed class NamedObjectYamlConverter : IYamlTypeConverter
{
- public NamedObjectYamlConverter(PaYamlSerializationContext context)
- : base(context)
- {
- }
+ private static readonly ConcurrentDictionary ConverterCache = new();
- internal static NamedObject ReadNameAndValueEventsCore(IParser parser, ObjectDeserializer nestedObjectDeserializer)
+ public bool Accepts(Type type)
{
- _ = parser.Current ?? throw new InvalidOperationException("The parser has not been started or has nothing to read.");
- _ = nestedObjectDeserializer ?? throw new ArgumentNullException(nameof(nestedObjectDeserializer));
-
- // The default representation is expected to represent a single-item mapping
- var start = parser.Current.Start;
- var name = (string?)nestedObjectDeserializer(typeof(string)) ?? throw new YamlException(start, parser.Current.End, $"Named object key cannot be null.");
- var value = (TValue?)nestedObjectDeserializer(typeof(TValue)) ?? throw new YamlException(start, parser.Current.End, $"Named object value cannot be null.");
-
- return new NamedObject(name, value) { Start = PaYamlLocation.FromMark(start) };
+ return type.IsGenericType && type.GetGenericTypeDefinition() == typeof(NamedObject<>);
}
- internal static void WriteNameAndValueEventsCore(IEmitter emitter, NamedObject namedObject, ObjectSerializer nestedObjectSerializer)
+ public object? ReadYaml(IParser parser, Type type, ObjectDeserializer rootDeserializer)
{
- _ = emitter ?? throw new ArgumentNullException(nameof(emitter));
- _ = namedObject ?? throw new ArgumentNullException(nameof(namedObject));
- _ = nestedObjectSerializer ?? throw new ArgumentNullException(nameof(nestedObjectSerializer));
-
- // Only write the events for the mapping key/value.
- nestedObjectSerializer(namedObject.Name, typeof(string));
- nestedObjectSerializer(namedObject.Value, typeof(TValue));
+ return GetConverter(type).ReadYaml(parser, type, rootDeserializer);
}
- public override NamedObject ReadYaml(IParser parser, Type typeToConvert)
+ public void WriteYaml(IEmitter emitter, object? value, Type type, ObjectSerializer serializer)
{
- // The default representation is expected to represent a single-item mapping
- var mappingStart = parser.Consume();
- var namedObject = ReadNameAndValueEventsCore(parser, SerializationContext.CreateObjectDeserializer(parser));
-
- // There shouldn't be any more keys in the mapping
- parser.Consume();
-
- return namedObject;
+ GetConverter(type).WriteYaml(emitter, value, type, serializer);
}
- public override void WriteYaml(IEmitter emitter, NamedObject? value, Type typeToConvert)
+ private static IYamlTypeConverter GetConverter(Type closedNamedObjectType)
{
- if (value is null)
+ return ConverterCache.GetOrAdd(closedNamedObjectType, static t =>
{
- emitter.EmitNull();
- return;
- }
-
- // The default representation is expected to represent a single-item mapping
- emitter.Emit(new MappingStart(AnchorName.Empty, TagName.Empty, isImplicit: true, MappingStyle.Block));
-
- WriteNameAndValueEventsCore(emitter, value, SerializationContext.CreateObjectSerializer(emitter));
-
- emitter.Emit(new MappingEnd());
+ var valueType = t.GetGenericArguments()[0];
+ var converterType = typeof(NamedObjectYamlConverter<>).MakeGenericType(valueType);
+ return (IYamlTypeConverter)Activator.CreateInstance(converterType)!;
+ });
}
}
diff --git a/src/Persistence/PaYaml/Serialization/NamedObjectYamlConverterOfT.cs b/src/Persistence/PaYaml/Serialization/NamedObjectYamlConverterOfT.cs
new file mode 100644
index 00000000..c145bb3f
--- /dev/null
+++ b/src/Persistence/PaYaml/Serialization/NamedObjectYamlConverterOfT.cs
@@ -0,0 +1,65 @@
+// Copyright (c) Microsoft Corporation.
+// Licensed under the MIT License.
+
+using YamlDotNet.Core;
+using YamlDotNet.Core.Events;
+using Microsoft.PowerPlatform.PowerApps.Persistence.PaYaml.Models;
+using YamlDotNet.Serialization;
+
+namespace Microsoft.PowerPlatform.PowerApps.Persistence.PaYaml.Serialization;
+
+internal class NamedObjectYamlConverter : YamlConverter>
+ where TValue : notnull
+{
+ internal static NamedObject ReadNameAndValueEventsCore(IParser parser, ObjectDeserializer rootDeserializer)
+ {
+ _ = parser.Current ?? throw new InvalidOperationException("The parser has not been started or has nothing to read.");
+ _ = rootDeserializer ?? throw new ArgumentNullException(nameof(rootDeserializer));
+
+ // The default representation is expected to represent a single-item mapping
+ var start = parser.Current.Start;
+ var name = (string?)rootDeserializer(typeof(string)) ?? throw new YamlException(start, parser.Current.End, $"Named object key cannot be null.");
+ var value = (TValue?)rootDeserializer(typeof(TValue)) ?? throw new YamlException(start, parser.Current.End, $"Named object value cannot be null.");
+
+ return new NamedObject(name, value) { Start = PaYamlLocation.FromMark(start) };
+ }
+
+ internal static void WriteNameAndValueEventsCore(IEmitter emitter, NamedObject namedObject, ObjectSerializer serializer)
+ {
+ _ = emitter ?? throw new ArgumentNullException(nameof(emitter));
+ _ = namedObject ?? throw new ArgumentNullException(nameof(namedObject));
+ _ = serializer ?? throw new ArgumentNullException(nameof(serializer));
+
+ // Only write the events for the mapping key/value.
+ serializer(namedObject.Name, typeof(string));
+ serializer(namedObject.Value, typeof(TValue));
+ }
+
+ public override NamedObject ReadYaml(IParser parser, Type typeToConvert, ObjectDeserializer rootDeserializer)
+ {
+ // The default representation is expected to represent a single-item mapping
+ var mappingStart = parser.Consume();
+ var namedObject = ReadNameAndValueEventsCore(parser, rootDeserializer);
+
+ // There shouldn't be any more keys in the mapping
+ parser.Consume();
+
+ return namedObject;
+ }
+
+ public override void WriteYaml(IEmitter emitter, NamedObject? value, Type typeToConvert, ObjectSerializer serializer)
+ {
+ if (value is null)
+ {
+ emitter.EmitNull();
+ return;
+ }
+
+ // The default representation is expected to represent a single-item mapping
+ emitter.Emit(new MappingStart(AnchorName.Empty, TagName.Empty, isImplicit: true, MappingStyle.Block));
+
+ WriteNameAndValueEventsCore(emitter, value, serializer);
+
+ emitter.Emit(new MappingEnd());
+ }
+}
diff --git a/src/Persistence/PaYaml/Serialization/PFxExpressionYamlConverter.cs b/src/Persistence/PaYaml/Serialization/PFxExpressionYamlConverter.cs
index 515f39d1..47c46373 100644
--- a/src/Persistence/PaYaml/Serialization/PFxExpressionYamlConverter.cs
+++ b/src/Persistence/PaYaml/Serialization/PFxExpressionYamlConverter.cs
@@ -8,23 +8,18 @@
namespace Microsoft.PowerPlatform.PowerApps.Persistence.PaYaml.Serialization;
-internal class PFxExpressionYamlConverter : IYamlTypeConverter
+internal class PFxExpressionYamlConverter(PFxExpressionYamlFormattingOptions formattingOptions) : IYamlTypeConverter
{
private static readonly char[] LineTerminators = ['\r', '\n', '\x85', '\x2028', '\x2029'];
- private readonly PFxExpressionYamlFormattingOptions _formattingOptions;
-
- public PFxExpressionYamlConverter(PFxExpressionYamlFormattingOptions formattingOptions)
- {
- _formattingOptions = formattingOptions ?? throw new ArgumentNullException(nameof(formattingOptions));
- }
+ private readonly PFxExpressionYamlFormattingOptions _formattingOptions = formattingOptions;
public bool Accepts(Type type)
{
return type == typeof(PFxExpressionYaml);
}
- public object? ReadYaml(IParser parser, Type type)
+ public object? ReadYaml(IParser parser, Type type, ObjectDeserializer _)
{
if (parser.TryConsumeNull())
return null;
@@ -41,7 +36,7 @@ public bool Accepts(Type type)
throw new YamlException(scalar.Start, scalar.End, $"Power Fx expressions must start with '{PFxExpressionYamlFormattingOptions.ScalarPrefix}'.");
}
- public void WriteYaml(IEmitter emitter, object? value, Type type)
+ public void WriteYaml(IEmitter emitter, object? value, Type type, ObjectSerializer _)
{
var expression = (PFxExpressionYaml?)value;
if (expression is null)
diff --git a/src/Persistence/PaYaml/Serialization/PaYamlSerializationContext.cs b/src/Persistence/PaYaml/Serialization/PaYamlSerializationContext.cs
index 1e2db7ef..e1d0ac6b 100644
--- a/src/Persistence/PaYaml/Serialization/PaYamlSerializationContext.cs
+++ b/src/Persistence/PaYaml/Serialization/PaYamlSerializationContext.cs
@@ -1,46 +1,24 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
-using Microsoft.PowerPlatform.PowerApps.Persistence.PaYaml.Models.SchemaV3;
-using YamlDotNet.Core;
using YamlDotNet.Serialization;
using YamlDotNet.Serialization.NamingConventions;
-using YamlDotNet.Serialization.Utilities;
namespace Microsoft.PowerPlatform.PowerApps.Persistence.PaYaml.Serialization;
-public class PaYamlSerializationContext(PaYamlSerializerOptions options) : IDisposable
+public class PaYamlSerializationContext : IDisposable
{
- private readonly SerializerState _serializerState = new();
private bool _isDisposed;
- ///
- /// The options used when creating this context.
- ///
- public PaYamlSerializerOptions Options { get; } = options ?? throw new ArgumentNullException(nameof(options));
-
- internal IValueSerializer? ValueSerializer { get; set; }
-
- internal IValueDeserializer? ValueDeserializer { get; set; }
-
- public ObjectDeserializer CreateObjectDeserializer(IParser parser)
- {
- var valueDeserializer = ValueDeserializer ?? throw new InvalidOperationException($"{nameof(ValueDeserializer)} is not set.");
-
- return (t) => valueDeserializer.DeserializeValue(parser, t, _serializerState, valueDeserializer);
- }
-
- public ObjectSerializer CreateObjectSerializer(IEmitter emitter)
+ public PaYamlSerializationContext(PaYamlSerializerOptions options)
{
- var valueSerializer = ValueSerializer ?? throw new InvalidOperationException($"{nameof(ValueSerializer)} is not set.");
-
- return (v, t) => valueSerializer.SerializeValue(emitter, v, t);
+ Options = options ?? throw new ArgumentNullException(nameof(options));
}
- internal void OnDeserialization()
- {
- _serializerState.OnDeserialization();
- }
+ ///
+ /// The options used when creating this context.
+ ///
+ public PaYamlSerializerOptions Options { get; }
internal void ApplyToDeserializerBuilder(DeserializerBuilder builder)
{
@@ -48,6 +26,7 @@ internal void ApplyToDeserializerBuilder(DeserializerBuilder builder)
.WithDuplicateKeyChecking()
.IgnoreFields()
;
+
AddTypeConverters(builder);
Options.AdditionalDeserializerConfiguration?.Invoke(builder);
}
@@ -76,8 +55,8 @@ private void AddTypeConverters(BuilderSkeleton builder)
where TBuilder : BuilderSkeleton
{
builder.WithTypeConverter(new PFxExpressionYamlConverter(Options.PFxExpressionYamlFormatting));
- builder.WithTypeConverter(new NamedObjectYamlConverter(this));
- builder.WithTypeConverter(new NamedObjectYamlConverter(this));
+ builder.WithTypeConverter(new NamedObjectYamlConverter());
+ builder.WithTypeConverter(new NamedObjectMappingYamlConverter());
}
///
@@ -93,11 +72,6 @@ protected virtual void Dispose(bool disposing)
{
if (!_isDisposed)
{
- if (disposing)
- {
- _serializerState.Dispose();
- }
-
_isDisposed = true;
}
}
diff --git a/src/Persistence/PaYaml/Serialization/PaYamlSerializer.cs b/src/Persistence/PaYaml/Serialization/PaYamlSerializer.cs
index b9f7aea0..c02873c8 100644
--- a/src/Persistence/PaYaml/Serialization/PaYamlSerializer.cs
+++ b/src/Persistence/PaYaml/Serialization/PaYamlSerializer.cs
@@ -52,7 +52,6 @@ private static void WriteTextWriter(TextWriter writer, in TValue? value,
using var context = new PaYamlSerializationContext(options);
var builder = new SerializerBuilder();
context.ApplyToSerializerBuilder(builder);
- context.ValueSerializer = builder.BuildValueSerializer();
var serializer = builder.Build();
try
@@ -104,16 +103,12 @@ private static void WriteTextWriter(TextWriter writer, in TValue? value,
using var context = new PaYamlSerializationContext(options);
var builder = new DeserializerBuilder();
context.ApplyToDeserializerBuilder(builder);
- context.ValueDeserializer = builder.BuildValueDeserializer();
var serializer = builder.Build();
try
{
var value = serializer.Deserialize(reader);
- // Must call OnDeserialization to invoke any post-deserialization callbacks on the deserialized object tree.
- context.OnDeserialization();
-
return value;
}
catch (YamlException ex)
diff --git a/src/Persistence/PaYaml/Serialization/YamlConverterOfT.cs b/src/Persistence/PaYaml/Serialization/YamlConverterOfT.cs
index 142ce941..a933c7bc 100644
--- a/src/Persistence/PaYaml/Serialization/YamlConverterOfT.cs
+++ b/src/Persistence/PaYaml/Serialization/YamlConverterOfT.cs
@@ -9,15 +9,8 @@ namespace Microsoft.PowerPlatform.PowerApps.Persistence.PaYaml.Serialization;
public abstract class YamlConverter : IYamlTypeConverter
{
- protected YamlConverter(PaYamlSerializationContext context)
- {
- SerializationContext = context ?? throw new ArgumentNullException(nameof(context));
- }
-
public Type Type { get; } = typeof(T);
- public PaYamlSerializationContext SerializationContext { get; }
-
///
/// The default implementation returns true when equals typeof().
///
@@ -26,19 +19,19 @@ public virtual bool Accepts(Type type)
return type == Type;
}
- public abstract T ReadYaml(IParser parser, Type typeToConvert);
+ public abstract T ReadYaml(IParser parser, Type typeToConvert, ObjectDeserializer rootDeserializer);
- public abstract void WriteYaml(IEmitter emitter, T? value, Type typeToConvert);
+ public abstract void WriteYaml(IEmitter emitter, T? value, Type typeToConvert, ObjectSerializer serializer);
- object? IYamlTypeConverter.ReadYaml(IParser parser, Type typeToConvert)
+ object? IYamlTypeConverter.ReadYaml(IParser parser, Type typeToConvert, ObjectDeserializer rootDeserializer)
{
- return ReadYaml(parser, typeToConvert);
+ return ReadYaml(parser, typeToConvert, rootDeserializer);
}
- void IYamlTypeConverter.WriteYaml(IEmitter emitter, object? value, Type typeToConvert)
+ void IYamlTypeConverter.WriteYaml(IEmitter emitter, object? value, Type typeToConvert, ObjectSerializer serializer)
{
var valueOfT = YamlSerialization.UnboxOnWrite(value);
- WriteYaml(emitter, valueOfT, typeToConvert);
+ WriteYaml(emitter, valueOfT, typeToConvert, serializer);
}
}
diff --git a/src/Persistence/PaYaml/Serialization/YamlDotNetExtensions.cs b/src/Persistence/PaYaml/Serialization/YamlDotNetExtensions.cs
index 27d9ab0c..5c544ff7 100644
--- a/src/Persistence/PaYaml/Serialization/YamlDotNetExtensions.cs
+++ b/src/Persistence/PaYaml/Serialization/YamlDotNetExtensions.cs
@@ -23,7 +23,7 @@ public static bool TryConsumeNull(this IParser parser)
// NullNodeDeserializer.Deserialize is undocumented, but here's a good summary of what it does:
// Attempts to consume the current node event iif it represents a YAML null value. Otherwise, the current event stays.
// Returns true if the current node was a null value and was consumed; otherwise, false.
- return _nullNodeDeserializer.Deserialize(parser, _typeofObject, null!, out _);
+ return _nullNodeDeserializer.Deserialize(parser, _typeofObject, null!, out _, null!);
}
public static void EmitNull(this IEmitter emitter)
diff --git a/src/Persistence/PersistenceErrorCode.cs b/src/Persistence/PersistenceErrorCode.cs
index 84984c7f..61220186 100644
--- a/src/Persistence/PersistenceErrorCode.cs
+++ b/src/Persistence/PersistenceErrorCode.cs
@@ -1,6 +1,7 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
+using Microsoft.PowerPlatform.PowerApps.Persistence.PaYaml.Serialization;
using System.ComponentModel;
namespace Microsoft.PowerPlatform.PowerApps.Persistence;
diff --git a/src/Persistence/packages.lock.json b/src/Persistence/packages.lock.json
index 98a61da4..1b6927cf 100644
--- a/src/Persistence/packages.lock.json
+++ b/src/Persistence/packages.lock.json
@@ -82,9 +82,9 @@
},
"YamlDotNet": {
"type": "Direct",
- "requested": "[15.1.6, )",
- "resolved": "15.1.6",
- "contentHash": "T/cQEK/KHK96Q8kytJ4iUGDXg1/fj2Qtk6rCQeIlHYU1zTeyGVHW0QNZgREQyxZpygGMDMmrXNWt0sj5TsQnjA=="
+ "requested": "[16.1.3, )",
+ "resolved": "16.1.3",
+ "contentHash": "gtHGiDvU9VTtWte8f0thIM38cL1oowOjStKpeAEKKfA+Rc4AvekJzqFDZiiPcc4kw00ZiwR4OTJS56L16q98DQ=="
},
"Microsoft.Bcl.AsyncInterfaces": {
"type": "Transitive",
@@ -144,9 +144,9 @@
},
"YamlDotNet": {
"type": "Direct",
- "requested": "[15.1.6, )",
- "resolved": "15.1.6",
- "contentHash": "T/cQEK/KHK96Q8kytJ4iUGDXg1/fj2Qtk6rCQeIlHYU1zTeyGVHW0QNZgREQyxZpygGMDMmrXNWt0sj5TsQnjA=="
+ "requested": "[16.1.3, )",
+ "resolved": "16.1.3",
+ "contentHash": "gtHGiDvU9VTtWte8f0thIM38cL1oowOjStKpeAEKKfA+Rc4AvekJzqFDZiiPcc4kw00ZiwR4OTJS56L16q98DQ=="
}
},
"net8.0": {
@@ -167,9 +167,9 @@
},
"YamlDotNet": {
"type": "Direct",
- "requested": "[15.1.6, )",
- "resolved": "15.1.6",
- "contentHash": "T/cQEK/KHK96Q8kytJ4iUGDXg1/fj2Qtk6rCQeIlHYU1zTeyGVHW0QNZgREQyxZpygGMDMmrXNWt0sj5TsQnjA=="
+ "requested": "[16.1.3, )",
+ "resolved": "16.1.3",
+ "contentHash": "gtHGiDvU9VTtWte8f0thIM38cL1oowOjStKpeAEKKfA+Rc4AvekJzqFDZiiPcc4kw00ZiwR4OTJS56L16q98DQ=="
}
}
}