Skip to content

Commit 32c1353

Browse files
authored
Merge pull request #312 from microsoft/andrueastman/fixes
Aligns Enum serialization across packages and pass relevant JsonWriterOptions from the KiotaJsonSerializationContext
2 parents bc0404d + b531e35 commit 32c1353

File tree

13 files changed

+183
-41
lines changed

13 files changed

+183
-41
lines changed

CHANGELOG.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,11 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
77

88
## [Unreleased]
99

10+
## [1.10.1] - 2024-08-01
11+
12+
- Cleans up enum serialization to read from attributes for form and text serialization [#284](https://github.com/microsoft/kiota-dotnet/issues/284)
13+
- Pass relevant `JsonWriterOptions` from the `KiotaJsonSerializationContext.Options` to the `Utf8JsonWriter` when writing json to enable customization. [#281](https://github.com/microsoft/kiota-dotnet/issues/281)
14+
1015
## [1.10.0] - 2024-07-17
1116

1217
- Adds Kiota bundle package to provide default adapter with registrations setup. [#290](https://github.com/microsoft/kiota-dotnet/issues/290)

Directory.Build.props

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
<Project>
22
<!-- Common default project properties for ALL projects-->
33
<PropertyGroup>
4-
<VersionPrefix>1.10.0</VersionPrefix>
4+
<VersionPrefix>1.10.1</VersionPrefix>
55
<VersionSuffix></VersionSuffix>
66
<!-- This is overidden in test projects by setting to true-->
77
<IsTestProject>false</IsTestProject>

src/abstractions/Helpers/EnumHelpers.cs

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
using System;
22
using System.Reflection;
33
using System.Runtime.Serialization;
4+
using Microsoft.Kiota.Abstractions.Extensions;
45

56
#if NET5_0_OR_GREATER
67
using System.Diagnostics.CodeAnalysis;
@@ -154,5 +155,29 @@ private static bool TryGetFieldValueName(Type type, string rawValue, out string
154155
}
155156
return false;
156157
}
158+
159+
/// <summary>
160+
/// Gets the enum string representation of the given value. Looks up if there is an <see cref="EnumMemberAttribute"/> and returns the value if found, otherwise returns the enum name in camel case.
161+
/// </summary>
162+
/// <typeparam name="T">The Enum type</typeparam>
163+
/// <param name="value">The enum value</param>
164+
/// <returns></returns>
165+
/// <exception cref="ArgumentException">If value is null</exception>
166+
#if NET5_0_OR_GREATER
167+
public static string? GetEnumStringValue<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicFields)] T>(T value) where T : struct, Enum
168+
#else
169+
public static string? GetEnumStringValue<T>(T value) where T : struct, Enum
170+
#endif
171+
{
172+
var type = typeof(T);
173+
174+
if(Enum.GetName(type, value) is not { } name)
175+
throw new ArgumentException($"Invalid Enum value {value} for enum of type {type}");
176+
177+
if(type.GetField(name)?.GetCustomAttribute<EnumMemberAttribute>() is { } attribute)
178+
return attribute.Value;
179+
180+
return name;
181+
}
157182
}
158183
}

src/serialization/form/FormSerializationWriter.cs

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
#endif
1212
using System;
1313
using System.Collections.Generic;
14+
using Microsoft.Kiota.Abstractions.Helpers;
1415

1516
namespace Microsoft.Kiota.Serialization.Form;
1617
/// <summary>Represents a serialization writer that can be used to write a form url encoded string.</summary>
@@ -247,13 +248,13 @@ public void WriteCollectionOfEnumValues<T>(string? key, IEnumerable<T?>? values)
247248
StringBuilder? valueNames = null;
248249
foreach(var x in values)
249250
{
250-
if(x.HasValue && Enum.GetName(typeof(T), x.Value) is string valueName)
251+
if(x.HasValue && EnumHelpers.GetEnumStringValue(x.Value) is string valueName)
251252
{
252253
if(valueNames == null)
253254
valueNames = new StringBuilder();
254255
else
255256
valueNames.Append(",");
256-
valueNames.Append(valueName.ToFirstCharacterLowerCase());
257+
valueNames.Append(valueName);
257258
}
258259
}
259260

