Skip to content

Commit

Permalink
Merge pull request #311 from svrooij/main
Browse files Browse the repository at this point in the history
Improving serialization
  • Loading branch information
andrueastman authored Aug 20, 2024
2 parents c198508 + e229345 commit a60e99c
Show file tree
Hide file tree
Showing 9 changed files with 262 additions and 33 deletions.
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

Check warning on line 26 in src/abstractions/serialization/KiotaJsonSerializer.Serialization.cs

View workflow job for this annotation

GitHub Actions / Build

All 'SerializeAsStream' method overloads should be adjacent. (https://rules.sonarsource.com/csharp/RSPEC-4136)
=> 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

Check warning on line 37 in src/abstractions/serialization/KiotaJsonSerializer.Serialization.cs

View workflow job for this annotation

GitHub Actions / Build

All 'SerializeAsString' method overloads should be adjacent. (https://rules.sonarsource.com/csharp/RSPEC-4136)
=> 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

Check warning on line 47 in src/abstractions/serialization/KiotaJsonSerializer.Serialization.cs

View workflow job for this annotation

GitHub Actions / Build

All 'SerializeAsStringAsync' method overloads should be adjacent. (https://rules.sonarsource.com/csharp/RSPEC-4136)
=> 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)
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");
}

}
}
16 changes: 10 additions & 6 deletions src/abstractions/serialization/SerializationWriterProxyFactory.cs
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

0 comments on commit a60e99c

Please sign in to comment.