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

Added Non-Generic Solution to KiotaDeserialization #436

Merged
merged 14 commits into from
Oct 28, 2024
Merged
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
// ------------------------------------------------------------------------------
// Copyright (c) Microsoft Corporation. All Rights Reserved. Licensed under the MIT License. See License in the project root for license information.
// ------------------------------------------------------------------------------


#if NET5_0_OR_GREATER

using System.Threading.Tasks;
using System.Threading;
using System.IO;
using System;
using System.Collections.Concurrent;
using System.Diagnostics.CodeAnalysis;
using System.Collections.Generic;
using System.Linq;

#pragma warning disable IL3050
DerGuru marked this conversation as resolved.
Show resolved Hide resolved

namespace Microsoft.Kiota.Abstractions.Serialization;

public static partial class KiotaJsonSerializer
{
private static bool IsIParsable(this Type? type) => type?.IsAssignableTo(typeof(IParsable)) ?? false;
private abstract class KiotaJsonDeserializer
DerGuru marked this conversation as resolved.
Show resolved Hide resolved
{
private static readonly ConcurrentDictionary<Type, KiotaJsonDeserializer> _deserializers = new ConcurrentDictionary<Type, KiotaJsonDeserializer>();

public static KiotaJsonDeserializer Create(Type type) => type.IsIParsable() ? _deserializers.GetOrAdd(type, CreateInternal) : throw new ArgumentException("The given Type is not of IParsable", nameof(type));

private static KiotaJsonDeserializer CreateInternal([DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicMethods)] Type targetType)
=> (KiotaJsonDeserializer)Activator.CreateInstance(typeof(TypedKiotaJsonDeserializer<>).MakeGenericType(targetType))!;


internal abstract Task<IParsable?> DeserializeAsync(Stream stream, CancellationToken cancellationToken = default);
internal abstract Task<IParsable?> DeserializeAsync(string serializedRepresentation, CancellationToken cancellationToken = default);
internal abstract Task<IEnumerable<IParsable>> DeserializeCollectionAsync(Stream stream, CancellationToken cancellationToken = default);
internal abstract Task<IEnumerable<IParsable>> DeserializeCollectionAsync(string serializedRepresentation, CancellationToken cancellationToken = default);

private class TypedKiotaJsonDeserializer<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicMethods)] T> : KiotaJsonDeserializer where T : IParsable
{
internal override async Task<IParsable?> DeserializeAsync(Stream stream, CancellationToken cancellationToken = default) => await KiotaJsonSerializer.DeserializeAsync<T>(stream, cancellationToken);
internal override async Task<IParsable?> DeserializeAsync(string serializedRepresentation, CancellationToken cancellationToken = default) => await KiotaJsonSerializer.DeserializeAsync<T>(serializedRepresentation, cancellationToken);
internal override async Task<IEnumerable<IParsable>> DeserializeCollectionAsync(Stream stream, CancellationToken cancellationToken = default) => (await KiotaJsonSerializer.DeserializeCollectionAsync<T>(stream, cancellationToken)).OfType<IParsable>();
internal override async Task<IEnumerable<IParsable>> DeserializeCollectionAsync(string serializedRepresentation, CancellationToken cancellationToken = default) => (await KiotaJsonSerializer.DeserializeCollectionAsync<T>(serializedRepresentation, cancellationToken)).OfType<IParsable>();
}
}

/// <summary>
/// Deserializes the given string into an object.
/// </summary>
/// <param name="type">The target type to deserialize</param>
/// <param name="serializedRepresentation">The serialized representation of the object.</param>
/// <param name="cancellationToken">The cancellation token for the task</param>
public static Task<IParsable?> DeserializeAsync([DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicMethods)] Type type, string serializedRepresentation, CancellationToken cancellationToken = default)
=> KiotaJsonDeserializer.Create(type).DeserializeAsync(serializedRepresentation, cancellationToken);

/// <summary>
/// Deserializes the given stream into an object.
/// </summary>
/// <param name="type">The target type to deserialize</param>
/// <param name="stream">The stream to deserialize.</param>
/// <param name="cancellationToken">The cancellation token for the task</param>
public static Task<IParsable?> DeserializeAsync([DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicMethods)] Type type, Stream stream, CancellationToken cancellationToken = default)
=> KiotaJsonDeserializer.Create(type).DeserializeAsync(stream, cancellationToken);

/// <summary>
/// Deserializes the given stream into a collection of objects based on the content type.
/// </summary>
/// <param name="type">The target type to deserialize</param>
/// <param name="stream">The stream to deserialize.</param>
/// <param name="cancellationToken">The cancellation token for the task</param>
public static Task<IEnumerable<IParsable>> DeserializeCollectionAsync(Type type, Stream stream, CancellationToken cancellationToken = default)
=> KiotaJsonDeserializer.Create(type).DeserializeCollectionAsync(stream, cancellationToken);