@@ -281,16 +282,16 @@ public void WriteEnumValue<T>(string? key, T? value) where T : struct, Enum
281282
StringBuilder valueNames = new StringBuilder();
282283
foreach(var x in values)
283284
{
284-
if(value.Value.HasFlag(x) && Enum.GetName(typeof(T), x) is string valueName)
285+
if(value.Value.HasFlag(x) && EnumHelpers.GetEnumStringValue(x) is string valueName)
285286
{
286287
if(valueNames.Length > 0)
287288
valueNames.Append(",");
288-
valueNames.Append(valueName.ToFirstCharacterLowerCase());
289+
valueNames.Append(valueName);
289290
}
290291
}
291292
WriteStringValue(key, valueNames.ToString());
292293
}
293-
else WriteStringValue(key, value.Value.ToString().ToFirstCharacterLowerCase());
294+
else WriteStringValue(key, EnumHelpers.GetEnumStringValue(value.Value));
294295
}
295296
}
296297
}

src/serialization/json/JsonSerializationWriter.cs

Lines changed: 8 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -7,12 +7,11 @@
77
using System.Collections.Generic;
88
using System.IO;
99
using System.Reflection;
10-
using System.Runtime.Serialization;
1110
using System.Text;
1211
using System.Text.Json;
1312
using System.Xml;
1413
using Microsoft.Kiota.Abstractions;
15-
using Microsoft.Kiota.Abstractions.Extensions;
14+
using Microsoft.Kiota.Abstractions.Helpers;
1615
using Microsoft.Kiota.Abstractions.Serialization;
1716

1817
#if NET5_0_OR_GREATER
@@ -49,7 +48,11 @@ public JsonSerializationWriter()
4948
public JsonSerializationWriter(KiotaJsonSerializationContext kiotaJsonSerializationContext)
5049
{
5150
_kiotaJsonSerializationContext = kiotaJsonSerializationContext;
52-
writer = new Utf8JsonWriter(_stream);
51+
writer = new Utf8JsonWriter(_stream, new JsonWriterOptions
52+
{
53+
Encoder = kiotaJsonSerializationContext.Options.Encoder,
54+
Indented = kiotaJsonSerializationContext.Options.WriteIndented
55+
});
5356
}
5457

5558
/// <summary>
@@ -290,7 +293,7 @@ public void WriteEnumValue<T>(string? key, T? value) where T : struct, Enum
290293
StringBuilder valueNames = new StringBuilder();
291294
foreach(var x in values)
292295
{
293-
if(value.Value.HasFlag(x) && GetEnumName(x) is string valueName)
296+
if(value.Value.HasFlag(x) && EnumHelpers.GetEnumStringValue(x) is string valueName)
294297
{
295298
if(valueNames.Length > 0)
296299
valueNames.Append(",");
@@ -299,7 +302,7 @@ public void WriteEnumValue<T>(string? key, T? value) where T : struct, Enum
299302
}
300303
WriteStringValue(null, valueNames.ToString());
301304
}
302-
else WriteStringValue(null, GetEnumName(value.Value));
305+
else WriteStringValue(null, EnumHelpers.GetEnumStringValue(value.Value));
303306
}
304307
}
305308

@@ -559,22 +562,7 @@ public void Dispose()
559562
writer.Dispose();
560563
GC.SuppressFinalize(this);
561564
}
562-
#if NET5_0_OR_GREATER
563-
private static string? GetEnumName<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicFields)] T>(T value) where T : struct, Enum
564-
#else
565-
private static string? GetEnumName<T>(T value) where T : struct, Enum
566-
#endif
567-
{
568-
var type = typeof(T);
569-
570-
if(Enum.GetName(type, value) is not { } name)
571-
throw new ArgumentException($"Invalid Enum value {value} for enum of type {type}");
572565

573-
if(type.GetField(name)?.GetCustomAttribute<EnumMemberAttribute>() is { } attribute)
574-
return attribute.Value;
575-
576-
return name.ToFirstCharacterLowerCase();
577-
}
578566
/// <summary>
579567
/// Writes a untyped value for the specified key.
580568
/// </summary>

src/serialization/text/TextSerializationWriter.cs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
using System.Xml;
66
using Microsoft.Kiota.Abstractions;
77
using Microsoft.Kiota.Abstractions.Extensions;
8+
using Microsoft.Kiota.Abstractions.Helpers;
89
using Microsoft.Kiota.Abstractions.Serialization;
910

