Skip to content

Commit

Permalink
Initial serialized member name mapping (#13527)
Browse files Browse the repository at this point in the history
* Initial implementation of prototype serialization types

* Initial serialized member name mapping

* Update APIs and READMEs

* Fix README and add CHANGELOG

* Resolves some PR feedback

* Resolve PR feedback

* Resolve archboard feedback

* Update public API

* Updated documentation and versions

* Fix CHANGELOG entry
  • Loading branch information
heaths authored Aug 6, 2020
1 parent 637ac1b commit c3e2a57
Show file tree
Hide file tree
Showing 16 changed files with 755 additions and 22 deletions.
26 changes: 25 additions & 1 deletion sdk/core/Azure.Core/Azure.Core.sln
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,15 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.Extensions.Azure"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.Extensions.Azure.Tests", "..\Microsoft.Extensions.Azure\tests\Microsoft.Extensions.Azure.Tests.csproj", "{5B093527-8B04-4D2B-B23D-441D4B5EE305}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Azure.Core.TestFramework", "..\Azure.Core.TestFramework\src\Azure.Core.TestFramework.csproj", "{EB0781C6-6C18-4C2A-8BDB-D61A1460AB09}"
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Azure.Core.TestFramework", "..\Azure.Core.TestFramework\src\Azure.Core.TestFramework.csproj", "{EB0781C6-6C18-4C2A-8BDB-D61A1460AB09}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Azure.Core.Experimental", "..\Azure.Core.Experimental\src\Azure.Core.Experimental.csproj", "{08159FC6-D991-4728-A12C-55A05C906465}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Azure.Core.Experimental.Tests", "..\Azure.Core.Experimental\tests\Azure.Core.Experimental.Tests.csproj", "{2310EC12-5F3A-4328-9926-0212A22696F5}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.Azure.Core.NewtonsoftJson", "..\Microsoft.Azure.Core.NewtonsoftJson\src\Microsoft.Azure.Core.NewtonsoftJson.csproj", "{0CF90583-C79F-44F8-B81F-5558D90CF621}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.Azure.Core.NewtonsoftJson.Tests", "..\Microsoft.Azure.Core.NewtonsoftJson\tests\Microsoft.Azure.Core.NewtonsoftJson.Tests.csproj", "{3F70FC18-5F83-4AEE-A9BD-AE100A0B1BF8}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Expand Down Expand Up @@ -39,6 +47,22 @@ Global
{EB0781C6-6C18-4C2A-8BDB-D61A1460AB09}.Debug|Any CPU.Build.0 = Debug|Any CPU
{EB0781C6-6C18-4C2A-8BDB-D61A1460AB09}.Release|Any CPU.ActiveCfg = Release|Any CPU
{EB0781C6-6C18-4C2A-8BDB-D61A1460AB09}.Release|Any CPU.Build.0 = Release|Any CPU
{08159FC6-D991-4728-A12C-55A05C906465}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{08159FC6-D991-4728-A12C-55A05C906465}.Debug|Any CPU.Build.0 = Debug|Any CPU
{08159FC6-D991-4728-A12C-55A05C906465}.Release|Any CPU.ActiveCfg = Release|Any CPU
{08159FC6-D991-4728-A12C-55A05C906465}.Release|Any CPU.Build.0 = Release|Any CPU
{2310EC12-5F3A-4328-9926-0212A22696F5}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{2310EC12-5F3A-4328-9926-0212A22696F5}.Debug|Any CPU.Build.0 = Debug|Any CPU
{2310EC12-5F3A-4328-9926-0212A22696F5}.Release|Any CPU.ActiveCfg = Release|Any CPU
{2310EC12-5F3A-4328-9926-0212A22696F5}.Release|Any CPU.Build.0 = Release|Any CPU
{0CF90583-C79F-44F8-B81F-5558D90CF621}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{0CF90583-C79F-44F8-B81F-5558D90CF621}.Debug|Any CPU.Build.0 = Debug|Any CPU
{0CF90583-C79F-44F8-B81F-5558D90CF621}.Release|Any CPU.ActiveCfg = Release|Any CPU
{0CF90583-C79F-44F8-B81F-5558D90CF621}.Release|Any CPU.Build.0 = Release|Any CPU
{3F70FC18-5F83-4AEE-A9BD-AE100A0B1BF8}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{3F70FC18-5F83-4AEE-A9BD-AE100A0B1BF8}.Debug|Any CPU.Build.0 = Debug|Any CPU
{3F70FC18-5F83-4AEE-A9BD-AE100A0B1BF8}.Release|Any CPU.ActiveCfg = Release|Any CPU
{3F70FC18-5F83-4AEE-A9BD-AE100A0B1BF8}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
Expand Down
5 changes: 5 additions & 0 deletions sdk/core/Azure.Core/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,11 @@

## 1.4.0-preview.1 (Unreleased)

### Added
- Added `ObjectSerializer` base class for serialization.
- Added `IMemberNameConverter` for converting member names to serialized property names.
- Added `JsonObjectSerializer` that implements `ObjectSerializer` for `System.Text.Json`.

### Fixed
- Connection leak for retried non-buffered requests on .NET Framework.

Expand Down
7 changes: 6 additions & 1 deletion sdk/core/Azure.Core/api/Azure.Core.netstandard2.0.cs
Original file line number Diff line number Diff line change
Expand Up @@ -497,10 +497,15 @@ protected HttpPipelineTransport() { }
}
namespace Azure.Core.Serialization
{
public partial class JsonObjectSerializer : Azure.Core.Serialization.ObjectSerializer
public partial interface IMemberNameConverter
{
string? ConvertMemberName(System.Reflection.MemberInfo member);
}
public partial class JsonObjectSerializer : Azure.Core.Serialization.ObjectSerializer, Azure.Core.Serialization.IMemberNameConverter
{
public JsonObjectSerializer() { }
public JsonObjectSerializer(System.Text.Json.JsonSerializerOptions options) { }
string? Azure.Core.Serialization.IMemberNameConverter.ConvertMemberName(System.Reflection.MemberInfo member) { throw null; }
public override object Deserialize(System.IO.Stream stream, System.Type returnType, System.Threading.CancellationToken cancellationToken) { throw null; }
public override System.Threading.Tasks.ValueTask<object> DeserializeAsync(System.IO.Stream stream, System.Type returnType, System.Threading.CancellationToken cancellationToken) { throw null; }
public override void Serialize(System.IO.Stream stream, object? value, System.Type inputType, System.Threading.CancellationToken cancellationToken) { }
Expand Down
22 changes: 22 additions & 0 deletions sdk/core/Azure.Core/src/Serialization/IMemberNameConverter.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.

using System;
using System.Reflection;

namespace Azure.Core.Serialization
{
/// <summary>
/// Converts type member names to serializable member names.
/// </summary>
public interface IMemberNameConverter
{
/// <summary>
/// Converts a <see cref="MemberInfo"/> to a serializable member name.
/// </summary>
/// <param name="member">The <see cref="MemberInfo"/> to convert to a serializable member name.</param>
/// <returns>The serializable member name, or null if the member is not defined or ignored by the serializer.</returns>
/// <exception cref="ArgumentNullException"><paramref name="member"/> is null.</exception>
string? ConvertMemberName(MemberInfo member);
}
}
81 changes: 78 additions & 3 deletions sdk/core/Azure.Core/src/Serialization/JsonObjectSerializer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,11 @@
// Licensed under the MIT License.

using System;
using System.Collections.Concurrent;
using System.IO;
using System.Reflection;
using System.Text.Json;
using System.Text.Json.Serialization;
using System.Threading;
using System.Threading.Tasks;

Expand All @@ -12,8 +15,9 @@ namespace Azure.Core.Serialization
/// <summary>
/// A <see cref="JsonObjectSerializer"/> implementation that uses <see cref="JsonSerializer"/> to for serialization/deserialization.
/// </summary>
public class JsonObjectSerializer : ObjectSerializer
public class JsonObjectSerializer : ObjectSerializer, IMemberNameConverter
{
private readonly ConcurrentDictionary<MemberInfo, string?> _cache;
private readonly JsonSerializerOptions _options;

/// <summary>
Expand All @@ -27,9 +31,13 @@ public JsonObjectSerializer() : this(new JsonSerializerOptions())
/// Initializes new instance of <see cref="JsonObjectSerializer"/>.
/// </summary>
/// <param name="options">The <see cref="JsonSerializerOptions"/> instance to use when serializing/deserializing.</param>
/// <exception cref="ArgumentNullException"><paramref name="options"/> is null.</exception>
public JsonObjectSerializer(JsonSerializerOptions options)
{
_options = options;
_options = options ?? throw new ArgumentNullException(nameof(options));

// TODO: Consider using WeakReference cache to allow the GC to collect if the JsonObjectSerialized is held for a long duration.
_cache = new ConcurrentDictionary<MemberInfo, string?>();
}

/// <inheritdoc />
Expand Down Expand Up @@ -58,5 +66,72 @@ public override async ValueTask<object> DeserializeAsync(Stream stream, Type ret
{
return await JsonSerializer.DeserializeAsync(stream, returnType, _options, cancellationToken).ConfigureAwait(false);
}

/// <inheritdoc/>
string? IMemberNameConverter.ConvertMemberName(MemberInfo member)
{
Argument.AssertNotNull(member, nameof(member));

return _cache.GetOrAdd(member, m =>
{
// Mimics property enumeration based on:
// * https://github.com/dotnet/corefx/blob/v3.1.0/src/System.Text.Json/src/System/Text/Json/Serialization/JsonClassInfo.cs#L130-L191
// * TODO: Add support for fields when .NET 5 GAs (https://github.com/Azure/azure-sdk-for-net/issues/13627)

if (m is PropertyInfo propertyInfo)
{
// Ignore indexers.
if (propertyInfo.GetIndexParameters().Length > 0)
{
return null;
}

// Only support public getters and/or setters.
if (propertyInfo.GetMethod?.IsPublic == true ||
propertyInfo.SetMethod?.IsPublic == true)
{
if (propertyInfo.GetCustomAttribute<JsonIgnoreAttribute>() != null)
{
return null;
}

// Ignore - but do not assert correctness - for JsonExtensionDataAttribute based on
// https://github.com/dotnet/corefx/blob/v3.1.0/src/System.Text.Json/src/System/Text/Json/Serialization/JsonClassInfo.cs#L244-L261
if (propertyInfo.GetCustomAttribute<JsonExtensionDataAttribute>() != null)
{
return null;
}

// No need to validate collisions since they are based on the serialized name.
return GetPropertyName(propertyInfo);
}
}

// The member is unsupported or ignored.
return null;
});
}

private string GetPropertyName(MemberInfo memberInfo)
{
// Mimics property name determination based on
// https://github.com/dotnet/runtime/blob/dc8b6f90/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonPropertyInfo.cs#L53-L90

JsonPropertyNameAttribute nameAttribute = memberInfo.GetCustomAttribute<JsonPropertyNameAttribute>(false);
if (nameAttribute != null)
{
return nameAttribute.Name
?? throw new InvalidOperationException($"The JSON property name for '{memberInfo.DeclaringType}.{memberInfo.Name}' cannot be null.");
}
else if (_options.PropertyNamingPolicy != null)
{
return _options.PropertyNamingPolicy.ConvertName(memberInfo.Name)
?? throw new InvalidOperationException($"The JSON property name for '{memberInfo.DeclaringType}.{memberInfo.Name}' cannot be null.");
}
else
{
return memberInfo.Name;
}
}
}
}
}
2 changes: 1 addition & 1 deletion sdk/core/Azure.Core/src/Serialization/ObjectSerializer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
namespace Azure.Core.Serialization
{
/// <summary>
/// An abstraction from reading typed objects.
/// An abstraction for reading typed objects.
/// </summary>
public abstract class ObjectSerializer
{
Expand Down
Loading

0 comments on commit c3e2a57

Please sign in to comment.