/// <summary>
/// Deserializes the given stream into a collection of objects based on the content type.
/// </summary>
/// <param name="type">The target type to deserialize</param>
/// <param name="serializedRepresentation">The serialized representation of the object.</param>
/// <param name="cancellationToken">The cancellation token for the task</param>
public static Task<IEnumerable<IParsable>> DeserializeCollectionAsync(Type type, string serializedRepresentation, CancellationToken cancellationToken = default)
=> KiotaJsonDeserializer.Create(type).DeserializeCollectionAsync(serializedRepresentation, cancellationToken);


}

#pragma warning restore IL3050
#endif
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
// ------------------------------------------------------------------------------
// Copyright (c) Microsoft Corporation. All Rights Reserved. Licensed under the MIT License. See License in the project root for license information.
// ------------------------------------------------------------------------------


#if NET5_0_OR_GREATER

using System.Threading.Tasks;
using System.Threading;
using System.IO;
using System;
using System.Collections.Concurrent;
using System.Diagnostics.CodeAnalysis;
using System.Collections.Generic;
using System.Linq;

#pragma warning disable IL3050

namespace Microsoft.Kiota.Abstractions.Serialization;

public static partial class KiotaSerializer
{
private static bool IsIParsable(this Type? type) => type?.IsAssignableTo(typeof(IParsable)) ?? false;
private abstract class KiotaDeserializer
{
private static readonly ConcurrentDictionary<Type, KiotaDeserializer> _deserializers = new ConcurrentDictionary<Type, KiotaDeserializer>();

public static KiotaDeserializer Create(Type type) => type.IsIParsable() ? _deserializers.GetOrAdd(type, CreateInternal) : throw new ArgumentException("The given Type is not of IParsable", nameof(type));

private static KiotaDeserializer CreateInternal([DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicMethods)] Type targetType)
=> (KiotaDeserializer)Activator.CreateInstance(typeof(TypedKiotaDeserializer<>).MakeGenericType(targetType))!;


internal abstract Task<IParsable?> DeserializeAsync(string contentType, Stream stream, CancellationToken cancellationToken = default);
internal abstract Task<IParsable?> DeserializeAsync(string contentType, string serializedRepresentation, CancellationToken cancellationToken = default);
internal abstract Task<IEnumerable<IParsable>> DeserializeCollectionAsync(string contentType, Stream stream, CancellationToken cancellationToken = default);
internal abstract Task<IEnumerable<IParsable>> DeserializeCollectionAsync(string contentType, string serializedRepresentation, CancellationToken cancellationToken = default);

private class TypedKiotaDeserializer<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicMethods)] T> : KiotaDeserializer where T : IParsable
{
internal override async Task<IParsable?> DeserializeAsync(string contentType, Stream stream, CancellationToken cancellationToken = default) => await KiotaSerializer.DeserializeAsync<T>(contentType, stream, cancellationToken);
internal override async Task<IParsable?> DeserializeAsync(string contentType, string serializedRepresentation, CancellationToken cancellationToken = default) => await KiotaSerializer.DeserializeAsync<T>(contentType, serializedRepresentation, cancellationToken);
internal override async Task<IEnumerable<IParsable>> DeserializeCollectionAsync(string contentType, Stream stream, CancellationToken cancellationToken = default) => (await KiotaSerializer.DeserializeCollectionAsync<T>(contentType, stream, cancellationToken)).OfType<IParsable>();
internal override async Task<IEnumerable<IParsable>> DeserializeCollectionAsync(string contentType, string serializedRepresentation, CancellationToken cancellationToken = default) => (await KiotaSerializer.DeserializeCollectionAsync<T>(contentType, serializedRepresentation, cancellationToken)).OfType<IParsable>();
}
}

/// <summary>
/// Deserializes the given string into a collection of objects based on the content type.
/// </summary>
/// <param name="type">The target type to deserialize</param>
/// <param name="contentType">The content type of the stream.</param>
/// <param name="serializedRepresentation">The serialized representation of the object.</param>
/// <param name="cancellationToken">The cancellation token for the task</param>
/// <returns></returns>
public static Task<IParsable?> DeserializeAsync([DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicMethods)] Type type, string contentType, string serializedRepresentation, CancellationToken cancellationToken = default)
=> KiotaDeserializer.Create(type).DeserializeAsync(contentType, serializedRepresentation, cancellationToken);

/// <summary>
/// Deserializes the given stream into a collection of objects based on the content type.
/// </summary>
/// <param name="type">The target type to deserialize</param>
/// <param name="contentType">The content type of the stream.</param>
/// <param name="stream">The stream to deserialize.</param>
/// <param name="cancellationToken">The cancellation token for the task</param>
/// <returns></returns>
public static Task<IParsable?> DeserializeAsync([DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicMethods)] Type type, string contentType, Stream stream, CancellationToken cancellationToken = default)
=> KiotaDeserializer.Create(type).DeserializeAsync(contentType, stream, cancellationToken);

