Skip to content

Commit f49c399

Browse files
authored
Implement AOT compatible serialization helper methods in ModelSerializationExtensions (#5345)
* Implement AOT compatible serialization helper methods in ModelSerializationExtensions * regen tests * refine field order * fix build * update serialization method signature
1 parent 8a58898 commit f49c399

File tree

151 files changed

+2300
-1
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

151 files changed

+2300
-1
lines changed

Directory.Build.targets

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -398,7 +398,6 @@
398398
<ProjectReference Include="..\UnbrandedProjects\Customized-TypeSpec\src\CustomizedTypeSpec.csproj" />
399399
<ProjectReference Include="..\UnbrandedProjects\NoDocsUnbranded-TypeSpec\src\NoDocsUnbrandedTypeSpec.csproj" />
400400
<ProjectReference Include="..\UnbrandedProjects\NoTest-TypeSpec\src\NoTestTypeSpec.csproj" />
401-
<ProjectReference Include="..\UnbrandedProjects\Unbranded-TypeSpec\src\UnbrandedTypeSpec.csproj" />
402401
</ItemGroup>
403402

404403
<Import Project="$(CentralPackagesFile)" Condition="'$(ShouldUseCentralVersions)' == 'true'" />

samples/AnomalyDetector/src/Generated/Internal/ModelSerializationExtensions.cs

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
using System.ClientModel.Primitives;
1010
using System.Collections.Generic;
1111
using System.Diagnostics;
12+
using System.Diagnostics.CodeAnalysis;
1213
using System.Globalization;
1314
using System.Text.Json;
1415
using System.Xml;
@@ -21,6 +22,7 @@ internal static class ModelSerializationExtensions
2122
internal static readonly JsonDocumentOptions JsonDocumentOptions = new JsonDocumentOptions { MaxDepth = 256 };
2223
internal static readonly ModelReaderWriterOptions WireOptions = new ModelReaderWriterOptions("W");
2324
internal static readonly BinaryData SentinelValue = BinaryData.FromBytes("\"__EMPTY__\""u8.ToArray());
25+
internal static readonly JsonSerializerOptions Options = new JsonSerializerOptions { Converters = { new JsonModelConverter(WireOptions, AnomalyDetectorContext.Default) } };
2426

2527
public static object GetObject(this JsonElement element)
2628
{
@@ -265,6 +267,20 @@ internal static bool IsSentinelValue(BinaryData value)
265267
return sentinelSpan.SequenceEqual(valueSpan);
266268
}
267269

270+
[UnconditionalSuppressMessage("Trimming", "IL2026", Justification = "By passing in the JsonSerializerOptions with a reference to AzureResourceManagerCosmosDBContext.Default we are certain there is no AOT compat issue.")]
271+
[UnconditionalSuppressMessage("Trimming", "IL3050", Justification = "By passing in the JsonSerializerOptions with a reference to AzureResourceManagerCosmosDBContext.Default we are certain there is no AOT compat issue.")]
272+
public static T JsonDeserialize<T>(string json, JsonSerializerOptions options)
273+
{
274+
return JsonSerializer.Deserialize<T>(json, options);
275+
}
276+
277+
[UnconditionalSuppressMessage("Trimming", "IL2026", Justification = "By passing in the JsonSerializerOptions with a reference to AzureResourceManagerCosmosDBContext.Default we are certain there is no AOT compat issue.")]
278+
[UnconditionalSuppressMessage("Trimming", "IL3050", Justification = "By passing in the JsonSerializerOptions with a reference to AzureResourceManagerCosmosDBContext.Default we are certain there is no AOT compat issue.")]
279+
public static void JsonSerialize<T>(Utf8JsonWriter writer, T data, JsonSerializerOptions options)
280+
{
281+
JsonSerializer.Serialize(writer, data, options);
282+
}
283+
268284
internal static class TypeFormatters
269285
{
270286
private const string RoundtripZFormat = "yyyy-MM-ddTHH:mm:ss.fffffffZ";

samples/AzureSample.ResourceManager.Sample/src/Generated/Internal/ModelSerializationExtensions.cs

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,10 +9,12 @@
99
using System.ClientModel.Primitives;
1010
using System.Collections.Generic;
1111
using System.Diagnostics;
12+
using System.Diagnostics.CodeAnalysis;
1213
using System.Globalization;
1314
using System.Text.Json;
1415
using System.Xml;
1516
using Azure.Core;
17+
using Azure.ResourceManager.Models;
1618

1719
namespace AzureSample.ResourceManager.Sample
1820
{
@@ -21,6 +23,8 @@ internal static class ModelSerializationExtensions
2123
internal static readonly JsonDocumentOptions JsonDocumentOptions = new JsonDocumentOptions { MaxDepth = 256 };
2224
internal static readonly ModelReaderWriterOptions WireOptions = new ModelReaderWriterOptions("W");
2325
internal static readonly BinaryData SentinelValue = BinaryData.FromBytes("\"__EMPTY__\""u8.ToArray());
26+
internal static readonly JsonSerializerOptions Options = new JsonSerializerOptions { Converters = { new JsonModelConverter(WireOptions, AzureSampleResourceManagerSampleContext.Default) } };
27+
internal static readonly JsonSerializerOptions OptionsUseManagedServiceIdentityV3 = new JsonSerializerOptions { Converters = { new JsonModelConverter(WireOptions, AzureSampleResourceManagerSampleContext.Default), new Azure.ResourceManager.Models.ManagedServiceIdentityTypeV3Converter() } };
2428

2529
public static object GetObject(this JsonElement element)
2630
{
@@ -265,6 +269,20 @@ internal static bool IsSentinelValue(BinaryData value)
265269
return sentinelSpan.SequenceEqual(valueSpan);
266270
}
267271

272+
[UnconditionalSuppressMessage("Trimming", "IL2026", Justification = "By passing in the JsonSerializerOptions with a reference to AzureResourceManagerCosmosDBContext.Default we are certain there is no AOT compat issue.")]
273+
[UnconditionalSuppressMessage("Trimming", "IL3050", Justification = "By passing in the JsonSerializerOptions with a reference to AzureResourceManagerCosmosDBContext.Default we are certain there is no AOT compat issue.")]
274+
public static T JsonDeserialize<T>(string json, JsonSerializerOptions options)
275+
{
276+
return JsonSerializer.Deserialize<T>(json, options);
277+
}
278+
279+
[UnconditionalSuppressMessage("Trimming", "IL2026", Justification = "By passing in the JsonSerializerOptions with a reference to AzureResourceManagerCosmosDBContext.Default we are certain there is no AOT compat issue.")]
280+
[UnconditionalSuppressMessage("Trimming", "IL3050", Justification = "By passing in the JsonSerializerOptions with a reference to AzureResourceManagerCosmosDBContext.Default we are certain there is no AOT compat issue.")]
281+
public static void JsonSerialize<T>(Utf8JsonWriter writer, T data, JsonSerializerOptions options)
282+
{
283+
JsonSerializer.Serialize(writer, data, options);
284+
}
285+
268286
internal static class TypeFormatters
269287
{
270288
private const string RoundtripZFormat = "yyyy-MM-ddTHH:mm:ss.fffffffZ";

src/AutoRest.CSharp/Common/Generation/Writers/CodeWriterExtensions.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -159,6 +159,7 @@ private static IDisposable WriteMethodDeclarationNoScope(this CodeWriter writer,
159159
foreach (var argument in attribute.Arguments)
160160
{
161161
argument.Write(writer);
162+
writer.AppendRaw(", ");
162163
}
163164
writer.RemoveTrailingComma();
164165
writer.LineRaw(")]");

src/AutoRest.CSharp/Common/Output/Models/Types/HelperTypeProviders/ModelSerializationExtensionsProvider.cs

Lines changed: 125 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
using System.ClientModel.Primitives;
66
using System.Collections.Generic;
77
using System.Diagnostics;
8+
using System.Diagnostics.CodeAnalysis;
89
using System.Globalization;
910
using System.Text.Json;
1011
using AutoRest.CSharp.Common.Input;
@@ -15,6 +16,7 @@
1516
using AutoRest.CSharp.Generation.Types;
1617
using AutoRest.CSharp.Output.Models.Shared;
1718
using Azure.Core;
19+
using Azure.ResourceManager.Models;
1820
using static AutoRest.CSharp.Common.Output.Models.Snippets;
1921

2022
namespace AutoRest.CSharp.Output.Models.Types
@@ -63,6 +65,38 @@ public ModelSerializationExtensionsProvider() : base(Configuration.HelperNamespa
6365
new InvokeInstanceMethodExpression(
6466
LiteralU8("\"__EMPTY__\""), "ToArray", [], null, false))
6567
};
68+
69+
_jsonSerializerOptionsField = new FieldDeclaration(
70+
modifiers: FieldModifiers.Internal | FieldModifiers.Static | FieldModifiers.ReadOnly,
71+
type: typeof(JsonSerializerOptions),
72+
name: _jsonSerializerOptionsName)
73+
{
74+
InitializationValue = New.Instance(
75+
typeof(JsonSerializerOptions),
76+
new Dictionary<string, ValueExpression>
77+
{
78+
{ "Converters", new ArrayInitializerExpression([New.Instance(typeof(JsonModelConverter), [new MemberExpression(null, _wireOptionsName), ModelReaderWriterContextExpression.Default])]) }
79+
})
80+
};
81+
82+
_jsonSerializerOptionsUseManagedServiceIdentityV3Field = new FieldDeclaration(
83+
modifiers: FieldModifiers.Internal | FieldModifiers.Static | FieldModifiers.ReadOnly,
84+
type: typeof(JsonSerializerOptions),
85+
name: _jsonSerializerOptionsUseManagedServiceIdentityV3Name)
86+
{
87+
InitializationValue = New.Instance(
88+
typeof(JsonSerializerOptions),
89+
new Dictionary<string, ValueExpression>
90+
{
91+
{
92+
"Converters",
93+
new ArrayInitializerExpression([
94+
New.Instance(typeof(JsonModelConverter), [new MemberExpression(null, _wireOptionsName), ModelReaderWriterContextExpression.Default]),
95+
New.Instance(typeof(ManagedServiceIdentityTypeV3Converter))
96+
])
97+
}
98+
})
99+
};
66100
}
67101

68102
private const string _wireOptionsName = "WireOptions";
@@ -71,6 +105,12 @@ public ModelSerializationExtensionsProvider() : base(Configuration.HelperNamespa
71105
private readonly FieldDeclaration _jsonDocumentOptionsField;
72106
private const string _sentinelBinaryDataName = "SentinelValue";
73107
private readonly FieldDeclaration? _sentinelBinaryDataField;
108+
private readonly FieldDeclaration _jsonSerializerOptionsField;
109+
private const string _jsonSerializerOptionsName = "Options";
110+
private readonly FieldDeclaration _jsonSerializerOptionsUseManagedServiceIdentityV3Field;
111+
private const string _jsonSerializerOptionsUseManagedServiceIdentityV3Name = "OptionsUseManagedServiceIdentityV3";
112+
private const string _jsonDeserializeMethodName = "JsonDeserialize";
113+
private const string _jsonSerializeMethodName = "JsonSerialize";
74114

75115
private ModelReaderWriterOptionsExpression? _wireOptions;
76116
public ModelReaderWriterOptionsExpression WireOptions => _wireOptions ??= new ModelReaderWriterOptionsExpression(new MemberExpression(Type, _wireOptionsName));
@@ -89,6 +129,15 @@ protected override IEnumerable<FieldDeclaration> BuildFields()
89129
{
90130
yield return _sentinelBinaryDataField;
91131
}
132+
if (Configuration.UseModelReaderWriter)
133+
{
134+
yield return _jsonSerializerOptionsField;
135+
136+
if (Configuration.AzureArm)
137+
{
138+
yield return _jsonSerializerOptionsUseManagedServiceIdentityV3Field;
139+
}
140+
}
92141
}
93142

94143
protected override IEnumerable<Method> BuildMethods()
@@ -138,6 +187,12 @@ protected override IEnumerable<Method> BuildMethods()
138187
yield return new(signature, body);
139188
}
140189
#endregion
190+
191+
if (Configuration.UseModelReaderWriter)
192+
{
193+
yield return BuildJsonDeserializeMethod();
194+
yield return BuildJsonSerializeMethod();
195+
}
141196
}
142197

143198
private const string _isSentinelValueMethodName = "IsSentinelValue";
@@ -451,6 +506,12 @@ private Method BuildWriteBase64StringValueMethod()
451506
return new Method(signature, body);
452507
}
453508

