diff --git a/src/abstractions/serialization/KiotaJsonSerializer.Serialization.cs b/src/abstractions/serialization/KiotaJsonSerializer.Serialization.cs index ac5141cf..087e7e3e 100644 --- a/src/abstractions/serialization/KiotaJsonSerializer.Serialization.cs +++ b/src/abstractions/serialization/KiotaJsonSerializer.Serialization.cs @@ -17,6 +17,17 @@ namespace Microsoft.Kiota.Abstractions.Serialization; public static partial class KiotaJsonSerializer { + /// + /// Serializes the given object into a string based on the content type. + /// + /// Type of the object to serialize + /// The object to serialize. + /// The serialized representation as a stream. + [Obsolete("This method is obsolete, use the extension methods in Microsoft.Kiota.Serialization.Json.IParsableExtensions instead")] + [EditorBrowsable(EditorBrowsableState.Never)] + public static Stream SerializeAsStream(T value) where T : IParsable + => KiotaSerializer.SerializeAsStream(_jsonContentType, value); + /// /// Serializes the given object into a string based on the content type. /// @@ -72,6 +83,4 @@ public static string SerializeAsString(IEnumerable value) where T : IParsa [EditorBrowsable(EditorBrowsableState.Never)] public static Task SerializeAsStringAsync(IEnumerable value, CancellationToken cancellationToken) where T : IParsable => KiotaSerializer.SerializeAsStringAsync(_jsonContentType, value, true, cancellationToken); - - -} \ No newline at end of file +} diff --git a/src/abstractions/serialization/SerializationWriterFactoryRegistry.cs b/src/abstractions/serialization/SerializationWriterFactoryRegistry.cs index bc71d7fd..b4f65d1e 100644 --- a/src/abstractions/serialization/SerializationWriterFactoryRegistry.cs +++ b/src/abstractions/serialization/SerializationWriterFactoryRegistry.cs @@ -26,30 +26,19 @@ public string ValidContentType /// Default singleton instance of the registry to be used when registering new factories that should be available by default. /// public static readonly SerializationWriterFactoryRegistry DefaultInstance = new(); + /// /// List of factories that are registered by content type. /// public ConcurrentDictionary ContentTypeAssociatedFactories { get; set; } = new(); + /// /// Get the relevant instance for the given content type /// /// The content type in use /// A instance to parse the content public ISerializationWriter GetSerializationWriter(string contentType) - { - if(string.IsNullOrEmpty(contentType)) - throw new ArgumentNullException(nameof(contentType)); - - var vendorSpecificContentType = contentType.Split(";".ToCharArray(), StringSplitOptions.RemoveEmptyEntries)[0]; - if(ContentTypeAssociatedFactories.TryGetValue(vendorSpecificContentType, out var vendorFactory)) - return vendorFactory.GetSerializationWriter(vendorSpecificContentType); - - var cleanedContentType = ParseNodeFactoryRegistry.contentTypeVendorCleanupRegex.Replace(vendorSpecificContentType, string.Empty); - if(ContentTypeAssociatedFactories.TryGetValue(cleanedContentType, out var factory)) - return factory.GetSerializationWriter(cleanedContentType); - - throw new InvalidOperationException($"Content type {cleanedContentType} does not have a factory registered to be parsed"); - } + => GetSerializationWriter(contentType, true); /// /// Get the relevant instance for the given content type @@ -59,35 +48,39 @@ public ISerializationWriter GetSerializationWriter(string contentType) /// A instance to parse the content public ISerializationWriter GetSerializationWriter(string contentType, bool serializeOnlyChangedValues) { - if(serializeOnlyChangedValues) - return GetSerializationWriter(contentType); - - var factory = GetSerializationWriterFactory(contentType); - if(factory is Store.BackingStoreSerializationWriterProxyFactory backingStoreFactory) - return backingStoreFactory.GetSerializationWriter(contentType, false); + var factory = GetSerializationWriterFactory(contentType, out string actualContentType); + if(!serializeOnlyChangedValues && factory is Store.BackingStoreSerializationWriterProxyFactory backingStoreFactory) + return backingStoreFactory.GetSerializationWriter(actualContentType, false); - return factory.GetSerializationWriter(contentType); + return factory.GetSerializationWriter(actualContentType); } /// /// Get the relevant instance for the given content type /// /// The content type in use + /// The content type where a writer factory is found for /// /// /// - private ISerializationWriterFactory GetSerializationWriterFactory(string contentType) + private ISerializationWriterFactory GetSerializationWriterFactory(string contentType, out string actualContentType) { if(string.IsNullOrEmpty(contentType)) throw new ArgumentNullException(nameof(contentType)); var vendorSpecificContentType = contentType.Split(";".ToCharArray(), StringSplitOptions.RemoveEmptyEntries)[0]; if(ContentTypeAssociatedFactories.TryGetValue(vendorSpecificContentType, out var vendorFactory)) + { + actualContentType = vendorSpecificContentType; return vendorFactory; + } var cleanedContentType = ParseNodeFactoryRegistry.contentTypeVendorCleanupRegex.Replace(vendorSpecificContentType, string.Empty); if(ContentTypeAssociatedFactories.TryGetValue(cleanedContentType, out var factory)) + { + actualContentType = cleanedContentType; return factory; + } throw new InvalidOperationException($"Content type {cleanedContentType} does not have a factory registered to be parsed"); } diff --git a/src/abstractions/serialization/SerializationWriterProxyFactory.cs b/src/abstractions/serialization/SerializationWriterProxyFactory.cs index 0e518534..d5a59786 100644 --- a/src/abstractions/serialization/SerializationWriterProxyFactory.cs +++ b/src/abstractions/serialization/SerializationWriterProxyFactory.cs @@ -14,28 +14,28 @@ public class SerializationWriterProxyFactory : ISerializationWriterFactory /// /// The valid content type for the /// - public string ValidContentType { get { return _concrete.ValidContentType; } } + public string ValidContentType { get { return ProxiedSerializationWriterFactory.ValidContentType; } } /// - /// The concrete factory to wrap. + /// The factory that is being proxied. /// - protected readonly ISerializationWriterFactory _concrete; + protected readonly ISerializationWriterFactory ProxiedSerializationWriterFactory; private readonly Action _onBefore; private readonly Action _onAfter; private readonly Action _onStartSerialization; /// /// Creates a new proxy factory that wraps the specified concrete factory while composing the before and after callbacks. /// - /// The concrete factory to wrap. + /// The concrete factory to wrap. /// The callback to invoke before the serialization of any model object. /// The callback to invoke after the serialization of any model object. /// The callback to invoke when serialization of the entire model has started. - public SerializationWriterProxyFactory(ISerializationWriterFactory concrete, + public SerializationWriterProxyFactory(ISerializationWriterFactory factoryToWrap, Action onBeforeSerialization, Action onAfterSerialization, Action onStartSerialization) { - _concrete = concrete ?? throw new ArgumentNullException(nameof(concrete)); + ProxiedSerializationWriterFactory = factoryToWrap ?? throw new ArgumentNullException(nameof(factoryToWrap)); _onBefore = onBeforeSerialization; _onAfter = onAfterSerialization; _onStartSerialization = onStartSerialization; @@ -47,7 +47,7 @@ public SerializationWriterProxyFactory(ISerializationWriterFactory concrete, /// A new instance for the given content type. public ISerializationWriter GetSerializationWriter(string contentType) { - var writer = _concrete.GetSerializationWriter(contentType); + var writer = ProxiedSerializationWriterFactory.GetSerializationWriter(contentType); var originalBefore = writer.OnBeforeObjectSerialization; var originalAfter = writer.OnAfterObjectSerialization; var originalStart = writer.OnStartObjectSerialization; diff --git a/src/abstractions/store/BackingStoreSerializationWriterProxyFactory.cs b/src/abstractions/store/BackingStoreSerializationWriterProxyFactory.cs index 79ad1cc9..87915a7c 100644 --- a/src/abstractions/store/BackingStoreSerializationWriterProxyFactory.cs +++ b/src/abstractions/store/BackingStoreSerializationWriterProxyFactory.cs @@ -42,14 +42,14 @@ public BackingStoreSerializationWriterProxyFactory(ISerializationWriterFactory c /// Get the serialization writer for the given content type. /// /// The content type for which a serialization writer should be created. - /// By default a backing store is used, and you'll only get changed properties + /// By default, a backing store is used, and you'll only get changed properties /// public ISerializationWriter GetSerializationWriter(string contentType, bool serializeOnlyChangedValues) { if(serializeOnlyChangedValues) return base.GetSerializationWriter(contentType); - return _concrete.GetSerializationWriter(contentType); + return ProxiedSerializationWriterFactory.GetSerializationWriter(contentType); } } } diff --git a/tests/serialization/json/IParsableExtensionsTests.cs b/tests/serialization/json/IParsableExtensionsTests.cs new file mode 100644 index 00000000..71db6a41 --- /dev/null +++ b/tests/serialization/json/IParsableExtensionsTests.cs @@ -0,0 +1,99 @@ +using System.IO; +using Microsoft.Kiota.Abstractions.Serialization; +using Microsoft.Kiota.Abstractions.Store; +using Microsoft.Kiota.Serialization.Json.Tests.Mocks; +using Xunit; + +namespace Microsoft.Kiota.Serialization.Json.Tests +{ + public class IParsableExtensionsTests + { + private const string _jsonContentType = "application/json"; + private readonly SerializationWriterFactoryRegistry _serializationWriterFactoryRegistry; + + public IParsableExtensionsTests() + { + _serializationWriterFactoryRegistry = new SerializationWriterFactoryRegistry(); + _serializationWriterFactoryRegistry.ContentTypeAssociatedFactories.TryAdd(_jsonContentType, new BackingStoreSerializationWriterProxyFactory(new JsonSerializationWriterFactory())); + } + + [Theory] + [InlineData(null)] + [InlineData(true)] + [InlineData(false)] + public void GetSerializationWriter_RetunsJsonSerializationWriter(bool? serializeOnlyChangedValues) + { + // Arrange + + // Act + using var writer = serializeOnlyChangedValues.HasValue + ? _serializationWriterFactoryRegistry.GetSerializationWriter(_jsonContentType, serializeOnlyChangedValues.Value) + : _serializationWriterFactoryRegistry.GetSerializationWriter(_jsonContentType); + + // Assert + Assert.NotNull(writer); + Assert.IsType(writer); + } + + [Fact] + public void GetSerializationWriterSerializedChangedTrue_RetunsEmptyJson() + { + // Arrange + var testUser = new BackedTestEntity { Id = "1", Name = "testUser" }; + testUser.BackingStore.InitializationCompleted = true; + using var writer = _serializationWriterFactoryRegistry.GetSerializationWriter(_jsonContentType, true); + + // Act + writer.WriteObjectValue(null, testUser); + using var stream = writer.GetSerializedContent(); + var serializedContent = GetStringFromStream(stream); + + // Assert + Assert.NotNull(serializedContent); + Assert.Equal("{}", serializedContent); + } + + [Fact] + public void GetSerializationWriterSerializedChangedTrue_ChangedName_ReturnsJustName() + { + // Arrange + var testUser = new BackedTestEntity { Id = "1", Name = "testUser" }; + testUser.BackingStore.InitializationCompleted = true; + testUser.Name = "Stephan"; + using var writer = _serializationWriterFactoryRegistry.GetSerializationWriter(_jsonContentType, true); + + // Act + writer.WriteObjectValue(null, testUser); + using var stream = writer.GetSerializedContent(); + var serializedContent = GetStringFromStream(stream); + + // Assert + Assert.NotNull(serializedContent); + Assert.Equal("{\"name\":\"Stephan\"}", serializedContent); + } + + [Fact] + public void GetSerializationWriterSerializedChangedFalse_SerializesEntireObject() + { + // Arrange + var testUser = new BackedTestEntity { Id = "1", Name = "testUser" }; + testUser.BackingStore.InitializationCompleted = true; + using var writer = _serializationWriterFactoryRegistry.GetSerializationWriter(_jsonContentType, false); + + // Act + writer.WriteObjectValue(null, testUser); + using var stream = writer.GetSerializedContent(); + var serializedContent = GetStringFromStream(stream); + + // Assert + Assert.NotNull(serializedContent); + Assert.Equal("{\"id\":\"1\",\"name\":\"testUser\"}", serializedContent); + } + + private static string GetStringFromStream(Stream stream) + { + using var reader = new StreamReader(stream); + return reader.ReadToEnd(); + } + } +} diff --git a/tests/serialization/json/Mocks/BackedTestEntity.cs b/tests/serialization/json/Mocks/BackedTestEntity.cs new file mode 100644 index 00000000..6ff41b8b --- /dev/null +++ b/tests/serialization/json/Mocks/BackedTestEntity.cs @@ -0,0 +1,40 @@ +using System; +using System.Collections.Generic; +using Microsoft.Kiota.Abstractions.Serialization; +using Microsoft.Kiota.Abstractions.Store; + +namespace Microsoft.Kiota.Serialization.Json.Tests.Mocks +{ + public class BackedTestEntity : IParsable, IBackedModel + { + public BackedTestEntity() + { + BackingStore = new InMemoryBackingStore(); + } + + public IBackingStore BackingStore { get; private set; } + + public string Id + { + get { return BackingStore?.Get("id"); } + set { BackingStore?.Set("id", value); } + } + public string Name + { + get { return BackingStore?.Get("name"); } + set { BackingStore?.Set("name", value); } + } + + public IDictionary> GetFieldDeserializers() => + new Dictionary> { + { "id", n => { Id = n.GetStringValue(); } }, + { "name", n => { Name = n.GetStringValue(); } }, + }; + public void Serialize(ISerializationWriter writer) + { + _ = writer ?? throw new ArgumentNullException(nameof(writer)); + writer.WriteStringValue("id", Id); + writer.WriteStringValue("name", Name); + } + } +}