Skip to content

Commit ce1921b

Browse files
authored
Add DynamicCaseMapping to DynamicOptions (#36362)
* Add DynamicDataNameMapping enum * API Update * Add DateTimeHandling enum to specify serialization * Add tests and serialization in the dynamic layer * fix tests * Updates to indicate UTC more prominently * Format expected value differently in DynamicData tests * fix * Update enum names * Use STJ camel casing policy * Implement Get * Add Set * Move case mapping tests to a separate file before updates * reshuffling * Add tests * Fix serialization bug and tests * Use standard JSON casing in non-case-mapping tests * pr fb * Fix merge * CLU * pr fb * updates per offline fb * api
1 parent a3b2c9d commit ce1921b

File tree

13 files changed

+586
-386
lines changed

13 files changed

+586
-386
lines changed

sdk/cognitivelanguage/Azure.AI.Language.Conversations/tests/ConversationAnalysisClientLiveTests.cs

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
using System.Collections;
66
using System.Threading.Tasks;
77
using Azure.Core;
8+
using Azure.Core.Dynamic;
89
using Azure.Core.TestFramework;
910
using NUnit.Framework;
1011

@@ -45,7 +46,7 @@ public async Task AnalyzeConversation()
4546
Assert.IsNotNull(response);
4647

4748
// deserialize
48-
dynamic conversationalTaskResult = response.Content.ToDynamicFromJson();
49+
dynamic conversationalTaskResult = response.Content.ToDynamicFromJson(DynamicCaseMapping.PascalToCamel);
4950
Assert.IsNotNull(conversationalTaskResult);
5051

5152
// assert - prediction type
@@ -91,7 +92,7 @@ public async Task AnalyzeConversation_Orchestration_Conversation()
9192
Assert.IsNotNull(response);
9293

9394
// deserialize
94-
dynamic conversationalTaskResult = response.Content.ToDynamicFromJson();
95+
dynamic conversationalTaskResult = response.Content.ToDynamicFromJson(DynamicCaseMapping.PascalToCamel);
9596
Assert.IsNotNull(conversationalTaskResult);
9697

9798
// assert - prediction type
@@ -148,7 +149,7 @@ public async Task AnalyzeConversation_Orchestration_Luis()
148149
Assert.IsNotNull(response);
149150

150151
// deserialize
151-
dynamic conversationalTaskResult = response.Content.ToDynamicFromJson();
152+
dynamic conversationalTaskResult = response.Content.ToDynamicFromJson(DynamicCaseMapping.PascalToCamel);
152153
Assert.IsNotNull(conversationalTaskResult);
153154

154155
// assert - prediction type
@@ -200,7 +201,7 @@ public async Task AnalyzeConversation_Orchestration_QuestionAnswering()
200201
Assert.IsNotNull(response);
201202

202203
// deserialize
203-
dynamic conversationalTaskResult = response.Content.ToDynamicFromJson();
204+
dynamic conversationalTaskResult = response.Content.ToDynamicFromJson(DynamicCaseMapping.PascalToCamel);
204205
Assert.IsNotNull(conversationalTaskResult);
205206

206207
// assert - prediction type
@@ -255,7 +256,7 @@ public async Task SupportsAadAuthentication()
255256

256257
Response response = await client.AnalyzeConversationAsync(RequestContent.Create(data));
257258

258-
dynamic conversationalTaskResult = response.Content.ToDynamicFromJson();
259+
dynamic conversationalTaskResult = response.Content.ToDynamicFromJson(DynamicCaseMapping.PascalToCamel);
259260
Assert.That((string)conversationalTaskResult.Result.Prediction.TopIntent, Is.EqualTo("Send"));
260261
}
261262

@@ -321,7 +322,7 @@ public async Task AnalyzeConversation_ConversationSummarization()
321322

322323
Operation<BinaryData> analyzeConversationOperation = await Client.AnalyzeConversationAsync(WaitUntil.Completed, RequestContent.Create(data));
323324

324-
dynamic jobResults = analyzeConversationOperation.Value.ToDynamicFromJson();
325+
dynamic jobResults = analyzeConversationOperation.Value.ToDynamicFromJson(DynamicCaseMapping.PascalToCamel);
325326
Assert.NotNull(jobResults);
326327

327328
foreach (dynamic analyzeConversationSummarization in jobResults.Tasks.Items)

