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
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@
using System.IO;
using System.Threading;
using System.Threading.Tasks;
using System.ComponentModel;

#if NET5_0_OR_GREATER
using System.Diagnostics.CodeAnalysis;
#endif
Expand All @@ -20,9 +22,21 @@ public static partial class KiotaJsonSerializer
/// </summary>
/// <typeparam name="T">Type of the object to serialize</typeparam>
/// <param name="value">The object to serialize.</param>
/// <param name="serializeOnlyChangedValues">By default a backing store is used, and you'll only get changed properties</param>
/// <returns>The serialized representation as a stream.</returns>
public static Stream SerializeAsStream<T>(T value) where T : IParsable
=> KiotaSerializer.SerializeAsStream(_jsonContentType, value);
public static Stream SerializeAsStream<T>(T value, bool serializeOnlyChangedValues = true) where T : IParsable
=> KiotaSerializer.SerializeAsStream(_jsonContentType, value, serializeOnlyChangedValues);

/// <summary>
/// Serializes the specified object as a string based JSON stream.
/// </summary>
/// <typeparam name="T">The type of the value to serialize.</typeparam>
/// <param name="value">The object to serialize.</param>
/// <param name="serializeOnlyChangedValues">By default a backing store is used, and you'll only get changed properties</param>
/// <returns>A <see cref="Stream"/> containing the serialized JSON data.</returns>

public static Stream SerializeAsJsonStream<T>(this T value, bool serializeOnlyChangedValues = true) where T : IParsable
svrooij marked this conversation as resolved.
Show resolved Hide resolved
=> SerializeAsStream(value, serializeOnlyChangedValues);

/// <summary>
/// Serializes the given object into a string based on the content type.
Expand All @@ -41,17 +55,40 @@ 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);
[EditorBrowsable(EditorBrowsableState.Never)]
public static Task<string> SerializeAsStringAsync<T>(T value, CancellationToken cancellationToken) where T : IParsable => SerializeAsStringAsync(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="value">The object to serialize.</param>
/// <param name="serializeOnlyChangedValues">By default a backing store is used, and you'll only get 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>(T value, bool serializeOnlyChangedValues = true, CancellationToken cancellationToken = default) where T : IParsable
=> KiotaSerializer.SerializeAsStringAsync(_jsonContentType, value, serializeOnlyChangedValues, 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="value">The object to serialize.</param>
/// <param name="serializeOnlyChangedValues">By default a backing store is used, and you'll only get changed properties</param>
/// <returns>The serialized representation as a stream.</returns>
public static Stream SerializeAsStream<T>(IEnumerable<T> value) where T : IParsable
=> KiotaSerializer.SerializeAsStream(_jsonContentType, value);
public static Stream SerializeAsStream<T>(IEnumerable<T> value, bool serializeOnlyChangedValues = true) where T : IParsable
=> KiotaSerializer.SerializeAsStream(_jsonContentType, value, serializeOnlyChangedValues);

/// <summary>
/// Serializes the specified object as a string based JSON stream.
/// </summary>
/// <typeparam name="T">The type of the object to serialize.</typeparam>
/// <param name="value">The enumerable of objects to serialize.</param>
/// <param name="serializeOnlyChangedValues">By default a backing store is used, and you'll only get changed properties</param>
/// <returns>A <see cref="Stream"/> containing the serialized JSON data.</returns>

public static Stream SerializeAsJsonStream<T>(this IEnumerable<T> value, bool serializeOnlyChangedValues = true) where T : IParsable
=> SerializeAsStream(value, serializeOnlyChangedValues);

/// <summary>
/// Serializes the given object into a string based on the content type.
Expand All @@ -69,7 +106,17 @@ 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);
[EditorBrowsable(EditorBrowsableState.Never)]
public static Task<string> SerializeAsStringAsync<T>(IEnumerable<T> value, CancellationToken cancellationToken) where T : IParsable => SerializeAsStringAsync(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="value">The object to serialize.</param>
/// <param name="serializeOnlyChangedValues">By default a backing store is used, and you'll only get 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>(IEnumerable<T> value, bool serializeOnlyChangedValues = true, CancellationToken cancellationToken = default) where T : IParsable
=> KiotaSerializer.SerializeAsStringAsync(_jsonContentType, value, serializeOnlyChangedValues, cancellationToken);

}
59 changes: 45 additions & 14 deletions src/abstractions/serialization/KiotaSerializer.Serialization.cs
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,10 @@
using System.IO;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.Kiota.Abstractions.Store;
using System.ComponentModel;


#if NET5_0_OR_GREATER
using System.Diagnostics.CodeAnalysis;
#endif
Expand All @@ -25,12 +29,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 +59,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 +81,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 +109,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 +143,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 @@ -51,5 +51,45 @@ public ISerializationWriter GetSerializationWriter(string contentType)
throw new InvalidOperationException($"Content type {cleanedContentType} does not have a factory registered to be parsed");
}

/// <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)
{
if(serializeOnlyChangedValues)
return GetSerializationWriter(contentType);

var factory = GetSerializationWriterFactory(contentType);
if(factory is Store.BackingStoreSerializationWriterProxyFactory backingStoreFactory)
return backingStoreFactory.GetSerializationWriter(contentType, false);

return factory.GetSerializationWriter(contentType);
}

/// <summary>
/// Get the relevant <see cref="ISerializationWriterFactory"/> instance for the given content type
/// </summary>
/// <param name="contentType">The content type in use</param>
/// <returns></returns>
/// <exception cref="ArgumentNullException"></exception>
/// <exception cref="InvalidOperationException"></exception>
private ISerializationWriterFactory GetSerializationWriterFactory(string contentType)
svrooij marked this conversation as resolved.
Show resolved Hide resolved
{
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;

var cleanedContentType = ParseNodeFactoryRegistry.contentTypeVendorCleanupRegex.Replace(vendorSpecificContentType, string.Empty);
if(ContentTypeAssociatedFactories.TryGetValue(cleanedContentType, out var factory))
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 @@ -15,7 +15,11 @@ public class SerializationWriterProxyFactory : ISerializationWriterFactory
/// The valid content type for the <see cref="SerializationWriterProxyFactory"/>
/// </summary>
public string ValidContentType { get { return _concrete.ValidContentType; } }
private readonly ISerializationWriterFactory _concrete;

/// <summary>
/// The concrete factory to wrap.
/// </summary>
protected readonly ISerializationWriterFactory _concrete;
svrooij marked this conversation as resolved.
Show resolved Hide resolved
private readonly Action<IParsable> _onBefore;
private readonly Action<IParsable> _onAfter;
private readonly Action<IParsable, ISerializationWriter> _onStartSerialization;
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 _concrete.GetSerializationWriter(contentType);
}
}
}