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
6 changes: 6 additions & 0 deletions Microsoft.Kiota.lutconfig
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
<LUTConfig Version="1.0">
<Repository />
<ParallelBuilds>true</ParallelBuilds>
<ParallelTestRuns>true</ParallelTestRuns>
<TestCaseTimeout>180000</TestCaseTimeout>
</LUTConfig>
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
// ------------------------------------------------------------------------------
// 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;

namespace Microsoft.Kiota.Abstractions.Serialization;
public static partial class KiotaJsonSerializer
{
#if NET8_0_OR_GREATER
#else
/// <summary>
/// Palceholder for .NET &lt; 8
/// </summary>
private class RequiresDynamicCodeAttribute : Attribute
DerGuru marked this conversation as resolved.
Show resolved Hide resolved
{
public RequiresDynamicCodeAttribute(string _) { }
}

#endif
private static bool IsIParsable(this Type type) => type.IsAssignableTo(typeof(IParsable));

private static class KiotaJsonDeserializationWrapperFactory
{
private static readonly ConcurrentDictionary<Type, IKiotaJsonDeserializationWrapper> _deserializers = new ConcurrentDictionary<Type, IKiotaJsonDeserializationWrapper>();

[RequiresDynamicCode("Activator creates an instance of a generic class with the Target Type as the generic type argument.")]
public static IKiotaJsonDeserializationWrapper Create(Type type) => type.IsIParsable() ? _deserializers.GetOrAdd(type, CreateInternal) : throw new ArgumentException("The given Type is not of IParsable", nameof(type));

[RequiresDynamicCode("Activator creates an instance of a generic class with the Target Type as the generic type argument.")]
private static IKiotaJsonDeserializationWrapper CreateInternal([DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicMethods)] Type targetType)
{
if(Activator.CreateInstance(typeof(KiotaJsonDeserializationWrapper<>).MakeGenericType(targetType)) is IKiotaJsonDeserializationWrapper deserializer)
return deserializer;
else
throw new InvalidOperationException($"Unable to create deserializer for type {targetType}");
}
}

private interface IKiotaJsonDeserializationWrapper
{
Task<IParsable?> DeserializeAsync(Stream stream, CancellationToken cancellationToken = default);
Task<IParsable?> DeserializeAsync(string serializedRepresentation, CancellationToken cancellationToken = default);
Task<IEnumerable<IParsable>> DeserializeCollectionAsync(Stream stream, CancellationToken cancellationToken = default);
Task<IEnumerable<IParsable>> DeserializeCollectionAsync(string serializedRepresentation, CancellationToken cancellationToken = default);
}

private class KiotaJsonDeserializationWrapper<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicMethods)] T> : IKiotaJsonDeserializationWrapper where T : IParsable
{
public async Task<IParsable?> DeserializeAsync(Stream stream, CancellationToken cancellationToken = default) => await KiotaJsonSerializer.DeserializeAsync<T>(stream, cancellationToken);
public async Task<IParsable?> DeserializeAsync(string serializedRepresentation, CancellationToken cancellationToken = default) => await KiotaJsonSerializer.DeserializeAsync<T>(serializedRepresentation, cancellationToken);
public async Task<IEnumerable<IParsable>> DeserializeCollectionAsync(Stream stream, CancellationToken cancellationToken = default) => (await KiotaJsonSerializer.DeserializeCollectionAsync<T>(stream, cancellationToken)).OfType<IParsable>();
public 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="targetType">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>
[RequiresDynamicCode("Activator creates an instance of a generic class with the Target Type as the generic type argument.")]
public static Task<IParsable?> DeserializeAsync([DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicMethods)] Type targetType, string serializedRepresentation, CancellationToken cancellationToken = default)
=> KiotaJsonDeserializationWrapperFactory.Create(targetType).DeserializeAsync(serializedRepresentation, cancellationToken);

/// <summary>
/// Deserializes the given stream into an object.
/// </summary>
/// <param name="targetType">The target type to deserialize</param>
/// <param name="stream">The stream to deserialize.</param>
/// <param name="cancellationToken">The cancellation token for the task</param>
[RequiresDynamicCode("Activator creates an instance of a generic class with the Target Type as the generic type argument.")]
public static Task<IParsable?> DeserializeAsync([DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicMethods)] Type targetType, Stream stream, CancellationToken cancellationToken = default)
=> KiotaJsonDeserializationWrapperFactory.Create(targetType).DeserializeAsync(stream, cancellationToken);

/// <summary>
/// Deserializes the given stream into a collection of objects based on the content type.
/// </summary>
/// <param name="targetType">The target type to deserialize</param>
/// <param name="stream">The stream to deserialize.</param>
/// <param name="cancellationToken">The cancellation token for the task</param>
[RequiresDynamicCode("Activator creates an instance of a generic class with the Target Type as the generic type argument.")]
public static Task<IEnumerable<IParsable>> DeserializeCollectionAsync(Type targetType, Stream stream, CancellationToken cancellationToken = default)
=> KiotaJsonDeserializationWrapperFactory.Create(targetType).DeserializeCollectionAsync(stream, cancellationToken);