1011
#if NET5_0_OR_GREATER
@@ -118,5 +119,5 @@ public void WriteCollectionOfEnumValues<T>(string? key, IEnumerable<T?>? values)
118119
#else
119120
public void WriteEnumValue<T>(string? key, T? value) where T : struct, Enum
120121
#endif
121-
=> WriteStringValue(key, value.HasValue ? value.Value.ToString().ToFirstCharacterLowerCase() : null);
122+
=> WriteStringValue(key, value.HasValue ? EnumHelpers.GetEnumStringValue(value.Value) : null);
122123
}

tests/serialization/form/FormSerializationWriterTests.cs

Lines changed: 21 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,7 @@ public void WritesSampleObjectValue()
5151

5252
// Assert
5353
var expectedString = "id=48d31887-5fad-4d73-a9f5-3c356e68a038&" +
54-
"numbers=one%2Ctwo&" + // serializes enums
54+
"numbers=One%2CTwo&" + // serializes enums
5555
"workDuration=PT1H&" + // Serializes timespans
5656
"birthDay=2017-09-04&" + // Serializes dates
5757
"startWorkTime=08%3A00%3A00&" + //Serializes times
@@ -432,7 +432,25 @@ public void WriteEnumValue_IsWrittenCorrectly()
432432
var serializedString = reader.ReadToEnd();
433433

434434
// Assert
435-
Assert.Equal("prop1=sixteen", serializedString);
435+
Assert.Equal("prop1=Sixteen", serializedString);
436+
}
437+
438+
[Fact]
439+
public void WriteEnumValueWithAttribute_IsWrittenCorrectly()
440+
{
441+
// Arrange
442+
var value = TestNamingEnum.Item2SubItem1;
443+
444+
using var formSerializationWriter = new FormSerializationWriter();
445+
446+
// Act
447+
formSerializationWriter.WriteEnumValue<TestNamingEnum>("prop1", value);
448+
var contentStream = formSerializationWriter.GetSerializedContent();
449+
using var reader = new StreamReader(contentStream, Encoding.UTF8);
450+
var serializedString = reader.ReadToEnd();
451+
452+
// Assert
453+
Assert.Equal("prop1=Item2%3ASubItem1", serializedString);
436454
}
437455

438456
[Fact]
@@ -450,6 +468,6 @@ public void WriteCollectionOfEnumValues_IsWrittenCorrectly()
450468
var serializedString = reader.ReadToEnd();
451469

452470
// Assert
453-
Assert.Equal("prop1=sixteen%2Ctwo", serializedString);
471+
Assert.Equal("prop1=Sixteen%2CTwo", serializedString);
454472
}
455473
}
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
using System.Runtime.Serialization;
2+
3+
namespace Microsoft.Kiota.Serialization.Form.Tests.Mocks
4+
{
5+
public enum TestNamingEnum
6+
{
7+
Item1,
8+
[EnumMember(Value = "Item2:SubItem1")]
9+
Item2SubItem1,
10+
[EnumMember(Value = "Item3:SubItem1")]
11+
Item3SubItem1
12+
}
13+
}

tests/serialization/json/JsonSerializationWriterTests.cs

Lines changed: 65 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
using System.Globalization;
44
using System.IO;
55
using System.Text;
6+
using System.Text.Encodings.Web;
67
using System.Text.Json;
78
using System.Threading;
89
using Microsoft.Kiota.Abstractions;
@@ -156,7 +157,7 @@ public void WritesSampleCollectionOfObjectValues()
156157
// Assert
157158
var expectedString = "[{" +
158159
"\"id\":\"48d31887-5fad-4d73-a9f5-3c356e68a038\"," +
159-
"\"numbers\":\"one,two\"," +
160+
"\"numbers\":\"One,Two\"," +
160161
"\"testNamingEnum\":\"Item2:SubItem1\"," +
161162
"\"mobilePhone\":null," +
162163
"\"accountEnabled\":false," +
@@ -205,7 +206,7 @@ public void WritesEnumValuesAsCamelCasedIfNotEscaped()
205206

