Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Improving serialization #311

Merged
merged 12 commits into from
Aug 20, 2024
6 changes: 6 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,12 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

## [Unreleased]

## [1.12.0] - 2024-08-20

### Changed

- Improved serialization helper methods to take boolean parameter to override the BackingStore functionality. [#310](https://github.com/microsoft/kiota-dotnet/issues/310)

## [1.11.3] - 2024-08-16

### Changed
Expand Down
2 changes: 1 addition & 1 deletion Directory.Build.props
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
<Project>
<!-- Common default project properties for ALL projects-->
<PropertyGroup>
<VersionPrefix>1.11.3</VersionPrefix>
<VersionPrefix>1.12.0</VersionPrefix>
<VersionSuffix></VersionSuffix>
<!-- This is overidden in test projects by setting to true-->
<IsTestProject>false</IsTestProject>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,11 @@

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.IO;
using System.Threading;
using System.Threading.Tasks;

#if NET5_0_OR_GREATER
using System.Diagnostics.CodeAnalysis;
#endif
Expand All @@ -22,15 +24,16 @@ public static partial class KiotaJsonSerializer
/// <param name="value">The object to serialize.</param>
/// <returns>The serialized representation as a stream.</returns>
public static Stream SerializeAsStream<T>(T value) where T : IParsable
=> KiotaSerializer.SerializeAsStream(_jsonContentType, value);
=> KiotaSerializer.SerializeAsStream(_jsonContentType, value);

/// <summary>
/// Serializes the given object into a string based on the content type.
/// </summary>
/// <typeparam name="T">Type of the object to serialize</typeparam>
/// <param name="value">The object to serialize.</param>
/// <returns>The serialized representation as a string.</returns>
[Obsolete("This method is obsolete, use the async method instead")]
[Obsolete("This method is obsolete, use SerializeAsStringAsync instead")]
[EditorBrowsable(EditorBrowsableState.Never)]
public static string SerializeAsString<T>(T value) where T : IParsable
=> KiotaSerializer.SerializeAsString(_jsonContentType, value);

Expand All @@ -41,8 +44,8 @@ public static string SerializeAsString<T>(T value) where T : IParsable
/// <param name="value">The object to serialize.</param>
/// <param name="cancellationToken">The token to monitor for cancellation requests.</param>
/// <returns>The serialized representation as a string.</returns>
public static Task<string> SerializeAsStringAsync<T>(T value, CancellationToken cancellationToken = default) where T : IParsable
=> KiotaSerializer.SerializeAsStringAsync(_jsonContentType, value, cancellationToken);
public static Task<string> SerializeAsStringAsync<T>(T value, CancellationToken cancellationToken) where T : IParsable
=> KiotaSerializer.SerializeAsStringAsync(_jsonContentType, value, true, cancellationToken);

/// <summary>
/// Serializes the given object into a string based on the content type.
Expand All @@ -59,7 +62,8 @@ public static Stream SerializeAsStream<T>(IEnumerable<T> value) where T : IParsa
/// <typeparam name="T">Type of the object to serialize</typeparam>
/// <param name="value">The object to serialize.</param>
/// <returns>The serialized representation as a string.</returns>
[Obsolete("This method is obsolete, use the async method instead")]
[Obsolete("This method is obsolete, use SerializeAsStringAsync instead")]
[EditorBrowsable(EditorBrowsableState.Never)]
public static string SerializeAsString<T>(IEnumerable<T> value) where T : IParsable
=> KiotaSerializer.SerializeAsString(_jsonContentType, value);
/// <summary>
Expand All @@ -69,7 +73,6 @@ public static string SerializeAsString<T>(IEnumerable<T> value) where T : IParsa
/// <param name="value">The object to serialize.</param>
/// <param name="cancellationToken">The token to monitor for cancellation requests.</param>
/// <returns>The serialized representation as a string.</returns>
public static Task<string> SerializeAsStringAsync<T>(IEnumerable<T> value, CancellationToken cancellationToken = default) where T : IParsable
=> KiotaSerializer.SerializeAsStringAsync(_jsonContentType, value, cancellationToken);

}
public static Task<string> SerializeAsStringAsync<T>(IEnumerable<T> value, CancellationToken cancellationToken) where T : IParsable =>
KiotaSerializer.SerializeAsStringAsync(_jsonContentType, value, true, cancellationToken);
}
58 changes: 44 additions & 14 deletions src/abstractions/serialization/KiotaSerializer.Serialization.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,12 @@

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.IO;
using System.Threading;
using System.Threading.Tasks;


