Skip to content

Commit

Permalink
Merge pull request #436 from DerGuru/main
Browse files Browse the repository at this point in the history
Added Non-Generic Solution to KiotaDeserialization
  • Loading branch information
andrueastman authored Oct 28, 2024
2 parents 650812d + f7e71dd commit 75f6bd3
Show file tree
Hide file tree
Showing 7 changed files with 276 additions and 2 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.13.2] - 2024-10-28

### Changed

- Added Non-Generic Solution to KiotaDeserialization helper method. [#436](https://github.com/microsoft/kiota-dotnet/pull/436)

## [1.13.1] - 2024-10-10

### 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.13.1</VersionPrefix>
<VersionPrefix>1.13.2</VersionPrefix>
<VersionSuffix></VersionSuffix>
<!-- This is overidden in test projects by setting to true-->
<IsTestProject>false</IsTestProject>
Expand Down
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,74 @@
// ------------------------------------------------------------------------------
// Copyright (c) Microsoft Corporation. All Rights Reserved. Licensed under the MIT License. See License in the project root for license information.\
// ------------------------------------------------------------------------------

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

#if NET5_0_OR_GREATER
using System.Diagnostics.CodeAnalysis;
#endif

namespace Microsoft.Kiota.Abstractions.Serialization;
public static partial class KiotaJsonSerializer
{

/// <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>
#if NET7_0_OR_GREATER
[RequiresDynamicCode("Activator creates an instance of a generic class with the Target Type as the generic type argument.")]
#endif
#if NET5_0_OR_GREATER
public static Task<IParsable?> DeserializeAsync([DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicMethods)] Type targetType, string serializedRepresentation, CancellationToken cancellationToken = default)
#else
public static Task<IParsable?> DeserializeAsync(Type targetType, string serializedRepresentation, CancellationToken cancellationToken = default)
#endif
=> KiotaSerializer.DeserializeAsync(targetType, _jsonContentType, 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>
#if NET7_0_OR_GREATER
[RequiresDynamicCode("Activator creates an instance of a generic class with the Target Type as the generic type argument.")]
#endif
#if NET5_0_OR_GREATER
public static Task<IParsable?> DeserializeAsync([DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicMethods)] Type targetType, Stream stream, CancellationToken cancellationToken = default)
#else
public static Task<IParsable?> DeserializeAsync(Type targetType, Stream stream, CancellationToken cancellationToken = default)
#endif
=> KiotaSerializer.DeserializeAsync(targetType, _jsonContentType, 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>
#if NET7_0_OR_GREATER
[RequiresDynamicCode("Activator creates an instance of a generic class with the Target Type as the generic type argument.")]
#endif
public static Task<IEnumerable<IParsable>> DeserializeCollectionAsync(Type targetType, Stream stream, CancellationToken cancellationToken = default)
=> KiotaSerializer.DeserializeCollectionAsync(targetType, _jsonContentType, 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>
#if NET7_0_OR_GREATER
[RequiresDynamicCode("Activator creates an instance of a generic class with the Target Type as the generic type argument.")]
#endif
public static Task<IEnumerable<IParsable>> DeserializeCollectionAsync(Type targetType, string serializedRepresentation, CancellationToken cancellationToken = default)
=> KiotaSerializer.DeserializeCollectionAsync(targetType, _jsonContentType, serializedRepresentation, cancellationToken);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,132 @@
// ------------------------------------------------------------------------------
// Copyright (c) Microsoft Corporation. All Rights Reserved. Licensed under the MIT License. See License in the project root for license information.
// ------------------------------------------------------------------------------



using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Runtime.CompilerServices;
using System.Threading;
using System.Threading.Tasks;



#if NET5_0_OR_GREATER
using System.Diagnostics.CodeAnalysis;
#endif

namespace Microsoft.Kiota.Abstractions.Serialization;

internal interface IKiotaDeserializationWrapper
{
Task<IParsable?> DeserializeAsync(string contentType, Stream stream, CancellationToken cancellationToken);
Task<IParsable?> DeserializeAsync(string contentType, string serializedRepresentation, CancellationToken cancellationToken);
Task<IEnumerable<IParsable>> DeserializeCollectionAsync(string contentType, Stream stream, CancellationToken cancellationToken);
Task<IEnumerable<IParsable>> DeserializeCollectionAsync(string contentType, string serializedRepresentation, CancellationToken cancellationToken);
}
#if NET5_0_OR_GREATER
internal class KiotaDeserializationWrapper<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicMethods)] T> : IKiotaDeserializationWrapper where T : IParsable
#else
internal class KiotaDeserializationWrapper<T> : IKiotaDeserializationWrapper where T : IParsable
#endif
{
public async Task<IParsable?> DeserializeAsync(string contentType, Stream stream, CancellationToken cancellationToken) => await KiotaSerializer.DeserializeAsync<T>(contentType, stream, cancellationToken).ConfigureAwait(false);
public async Task<IParsable?> DeserializeAsync(string contentType, string serializedRepresentation, CancellationToken cancellationToken) => await KiotaSerializer.DeserializeAsync<T>(contentType, serializedRepresentation, cancellationToken).ConfigureAwait(false);
public async Task<IEnumerable<IParsable>> DeserializeCollectionAsync(string contentType, Stream stream, CancellationToken cancellationToken) => (await KiotaSerializer.DeserializeCollectionAsync<T>(contentType, stream, cancellationToken).ConfigureAwait(false)).OfType<IParsable>();
public async Task<IEnumerable<IParsable>> DeserializeCollectionAsync(string contentType, string serializedRepresentation, CancellationToken cancellationToken) => (await KiotaSerializer.DeserializeCollectionAsync<T>(contentType, serializedRepresentation, cancellationToken).ConfigureAwait(false)).OfType<IParsable>();
}
static internal class KiotaDeserializationWrapperFactory
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static bool IsIParsable(Type type) => typeof(IParsable).IsAssignableFrom(type);
private static readonly ConcurrentDictionary<Type, IKiotaDeserializationWrapper> _deserializers = new ConcurrentDictionary<Type, IKiotaDeserializationWrapper>();
#if NET7_0_OR_GREATER
[RequiresDynamicCode("Activator creates an instance of a generic class with the Target Type as the generic type argument.")]
#endif
static internal IKiotaDeserializationWrapper Create(Type type) => IsIParsable(type) ? _deserializers.GetOrAdd(type, CreateInternal) : throw new ArgumentException("The given Type is not of IParsable", nameof(type));

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

public static partial class KiotaSerializer
{
/// <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>

#if NET7_0_OR_GREATER
[RequiresDynamicCode("Activator creates an instance of a generic class with the Target Type as the generic type argument.")]
#endif
#if NET5_0_OR_GREATER
public static Task<IParsable?> DeserializeAsync([DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicMethods)] Type type, string contentType, string serializedRepresentation, CancellationToken cancellationToken = default)
#else
public static Task<IParsable?> DeserializeAsync(Type type, string contentType, string serializedRepresentation, CancellationToken cancellationToken = default)
#endif
=> 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>
#if NET7_0_OR_GREATER
[RequiresDynamicCode("Activator creates an instance of a generic class with the Target Type as the generic type argument.")]
#endif
#if NET5_0_OR_GREATER
public static Task<IParsable?> DeserializeAsync([DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicMethods)] Type type, string contentType, Stream stream, CancellationToken cancellationToken = default)
#else
public static Task<IParsable?> DeserializeAsync(Type type, string contentType, Stream stream, CancellationToken cancellationToken = default)
#endif
=> 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>
#if NET7_0_OR_GREATER
[RequiresDynamicCode("Activator creates an instance of a generic class with the Target Type as the generic type argument.")]
#endif
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>
#if NET7_0_OR_GREATER
[RequiresDynamicCode("Activator creates an instance of a generic class with the Target Type as the generic type argument.")]
#endif
public static Task<IEnumerable<IParsable>> DeserializeCollectionAsync(Type type, string contentType, string serializedRepresentation, CancellationToken cancellationToken = default)
=> KiotaDeserializationWrapperFactory.Create(type).DeserializeCollectionAsync(contentType, serializedRepresentation, cancellationToken);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@

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 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);
}
}
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

0 comments on commit 75f6bd3

Please sign in to comment.