206207
// Assert
207208
var expectedString = "[{" +
208-
"\"testNamingEnum\":\"item1\"" + // Camel Cased
209+
"\"testNamingEnum\":\"Item1\"" + // Camel Cased
209210
"}]";
210211
Assert.Equal(expectedString, serializedJsonString);
211212
}
@@ -258,6 +259,68 @@ public void WriteGuidUsingConverter()
258259
Assert.Equal(expectedString, serializedJsonString);
259260
}
260261

262+
[Fact]
263+
public void ForwardsOptionsToWriterFromSerializationContext()
264+
{
265+
// Arrange
266+
var testEntity = new TestEntity
267+
{
268+
Id = "testId",
269+
AdditionalData = new Dictionary<string, object>()
270+
{
271+
{"href", "https://graph.microsoft.com/users/{user-id}"},
272+
{"unicodeName", "你好"}
273+
}
274+
};
275+
var serializerOptions = new JsonSerializerOptions(JsonSerializerDefaults.General)
276+
{
277+
WriteIndented = true,
278+
Encoder = JavaScriptEncoder.UnsafeRelaxedJsonEscaping
279+
};
280+
var serializationContext = new KiotaJsonSerializationContext(serializerOptions);
281+
using var jsonSerializerWriter = new JsonSerializationWriter(serializationContext);
282+
283+
// Act
284+
jsonSerializerWriter.WriteObjectValue(string.Empty, testEntity);
285+
var serializedStream = jsonSerializerWriter.GetSerializedContent();
286+
using var reader = new StreamReader(serializedStream, Encoding.UTF8);
287+
var serializedJsonString = reader.ReadToEnd();
288+
289+
// Assert
290+
const string expectedString = "{\n \"id\": \"testId\",\n \"href\": \"https://graph.microsoft.com/users/{user-id}\",\n \"unicodeName\": \"你好\"\n}";
291+
Assert.Contains("\n", serializedJsonString); // string is indented and not escaped
292+
Assert.Contains("你好", serializedJsonString); // string is indented and not escaped
293+
Assert.Equal(expectedString, serializedJsonString.Replace("\r", string.Empty)); // string is indented and not escaped
294+
}
295+
296+
[Fact]
297+
public void UsesDefaultOptionsToWriterFromSerializationContext()
298+
{
299+
// Arrange
300+
var testEntity = new TestEntity
301+
{
302+
Id = "testId",
303+
AdditionalData = new Dictionary<string, object>()
304+
{
305+
{"href", "https://graph.microsoft.com/users/{user-id}"},
306+
{"unicodeName", "你好"}
307+
}
308+
};
309+
using var jsonSerializerWriter = new JsonSerializationWriter(new KiotaJsonSerializationContext());
310+
311+
// Act
312+
jsonSerializerWriter.WriteObjectValue(string.Empty, testEntity);
313+
var serializedStream = jsonSerializerWriter.GetSerializedContent();
314+
using var reader = new StreamReader(serializedStream, Encoding.UTF8);
315+
var serializedJsonString = reader.ReadToEnd();
316+
317+
// Assert
318+
var expectedString = $"{{\"id\":\"testId\",\"href\":\"https://graph.microsoft.com/users/{{user-id}}\",\"unicodeName\":\"\\u4F60\\u597D\"}}";
319+
Assert.DoesNotContain("\n", serializedJsonString); // string is not indented and not escaped
320+
Assert.DoesNotContain("你好", serializedJsonString); // string is not indented and not escaped
321+
Assert.Contains("\\u4F60\\u597D", serializedJsonString); // string is not indented and not escaped
322+
Assert.Equal(expectedString, serializedJsonString); // string is indented and not escaped
323+
}
261324
[Fact]
262325
public void WriteGuidUsingNoConverter()
263326
{
Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,8 @@
1-
using System.Runtime.Serialization;
2-
3-
namespace Microsoft.Kiota.Serialization.Text.Tests.Mocks
1+
namespace Microsoft.Kiota.Serialization.Text.Tests.Mocks
42
{
53
public enum TestEnum
64
{
7-
[EnumMember(Value = "Value_1")]
85
FirstItem,
9-
[EnumMember(Value = "Value_2")]
106
SecondItem,
117
}
128
}

0 commit comments

Comments
 (0)