#if NET5_0_OR_GREATER
using System.Diagnostics.CodeAnalysis;
#endif
Expand All @@ -25,12 +28,14 @@ public static partial class KiotaSerializer
/// <typeparam name="T">Type of the object to serialize</typeparam>
/// <param name="contentType">Content type to serialize the object to </param>
/// <param name="value">The object to serialize.</param>
/// <param name="serializeOnlyChangedValues">By default, you'll only get the changed properties.</param>
/// <returns>The serialized representation as a stream.</returns>
public static Stream SerializeAsStream<T>(string contentType, T value) where T : IParsable
public static Stream SerializeAsStream<T>(string contentType, T value, bool serializeOnlyChangedValues = true) where T : IParsable
{
using var writer = GetSerializationWriter(contentType, value);
writer.WriteObjectValue(string.Empty, value);
return writer.GetSerializedContent();
using var writer = GetSerializationWriter(contentType, value, serializeOnlyChangedValues);
writer.WriteObjectValue(null, value);
var stream = writer.GetSerializedContent();
return stream;
}
/// <summary>
/// Serializes the given object into a string based on the content type.
Expand All @@ -53,9 +58,20 @@ public static string SerializeAsString<T>(string contentType, T value) where T :
/// <param name="value">The object to serialize.</param>
/// <param name="cancellationToken">The token to monitor for cancellation requests.</param>
/// <returns>The serialized representation as a string.</returns>
public static Task<string> SerializeAsStringAsync<T>(string contentType, T value, CancellationToken cancellationToken = default) where T : IParsable
[EditorBrowsable(EditorBrowsableState.Never)]
public static Task<string> SerializeAsStringAsync<T>(string contentType, T value, CancellationToken cancellationToken) where T : IParsable => SerializeAsStringAsync(contentType, value, true, cancellationToken);
/// <summary>
/// Serializes the given object into a string based on the content type.
/// </summary>
/// <typeparam name="T">Type of the object to serialize</typeparam>
/// <param name="contentType">Content type to serialize the object to </param>
/// <param name="value">The object to serialize.</param>
/// <param name="serializeOnlyChangedValues">By default, you'll only get the changed properties.</param>
/// <param name="cancellationToken">The token to monitor for cancellation requests.</param>
/// <returns>The serialized representation as a string.</returns>
public static Task<string> SerializeAsStringAsync<T>(string contentType, T value, bool serializeOnlyChangedValues = true, CancellationToken cancellationToken = default) where T : IParsable
{
using var stream = SerializeAsStream(contentType, value);
using var stream = SerializeAsStream(contentType, value, serializeOnlyChangedValues);
return GetStringFromStreamAsync(stream, cancellationToken);
}
/// <summary>
Expand All @@ -64,12 +80,14 @@ public static Task<string> SerializeAsStringAsync<T>(string contentType, T value
/// <typeparam name="T">Type of the object to serialize</typeparam>
/// <param name="contentType">Content type to serialize the object to </param>
/// <param name="value">The object to serialize.</param>
/// <param name="serializeOnlyChangedValues">By default, you'll only get the changed properties.</param>
/// <returns>The serialized representation as a stream.</returns>
public static Stream SerializeAsStream<T>(string contentType, IEnumerable<T> value) where T : IParsable
public static Stream SerializeAsStream<T>(string contentType, IEnumerable<T> value, bool serializeOnlyChangedValues = true) where T : IParsable
{
using var writer = GetSerializationWriter(contentType, value);
writer.WriteCollectionOfObjectValues(string.Empty, value);
return writer.GetSerializedContent();
using var writer = GetSerializationWriter(contentType, value, serializeOnlyChangedValues);
writer.WriteCollectionOfObjectValues(null, value);
var stream = writer.GetSerializedContent();
return stream;
}
/// <summary>
/// Serializes the given object into a string based on the content type.
Expand All @@ -90,13 +108,25 @@ public static string SerializeAsString<T>(string contentType, IEnumerable<T> val
/// <typeparam name="T">Type of the object to serialize</typeparam>
/// <param name="contentType">Content type to serialize the object to </param>
/// <param name="value">The object to serialize.</param>
/// <param name="serializeOnlyChangedValues"></param>
/// <param name="cancellationToken">The token to monitor for cancellation requests.</param>
/// <returns>The serialized representation as a string.</returns>
public static Task<string> SerializeAsStringAsync<T>(string contentType, IEnumerable<T> value, CancellationToken cancellationToken = default) where T : IParsable
public static Task<string> SerializeAsStringAsync<T>(string contentType, IEnumerable<T> value, bool serializeOnlyChangedValues = true, CancellationToken cancellationToken = default) where T : IParsable
{
using var stream = SerializeAsStream(contentType, value);
using var stream = SerializeAsStream(contentType, value, serializeOnlyChangedValues);
return GetStringFromStreamAsync(stream, cancellationToken);
}
/// <summary>
/// Serializes the given object into a string based on the content type.
/// </summary>
/// <typeparam name="T">Type of the object to serialize</typeparam>
/// <param name="contentType">Content type to serialize the object to </param>
/// <param name="value">The object to serialize.</param>
/// <param name="cancellationToken">The token to monitor for cancellation requests.</param>
/// <returns>The serialized representation as a string.</returns>
[EditorBrowsable(EditorBrowsableState.Never)]
public static Task<string> SerializeAsStringAsync<T>(string contentType, IEnumerable<T> value, CancellationToken cancellationToken) where T : IParsable => SerializeAsStringAsync(contentType, value, true, cancellationToken);

[Obsolete("This method is obsolete, use the async method instead")]
private static string GetStringFromStream(Stream stream)
{
Expand All @@ -112,10 +142,10 @@ private static async Task<string> GetStringFromStreamAsync(Stream stream, Cancel
return await reader.ReadToEndAsync().ConfigureAwait(false);
#endif
}
private static ISerializationWriter GetSerializationWriter(string contentType, object value)
private static ISerializationWriter GetSerializationWriter(string contentType, object value, bool serializeOnlyChangedValues = true)
{
if(string.IsNullOrEmpty(contentType)) throw new ArgumentNullException(nameof(contentType));
if(value == null) throw new ArgumentNullException(nameof(value));
return SerializationWriterFactoryRegistry.DefaultInstance.GetSerializationWriter(contentType);
return SerializationWriterFactoryRegistry.DefaultInstance.GetSerializationWriter(contentType, serializeOnlyChangedValues);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -26,30 +26,63 @@ public string ValidContentType
/// Default singleton instance of the registry to be used when registering new factories that should be available by default.
/// </summary>
public static readonly SerializationWriterFactoryRegistry DefaultInstance = new();

/// <summary>
/// List of factories that are registered by content type.
/// </summary>
public ConcurrentDictionary<string, ISerializationWriterFactory> ContentTypeAssociatedFactories { get; set; } = new();

/// <summary>
/// Get the relevant <see cref="ISerializationWriter"/> instance for the given content type
/// </summary>
/// <param name="contentType">The content type in use</param>
/// <returns>A <see cref="ISerializationWriter"/> instance to parse the content</returns>
public ISerializationWriter GetSerializationWriter(string contentType)
=> GetSerializationWriter(contentType, true);

/// <summary>
/// Get the relevant <see cref="ISerializationWriter"/> instance for the given content type
/// </summary>
/// <param name="contentType">The content type in use</param>
/// <param name="serializeOnlyChangedValues">If <see langword="true"/> will only return changed values, otherwise will return the full object </param>
/// <returns>A <see cref="ISerializationWriter"/> instance to parse the content</returns>
public ISerializationWriter GetSerializationWriter(string contentType, bool serializeOnlyChangedValues)
{
var factory = GetSerializationWriterFactory(contentType, out string actualContentType);
if(!serializeOnlyChangedValues && factory is Store.BackingStoreSerializationWriterProxyFactory backingStoreFactory)
svrooij marked this conversation as resolved.
Show resolved Hide resolved
return backingStoreFactory.GetSerializationWriter(actualContentType, false);

return factory.GetSerializationWriter(actualContentType);
}

/// <summary>
/// Get the relevant <see cref="ISerializationWriterFactory"/> instance for the given content type
/// </summary>
/// <param name="contentType">The content type in use</param>
/// <param name="actualContentType">The content type where a writer factory is found for</param>
/// <returns></returns>
/// <exception cref="ArgumentNullException"></exception>
/// <exception cref="InvalidOperationException"></exception>
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))
return vendorFactory.GetSerializationWriter(vendorSpecificContentType);
{
actualContentType = vendorSpecificContentType;
return vendorFactory;
}

var cleanedContentType = ParseNodeFactoryRegistry.contentTypeVendorCleanupRegex.Replace(vendorSpecificContentType, string.Empty);
if(ContentTypeAssociatedFactories.TryGetValue(cleanedContentType, out var factory))
return factory.GetSerializationWriter(cleanedContentType);
{
actualContentType = cleanedContentType;
return factory;
}

throw new InvalidOperationException($"Content type {cleanedContentType} does not have a factory registered to be parsed");
}

}
}
Original file line number Diff line number Diff line change
Expand Up @@ -14,24 +14,28 @@ public class SerializationWriterProxyFactory : ISerializationWriterFactory
/// <summary>
/// The valid content type for the <see cref="SerializationWriterProxyFactory"/>
/// </summary>
public string ValidContentType { get { return _concrete.ValidContentType; } }
private readonly ISerializationWriterFactory _concrete;
public string ValidContentType { get { return ProxiedSerializationWriterFactory.ValidContentType; } }