/// <summary>
/// Deserializes the given stream into a collection of objects based on the content type.
/// </summary>
/// <param name="targetType">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>
[RequiresDynamicCode("Activator creates an instance of a generic class with the Target Type as the generic type argument.")]
public static Task<IEnumerable<IParsable>> DeserializeCollectionAsync(Type targetType, string serializedRepresentation, CancellationToken cancellationToken = default)
=> KiotaJsonDeserializationWrapperFactory.Create(targetType).DeserializeCollectionAsync(serializedRepresentation, cancellationToken);
}
DerGuru marked this conversation as resolved.
Show resolved Hide resolved
#endif
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
// ------------------------------------------------------------------------------
// 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;

namespace Microsoft.Kiota.Abstractions.Serialization;

public static partial class KiotaSerializer
{
#if NET8_0_OR_GREATER
#else
/// <summary>
/// Palceholder for .NET &lt; 8
/// </summary>
private class RequiresDynamicCodeAttribute : Attribute
{
public RequiresDynamicCodeAttribute(string _) { }
}

#endif
private static bool IsIParsable(this Type type) => type.IsAssignableTo(typeof(IParsable));

private static class KiotaDeserializationWrapperFactory
{
private static readonly ConcurrentDictionary<Type, IKiotaDeserializationWrapper> _deserializers = new ConcurrentDictionary<Type, IKiotaDeserializationWrapper>();

[RequiresDynamicCode("Activator creates an instance of a generic class with the Target Type as the generic type argument.")]
public static IKiotaDeserializationWrapper Create(Type type) => type.IsIParsable() ? _deserializers.GetOrAdd(type, CreateInternal) : throw new ArgumentException("The given Type is not of IParsable", nameof(type));

[RequiresDynamicCode("Activator creates an instance of a generic class with the Target Type as the generic type argument.")]
private static IKiotaDeserializationWrapper CreateInternal([DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicMethods)] Type targetType)
{
if(Activator.CreateInstance(typeof(KiotaDeserializationWrapper<>).MakeGenericType(targetType)) is IKiotaDeserializationWrapper deserializer)
return deserializer;
else
throw new InvalidOperationException($"Unable to create deserializer for type {targetType}");
}
}

private interface IKiotaDeserializationWrapper
{
Task<IParsable?> DeserializeAsync(string contentType, Stream stream, CancellationToken cancellationToken = default);
Task<IParsable?> DeserializeAsync(string contentType, string serializedRepresentation, CancellationToken cancellationToken = default);
Task<IEnumerable<IParsable>> DeserializeCollectionAsync(string contentType, Stream stream, CancellationToken cancellationToken = default);
Task<IEnumerable<IParsable>> DeserializeCollectionAsync(string contentType, string serializedRepresentation, CancellationToken cancellationToken = default);
}

private class KiotaDeserializationWrapper<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicMethods)] T> : IKiotaDeserializationWrapper where T : IParsable
{
public async Task<IParsable?> DeserializeAsync(string contentType, Stream stream, CancellationToken cancellationToken = default) => await KiotaSerializer.DeserializeAsync<T>(contentType, stream, cancellationToken);
public async Task<IParsable?> DeserializeAsync(string contentType, string serializedRepresentation, CancellationToken cancellationToken = default) => await KiotaSerializer.DeserializeAsync<T>(contentType, serializedRepresentation, cancellationToken);
public async Task<IEnumerable<IParsable>> DeserializeCollectionAsync(string contentType, Stream stream, CancellationToken cancellationToken = default) => (await KiotaSerializer.DeserializeCollectionAsync<T>(contentType, stream, cancellationToken)).OfType<IParsable>();
public 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>

[RequiresDynamicCode("Activator creates an instance of a generic class with the Target Type as the generic type argument.")]
public static Task<IParsable?> DeserializeAsync([DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicMethods)] Type type, string contentType, string serializedRepresentation, CancellationToken cancellationToken = default)
=> KiotaDeserializationWrapperFactory.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>
[RequiresDynamicCode("Activator creates an instance of a generic class with the Target Type as the generic type argument.")]
public static Task<IParsable?> DeserializeAsync([DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicMethods)] Type type, string contentType, Stream stream, CancellationToken cancellationToken = default)
=> KiotaDeserializationWrapperFactory.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>

[RequiresDynamicCode("Activator creates an instance of a generic class with the Target Type as the generic type argument.")]
public static Task<IEnumerable<IParsable>> DeserializeCollectionAsync(Type type, string contentType, Stream stream, CancellationToken cancellationToken = default)
=> KiotaDeserializationWrapperFactory.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>
[RequiresDynamicCode("Activator creates an instance of a generic class with the Target Type as the generic type argument.")]
public static Task<IEnumerable<IParsable>> DeserializeCollectionAsync(Type type, string contentType, string serializedRepresentation, CancellationToken cancellationToken = default)
=> KiotaDeserializationWrapperFactory.Create(type).DeserializeCollectionAsync(contentType, serializedRepresentation, cancellationToken);
}
#endif
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
#if NET5_0_OR_GREATER
using Microsoft.Kiota.Abstractions.Serialization;
using Microsoft.Kiota.Abstractions.Tests.Mocks;
using Moq;
using Xunit;

namespace Microsoft.Kiota.Abstractions.Tests.Serialization;

public partial class DeserializationHelpersTests
{

[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()
andrueastman marked this conversation as resolved.
Show resolved Hide resolved
{
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
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@

namespace Microsoft.Kiota.Abstractions.Tests.Serialization;

public class DeserializationHelpersTests
public partial class DeserializationHelpersTests
{
private const string _jsonContentType = "application/json";
[Fact]
Expand Down
Loading