/// <summary>
/// Deserializes the given stream into a collection of objects based on the content type.
/// </summary>
/// <param name="type">The target type to deserialize</param>
/// <param name="contentType">The content type of the stream.</param>
/// <param name="stream">The stream to deserialize.</param>
/// <param name="cancellationToken">The cancellation token for the task</param>
public static Task<IEnumerable<IParsable>> DeserializeCollectionAsync(Type type, string contentType, Stream stream, CancellationToken cancellationToken = default)
=> KiotaDeserializer.Create(type).DeserializeCollectionAsync(contentType, stream, cancellationToken);

/// <summary>
/// Deserializes the given stream into a collection of objects based on the content type.
/// </summary>
/// <param name="type">The target type to deserialize</param>
/// <param name="contentType">The content type of the stream.</param>
/// <param name="serializedRepresentation">The serialized representation of the object.</param>
/// <param name="cancellationToken">The cancellation token for the task</param>
public static Task<IEnumerable<IParsable>> DeserializeCollectionAsync(Type type, string contentType, string serializedRepresentation, CancellationToken cancellationToken = default)
=> KiotaDeserializer.Create(type).DeserializeCollectionAsync(contentType, serializedRepresentation, cancellationToken);

}

#pragma warning restore IL3050
#endif
68 changes: 68 additions & 0 deletions tests/abstractions/Serialization/DeserializationHelpersTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -171,4 +171,72 @@ public async Task DeserializesCollectionOfObjectAsync()
Assert.NotNull(result);
Assert.Single(result);
}

#if NET5_0_OR_GREATER
[Fact]
public async Task DeserializesObjectUntypedWithoutReflectionAsync()
{
var strValue = "{'id':'123'}";
var mockParseNode = new Mock<IParseNode>();
mockParseNode.Setup(x => x.GetObjectValue(It.IsAny<ParsableFactory<TestEntity>>())).Returns(new TestEntity()
{
Id = "123"
});
var mockJsonParseNodeFactory = new Mock<IAsyncParseNodeFactory>();
mockJsonParseNodeFactory.Setup(x => x.GetRootParseNodeAsync(It.IsAny<string>(), It.IsAny<Stream>(), It.IsAny<CancellationToken>())).Returns(Task.FromResult(mockParseNode.Object));
mockJsonParseNodeFactory.Setup(x => x.ValidContentType).Returns(_jsonContentType);
ParseNodeFactoryRegistry.DefaultInstance.ContentTypeAssociatedFactories[_jsonContentType] = mockJsonParseNodeFactory.Object;

var result = (TestEntity?) await KiotaSerializer.DeserializeAsync(typeof(TestEntity), _jsonContentType, strValue);

Assert.NotNull(result);
Assert.Equal("123", result.Id);
}

[Fact]
public async Task DeserializesObjectUntypedWithReflectionAsync()
{
var strValue = "{'id':'123'}";
var mockParseNode = new Mock<IParseNode>();
mockParseNode.Setup(x => x.GetObjectValue(It.IsAny<ParsableFactory<TestEntity>>())).Returns(new TestEntity()
{
Id = "123"
});
var mockJsonParseNodeFactory = new Mock<IAsyncParseNodeFactory>();
mockJsonParseNodeFactory.Setup(x => x.GetRootParseNodeAsync(It.IsAny<string>(), It.IsAny<Stream>(), It.IsAny<CancellationToken>())).Returns(Task.FromResult(mockParseNode.Object));
mockJsonParseNodeFactory.Setup(x => x.ValidContentType).Returns(_jsonContentType);
ParseNodeFactoryRegistry.DefaultInstance.ContentTypeAssociatedFactories[_jsonContentType] = mockJsonParseNodeFactory.Object;

var result = (TestEntity?) await KiotaSerializer.DeserializeAsync(typeof(TestEntity),_jsonContentType, strValue);

Assert.NotNull(result);
Assert.Equal("123", result.Id);
}

[Fact]
public async Task DeserializesCollectionOfObjectUntypedAsync()
{
var strValue = "{'id':'123'}";
var mockParseNode = new Mock<IParseNode>();
mockParseNode.Setup(x => x.GetCollectionOfObjectValues(It.IsAny<ParsableFactory<TestEntity>>())).Returns(new List<TestEntity> {
new TestEntity()
{
Id = "123"
}
});
var mockJsonParseNodeFactory = new Mock<IAsyncParseNodeFactory>();
mockJsonParseNodeFactory.Setup(x => x.GetRootParseNodeAsync(It.IsAny<string>(), It.IsAny<Stream>(), It.IsAny<CancellationToken>())).Returns(Task.FromResult(mockParseNode.Object));
mockJsonParseNodeFactory.Setup(x => x.ValidContentType).Returns(_jsonContentType);
ParseNodeFactoryRegistry.DefaultInstance.ContentTypeAssociatedFactories[_jsonContentType] = mockJsonParseNodeFactory.Object;

var result = await KiotaSerializer.DeserializeCollectionAsync(typeof(TestEntity), _jsonContentType, strValue);

Assert.NotNull(result);
Assert.Single(result);
var first = result.First() as TestEntity;
Assert.NotNull(first);
Assert.Equal("123", first.Id);
}
#endif

}
Loading