509+
public static ValueExpression Deserialize(JsonElementExpression element, CSharpType type, bool useManagedServiceIdentityV3 = false)
510+
=> new InvokeStaticMethodExpression(Instance.Type, _jsonDeserializeMethodName, [element, new MemberExpression(null, useManagedServiceIdentityV3 ? _jsonSerializerOptionsUseManagedServiceIdentityV3Name : _jsonSerializerOptionsName)], TypeArguments: [type]);
511+
512+
public static ValueExpression Serialize(ValueExpression data, CSharpType type, bool useManagedServiceIdentityV3 = false)
513+
=> new InvokeStaticMethodExpression(Instance.Type, _jsonSerializeMethodName, [data, new MemberExpression(null, useManagedServiceIdentityV3 ? _jsonSerializerOptionsUseManagedServiceIdentityV3Name : _jsonSerializerOptionsName)], TypeArguments: [type]);
514+
454515
public MethodBodyStatement WriteBase64StringValue(Utf8JsonWriterExpression writer, ValueExpression value, string? format)
455516
=> new InvokeStaticMethodStatement(Type, _writeBase64StringValueMethodName, new[] { writer, value, Literal(format) }, CallAsExtension: true);
456517

@@ -683,6 +744,70 @@ public MethodBodyStatement WriteObjectValue(Utf8JsonWriterExpression writer, Typ
683744
}
684745
#endregion
685746

