Skip to content

Commit

Permalink
Added Non-Generic Solution to KiotaDeserialization
Browse files Browse the repository at this point in the history
  • Loading branch information
AndreasJakof-BASF committed Oct 11, 2024
1 parent 9ea5100 commit 7fd1380
Show file tree
Hide file tree
Showing 3 changed files with 249 additions and 0 deletions.
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

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
{
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

}

0 comments on commit 7fd1380

Please sign in to comment.