/// <summary>
/// The factory that is being proxied.
/// </summary>
protected readonly ISerializationWriterFactory ProxiedSerializationWriterFactory;
private readonly Action<IParsable> _onBefore;
private readonly Action<IParsable> _onAfter;
private readonly Action<IParsable, ISerializationWriter> _onStartSerialization;
/// <summary>
/// Creates a new proxy factory that wraps the specified concrete factory while composing the before and after callbacks.
/// </summary>
/// <param name="concrete">The concrete factory to wrap.</param>
/// <param name="factoryToWrap">The concrete factory to wrap.</param>
/// <param name="onBeforeSerialization">The callback to invoke before the serialization of any model object.</param>
/// <param name="onAfterSerialization">The callback to invoke after the serialization of any model object.</param>
/// <param name="onStartSerialization">The callback to invoke when serialization of the entire model has started.</param>
public SerializationWriterProxyFactory(ISerializationWriterFactory concrete,
public SerializationWriterProxyFactory(ISerializationWriterFactory factoryToWrap,
Action<IParsable> onBeforeSerialization,
Action<IParsable> onAfterSerialization,
Action<IParsable, ISerializationWriter> onStartSerialization)
{
_concrete = concrete ?? throw new ArgumentNullException(nameof(concrete));
ProxiedSerializationWriterFactory = factoryToWrap ?? throw new ArgumentNullException(nameof(factoryToWrap));
_onBefore = onBeforeSerialization;
_onAfter = onAfterSerialization;
_onStartSerialization = onStartSerialization;
Expand All @@ -43,7 +47,7 @@ public SerializationWriterProxyFactory(ISerializationWriterFactory concrete,
/// <returns>A new <see cref="ISerializationWriter" /> instance for the given content type.</returns>
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;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -37,5 +37,19 @@ public BackingStoreSerializationWriterProxyFactory(ISerializationWriterFactory c
}
})
{ }

/// <summary>
/// Get the serialization writer for the given content type.
/// </summary>
/// <param name="contentType">The content type for which a serialization writer should be created.</param>
/// <param name="serializeOnlyChangedValues">By default, a backing store is used, and you'll only get changed properties</param>
/// <returns></returns>
public ISerializationWriter GetSerializationWriter(string contentType, bool serializeOnlyChangedValues)
{
if(serializeOnlyChangedValues)
return base.GetSerializationWriter(contentType);

return ProxiedSerializationWriterFactory.GetSerializationWriter(contentType);
}
}
}
Loading
Loading