747+
private Method BuildJsonDeserializeMethod()
748+
{
749+
var jsonParameter = new Parameter("json", null, typeof(string), null, ValidationType.None, null);
750+
var optionsParameter = new Parameter("options", null, typeof(JsonSerializerOptions), null, ValidationType.None, null);
751+
var justificationExpression = new KeywordExpression("Justification =", Literal("By passing in the JsonSerializerOptions with a reference to AzureResourceManagerCosmosDBContext.Default we are certain there is no AOT compat issue."));
752+
var signature = new MethodSignature(
753+
_jsonDeserializeMethodName,
754+
null,
755+
null,
756+
MethodSignatureModifiers.Static | MethodSignatureModifiers.Public,
757+
_t,
758+
null,
759+
[jsonParameter, optionsParameter],
760+
Attributes: Configuration.Flavor == "azure"
761+
? [
762+
new CSharpAttribute(typeof(UnconditionalSuppressMessageAttribute), Literal("Trimming"), Literal("IL2026"), justificationExpression),
763+
new CSharpAttribute(typeof(UnconditionalSuppressMessageAttribute), Literal("Trimming"), Literal("IL3050"), justificationExpression)
764+
]
765+
: [],
766+
GenericArguments: [_t]);
767+
return new Method(signature, new MethodBodyStatement[]
768+
{
769+
Return(new InvokeStaticMethodExpression(
770+
typeof(JsonSerializer),
771+
$"{nameof(JsonSerializer.Deserialize)}",
772+
[jsonParameter, optionsParameter],
773+
TypeArguments: [_t]
774+
))
775+
});
776+
}
777+
778+
private Method BuildJsonSerializeMethod()
779+
{
780+
var writerParameter = new Parameter("writer", null, typeof(Utf8JsonWriter), null, ValidationType.None, null);
781+
var dataParameter = new Parameter("data", null, _t, null, ValidationType.None, null);
782+
var optionsParameter = new Parameter("options", null, typeof(JsonSerializerOptions), null, ValidationType.None, null);
783+
var justificationExpression = new KeywordExpression("Justification =", Literal("By passing in the JsonSerializerOptions with a reference to AzureResourceManagerCosmosDBContext.Default we are certain there is no AOT compat issue."));
784+
var signature = new MethodSignature(
785+
_jsonSerializeMethodName,
786+
null,
787+
null,
788+
MethodSignatureModifiers.Static | MethodSignatureModifiers.Public,
789+
null,
790+
null,
791+
[writerParameter, dataParameter, optionsParameter],
792+
Attributes: Configuration.Flavor == "azure"
793+
? [
794+
new CSharpAttribute(typeof(UnconditionalSuppressMessageAttribute), Literal("Trimming"), Literal("IL2026"), justificationExpression),
795+
new CSharpAttribute(typeof(UnconditionalSuppressMessageAttribute), Literal("Trimming"), Literal("IL3050"), justificationExpression)
796+
]
797+
: [],
798+
GenericArguments: [_t]);
799+
return new Method(signature, new MethodBodyStatement[]
800+
{
801+
new InvokeStaticMethodExpression(
802+
typeof(JsonSerializer),
803+
$"{nameof(JsonSerializer.Serialize)}",
804+
[writerParameter, dataParameter, optionsParameter],
805+
TypeArguments: [_t]
806+
).ToStatement()
807+
});
808+
}
809+
810+
686811
public BoolExpression IsSentinelValue(ValueExpression value)
687812
{
688813
return new(new InvokeStaticMethodExpression(Type, _isSentinelValueMethodName, new[] { value }));

test/AutoRest.Shared.Tests/AutoRest.Shared.Tests.csproj

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
<ItemGroup>
2121
<Compile Include="../../src/assets/**/*.cs" />
2222
<Compile Include="../../test/TestProjects/FirstTest-TypeSpec/src/Generated/Internal/**/*.cs" Link="Generated/Helpers/%(RecursiveDir)/%(Filename)%(Extension)" />
23+
<Compile Include="../../test/TestProjects/FirstTest-TypeSpec/src/Generated/Models/FirstTestTypeSpecContext.cs" Link="Generated/Helpers/%(RecursiveDir)/%(Filename)%(Extension)" />
2324
</ItemGroup>
2425

2526
</Project>

test/AutoRest.TestServerLowLevel.Tests/AutoRest.TestServerLowLevel.Tests.csproj

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@
3737
<ItemGroup>
3838
<None Include="../swaggers/*.json" LinkBase="swaggers" />
3939
<Compile Include="..\CadlRanchProjects\authentication\api-key\src\Generated\Internal\*.cs" LinkBase="Shared/Internal" />
40+
<Compile Include="..\CadlRanchProjects\authentication\api-key\src\Generated\Models\AuthenticationApiKeyContext.cs" LinkBase="Shared/Internal" />
4041

4142
<Compile Include="../AutoRest.TestServer.Tests/Infrastructure/*.cs" LinkBase="Shared/Infrastructure" />
4243
<Compile Include="../AutoRest.TestServer.Tests/TestConstants.cs" LinkBase="Shared/Infrastructure" />

test/CadlRanchProjects/authentication/api-key/src/Generated/Internal/ModelSerializationExtensions.cs

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
using System.ClientModel.Primitives;
1010
using System.Collections.Generic;
1111
using System.Diagnostics;
12+
using System.Diagnostics.CodeAnalysis;
1213
using System.Globalization;
1314
using System.Text.Json;
1415
using System.Xml;
@@ -21,6 +22,7 @@ internal static class ModelSerializationExtensions
2122
internal static readonly JsonDocumentOptions JsonDocumentOptions = new JsonDocumentOptions { MaxDepth = 256 };
2223
internal static readonly ModelReaderWriterOptions WireOptions = new ModelReaderWriterOptions("W");
2324
internal static readonly BinaryData SentinelValue = BinaryData.FromBytes("\"__EMPTY__\""u8.ToArray());
25+
internal static readonly JsonSerializerOptions Options = new JsonSerializerOptions { Converters = { new JsonModelConverter(WireOptions, AuthenticationApiKeyContext.Default) } };
2426

2527
public static object GetObject(this JsonElement element)
2628
{
@@ -265,6 +267,20 @@ internal static bool IsSentinelValue(BinaryData value)
265267
return sentinelSpan.SequenceEqual(valueSpan);
266268
}
267269

270+
[UnconditionalSuppressMessage("Trimming", "IL2026", Justification = "By passing in the JsonSerializerOptions with a reference to AzureResourceManagerCosmosDBContext.Default we are certain there is no AOT compat issue.")]
271+
[UnconditionalSuppressMessage("Trimming", "IL3050", Justification = "By passing in the JsonSerializerOptions with a reference to AzureResourceManagerCosmosDBContext.Default we are certain there is no AOT compat issue.")]
272+
public static T JsonDeserialize<T>(string json, JsonSerializerOptions options)
273+
{
274+
return JsonSerializer.Deserialize<T>(json, options);
275+
}
276+
277+
[UnconditionalSuppressMessage("Trimming", "IL2026", Justification = "By passing in the JsonSerializerOptions with a reference to AzureResourceManagerCosmosDBContext.Default we are certain there is no AOT compat issue.")]
278+
[UnconditionalSuppressMessage("Trimming", "IL3050", Justification = "By passing in the JsonSerializerOptions with a reference to AzureResourceManagerCosmosDBContext.Default we are certain there is no AOT compat issue.")]
279+
public static void JsonSerialize<T>(Utf8JsonWriter writer, T data, JsonSerializerOptions options)
280+
{
281+
JsonSerializer.Serialize(writer, data, options);
282+
}
283+
268284
internal static class TypeFormatters
269285
{
270286
private const string RoundtripZFormat = "yyyy-MM-ddTHH:mm:ss.fffffffZ";

0 commit comments

Comments
 (0)