sdk/cognitivelanguage/Azure.AI.Language.Conversations/tests/ConversationAuthoringClientLiveTests.cs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
using System;
55
using System.Threading.Tasks;
66
using Azure.AI.Language.Conversations.Authoring;
7+
using Azure.Core.Dynamic;
78
using Azure.Core.TestFramework;
89
using NUnit.Framework;
910

@@ -59,7 +60,7 @@ public async Task SupportsAadAuthentication()
5960

6061
Response response = await client.GetProjectAsync(TestEnvironment.ProjectName);
6162

62-
dynamic project = response.Content.ToDynamicFromJson();
63+
dynamic project = response.Content.ToDynamicFromJson(DynamicCaseMapping.PascalToCamel);
6364
Assert.That((string)project.ProjectKind, Is.EqualTo("Conversation"));
6465
}
6566
}

sdk/core/Azure.Core.Experimental/api/Azure.Core.Experimental.net461.cs

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ namespace Azure
33
public static partial class BinaryDataExtensions
44
{
55
public static dynamic ToDynamicFromJson(this System.BinaryData utf8Json) { throw null; }
6+
public static dynamic ToDynamicFromJson(this System.BinaryData utf8Json, Azure.Core.Dynamic.DynamicCaseMapping caseMapping, Azure.Core.Dynamic.DynamicDateTimeHandling dateTimeHandling = Azure.Core.Dynamic.DynamicDateTimeHandling.Rfc3339) { throw null; }
67
public static dynamic ToDynamicFromJson(this System.BinaryData utf8Json, Azure.Core.Dynamic.DynamicDataOptions options) { throw null; }
78
}
89
[System.Runtime.InteropServices.StructLayoutAttribute(System.Runtime.InteropServices.LayoutKind.Sequential)]
@@ -113,6 +114,11 @@ public readonly partial struct Value
113114
}
114115
namespace Azure.Core.Dynamic
115116
{
117+
public enum DynamicCaseMapping
118+
{
119+
None = 0,
120+
PascalToCamel = 1,
121+
}
116122
[System.Diagnostics.DebuggerDisplayAttribute("{DebuggerDisplay,nq}")]
117123
public sealed partial class DynamicData : System.Dynamic.IDynamicMetaObjectProvider, System.IDisposable
118124
{
@@ -146,6 +152,7 @@ public void Dispose() { }
146152
public partial class DynamicDataOptions
147153
{
148154
public DynamicDataOptions() { }
155+
public Azure.Core.Dynamic.DynamicCaseMapping CaseMapping { get { throw null; } set { } }
149156
public Azure.Core.Dynamic.DynamicDateTimeHandling DateTimeHandling { get { throw null; } set { } }
150157
}
151158
public enum DynamicDateTimeHandling

sdk/core/Azure.Core.Experimental/api/Azure.Core.Experimental.net6.0.cs

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ namespace Azure
33
public static partial class BinaryDataExtensions
44
{
55
public static dynamic ToDynamicFromJson(this System.BinaryData utf8Json) { throw null; }
6+
public static dynamic ToDynamicFromJson(this System.BinaryData utf8Json, Azure.Core.Dynamic.DynamicCaseMapping caseMapping, Azure.Core.Dynamic.DynamicDateTimeHandling dateTimeHandling = Azure.Core.Dynamic.DynamicDateTimeHandling.Rfc3339) { throw null; }
67
public static dynamic ToDynamicFromJson(this System.BinaryData utf8Json, Azure.Core.Dynamic.DynamicDataOptions options) { throw null; }
78
}
89
[System.Runtime.InteropServices.StructLayoutAttribute(System.Runtime.InteropServices.LayoutKind.Sequential)]
@@ -113,6 +114,11 @@ public readonly partial struct Value
113114
}
114115
namespace Azure.Core.Dynamic
115116
{
117+
public enum DynamicCaseMapping
118+
{
119+
None = 0,
120+
PascalToCamel = 1,
121+
}
116122
[System.Diagnostics.DebuggerDisplayAttribute("{DebuggerDisplay,nq}")]
117123
public sealed partial class DynamicData : System.Dynamic.IDynamicMetaObjectProvider, System.IDisposable
118124
{
@@ -146,6 +152,7 @@ public void Dispose() { }
146152
public partial class DynamicDataOptions
147153
{
148154
public DynamicDataOptions() { }
155+
public Azure.Core.Dynamic.DynamicCaseMapping CaseMapping { get { throw null; } set { } }
149156
public Azure.Core.Dynamic.DynamicDateTimeHandling DateTimeHandling { get { throw null; } set { } }
150157
}
151158
public enum DynamicDateTimeHandling

sdk/core/Azure.Core.Experimental/api/Azure.Core.Experimental.netstandard2.0.cs

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ namespace Azure
33
public static partial class BinaryDataExtensions
44
{
55
public static dynamic ToDynamicFromJson(this System.BinaryData utf8Json) { throw null; }
6+
public static dynamic ToDynamicFromJson(this System.BinaryData utf8Json, Azure.Core.Dynamic.DynamicCaseMapping caseMapping, Azure.Core.Dynamic.DynamicDateTimeHandling dateTimeHandling = Azure.Core.Dynamic.DynamicDateTimeHandling.Rfc3339) { throw null; }
67
public static dynamic ToDynamicFromJson(this System.BinaryData utf8Json, Azure.Core.Dynamic.DynamicDataOptions options) { throw null; }
78
}
89
[System.Runtime.InteropServices.StructLayoutAttribute(System.Runtime.InteropServices.LayoutKind.Sequential)]
@@ -113,6 +114,11 @@ public readonly partial struct Value
113114
}
114115
namespace Azure.Core.Dynamic
115116
{
117+
public enum DynamicCaseMapping
118+
{
119+
None = 0,
120+
PascalToCamel = 1,
121+
}
116122
[System.Diagnostics.DebuggerDisplayAttribute("{DebuggerDisplay,nq}")]
117123
public sealed partial class DynamicData : System.Dynamic.IDynamicMetaObjectProvider, System.IDisposable
118124
{
@@ -146,6 +152,7 @@ public void Dispose() { }
146152
public partial class DynamicDataOptions
147153
{
148154
public DynamicDataOptions() { }
155+
public Azure.Core.Dynamic.DynamicCaseMapping CaseMapping { get { throw null; } set { } }
149156
public Azure.Core.Dynamic.DynamicDateTimeHandling DateTimeHandling { get { throw null; } set { } }
150157
}
151158
public enum DynamicDateTimeHandling

sdk/core/Azure.Core.Experimental/src/DynamicData/BinaryDataExtensions.cs

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,20 @@ public static dynamic ToDynamicFromJson(this BinaryData utf8Json)
1919
return utf8Json.ToDynamicFromJson(DynamicDataOptions.Default);
2020
}
2121

22+
/// <summary>
23+
/// Return the content of the BinaryData as a dynamic type.
24+
/// </summary>
25+
public static dynamic ToDynamicFromJson(this BinaryData utf8Json, DynamicCaseMapping caseMapping, DynamicDateTimeHandling dateTimeHandling = DynamicDateTimeHandling.Rfc3339)
26+
{
27+
DynamicDataOptions options = new()
28+
{
29+
CaseMapping = caseMapping,
30+
DateTimeHandling = dateTimeHandling
31+
};
32+
33+
return utf8Json.ToDynamicFromJson(options);
34+
}
35+
2236
/// <summary>
2337
/// Return the content of the BinaryData as a dynamic type.
2438
/// </summary>
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
// Copyright (c) Microsoft Corporation. All rights reserved.
2+
// Licensed under the MIT License.
3+
4+
namespace Azure.Core.Dynamic
5+
{
6+
/// <summary>
7+
/// Options for getting and setting DynamicData properties.
8+
/// </summary>
9+
public enum DynamicCaseMapping
10+
{
11+
/// <summary>
12+
/// Properties are read from and written to the data content with the same casing as the DynamicData property name.
13+
/// </summary>
14+
None = 0,
15+
16+
/// <summary>
17+
/// A "PascalCase" DynamicData property name can be used to get "camelCase" members in the data content.
18+
/// Values assigned to DynamicData properties are written to the data content with a "camelCase" name mapping
19+
/// applied to the property name. This mapping is not applied when using indexer syntax.
20+
/// </summary>
21+
PascalToCamel = 1
22+
}
23+
}

sdk/core/Azure.Core.Experimental/src/DynamicData/DynamicData.cs

Lines changed: 25 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -42,30 +42,38 @@ internal DynamicData(MutableJsonElement element, DynamicDataOptions options)
4242

4343
internal static JsonSerializerOptions GetSerializerOptions(DynamicDataOptions options)
4444
{
45-
JsonSerializerOptions serializer = new()
45+
JsonSerializerOptions serializerOptions = new()
4646
{
47-
PropertyNameCaseInsensitive = true,
48-
PropertyNamingPolicy = JsonNamingPolicy.CamelCase,
4947
Converters =
5048
{
5149
new DefaultTimeSpanConverter()
5250
}
5351
};
5452

53+
switch (options.CaseMapping)
54+
{
55+
case DynamicCaseMapping.PascalToCamel:
56+
serializerOptions.PropertyNamingPolicy = JsonNamingPolicy.CamelCase;
57+
break;
58+
case DynamicCaseMapping.None:
59+
default:
60+
break;
61+
}
62+
5563
switch (options.DateTimeHandling)
5664
{
5765
case DynamicDateTimeHandling.UnixTime:
58-
serializer.Converters.Add(new UnixTimeDateTimeConverter());
59-
serializer.Converters.Add(new UnixTimeDateTimeOffsetConverter());
66+
serializerOptions.Converters.Add(new UnixTimeDateTimeConverter());
67+
serializerOptions.Converters.Add(new UnixTimeDateTimeOffsetConverter());
6068
break;
6169
case DynamicDateTimeHandling.Rfc3339:
6270
default:
63-
serializer.Converters.Add(new Rfc3339DateTimeConverter());
64-
serializer.Converters.Add(new Rfc3339DateTimeOffsetConverter());
71+
serializerOptions.Converters.Add(new Rfc3339DateTimeConverter());
72+
serializerOptions.Converters.Add(new Rfc3339DateTimeOffsetConverter());
6573
break;
6674
}
6775

68-
return serializer;
76+
return serializerOptions;
6977
}
7078

7179
internal void WriteTo(Stream stream)
@@ -88,26 +96,21 @@ internal void WriteTo(Stream stream)
8896
return new DynamicData(element, _options);
8997
}
9098

91-
if (char.IsUpper(name[0]))
99+
// If we're using the PascalToCamel mapping and the strict name lookup
100+
// failed, do a second lookup with a camelCase name as well.
101+
if (_options.CaseMapping == DynamicCaseMapping.PascalToCamel && char.IsUpper(name[0]))
92102
{
93-
if (_element.TryGetProperty(GetAsCamelCase(name), out element))
103+
if (_element.TryGetProperty(ConvertToCamelCase(name), out element))
94104
{
95105
return new DynamicData(element, _options);
96106
}
97107
}
98108

109+
// Mimic Azure SDK model behavior for optional properties.
99110
return null;
100111
}
101112

102-
private static string GetAsCamelCase(string value)
103-
{
104-
if (value.Length < 2)
105-
{
106-
return value.ToLowerInvariant();
107-
}
108-
109-
return $"{char.ToLowerInvariant(value[0])}{value.Substring(1)}";
110-
}
113+
private static string ConvertToCamelCase(string value) => JsonNamingPolicy.CamelCase.ConvertName(value);
111114

112115
private object? GetViaIndexer(object index)
113116
{
@@ -147,28 +150,12 @@ private IEnumerable GetEnumerable()
147150
value = ConvertType(value);
148151
}
149152

150-
if (!char.IsUpper(name[0]))
151-
{
152-
// Lookup name is camelCase, so set unchanged.
153-
_element = _element.SetProperty(name, value);
154-
return null;
155-
}
156-
157-
// Lookup name is PascalCase, so check for the property as PascalCase then camelCase.
158-
if (_element.TryGetProperty(name, out MutableJsonElement element))
159-
{
160-
element.Set(value);
161-
return null;
162-
}
163-
164-
if (_element.TryGetProperty(GetAsCamelCase(name), out element))
153+
if (_options.CaseMapping == DynamicCaseMapping.PascalToCamel)
165154
{
166-
element.Set(value);
167-
return null;
155+
name = ConvertToCamelCase(name);
168156
}
169157

170-
// It's a new property, so set with a camelCase member name.
171-
_element = _element.SetProperty(GetAsCamelCase(name), value);
158+
_element = _element.SetProperty(name, value);
172159

173160
// Binding machinery expects the call site signature to return an object
174161
return null;

sdk/core/Azure.Core.Experimental/src/DynamicData/DynamicDataOptions.cs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,11 @@ public class DynamicDataOptions
1414
};
1515
internal static DynamicDataOptions Default { get => _default; }
1616

17+
/// <summary>
18+
/// Gets or sets an object that specifies how dynamic property names will be mapped to member names in the data buffer.
19+
/// </summary>
20+
public DynamicCaseMapping CaseMapping { get; set; }
21+
1722
/// <summary>
1823
/// Gets or sets an object that specifies how DateTime and DateTimeOffset should be handled when serializing and deserializing.
1924
/// </summary>

0 commit comments

Comments
 (0)