Skip to content

Commit

Permalink
Merge pull request #312 from microsoft/andrueastman/fixes
Browse files Browse the repository at this point in the history
Aligns Enum serialization across packages and pass relevant JsonWriterOptions from the KiotaJsonSerializationContext
  • Loading branch information
baywet authored Aug 1, 2024
2 parents bc0404d + b531e35 commit 32c1353
Show file tree
Hide file tree
Showing 13 changed files with 183 additions and 41 deletions.
5 changes: 5 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,11 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

## [Unreleased]

## [1.10.1] - 2024-08-01

- Cleans up enum serialization to read from attributes for form and text serialization [#284](https://github.com/microsoft/kiota-dotnet/issues/284)
- 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)

## [1.10.0] - 2024-07-17

- Adds Kiota bundle package to provide default adapter with registrations setup. [#290](https://github.com/microsoft/kiota-dotnet/issues/290)
Expand Down
2 changes: 1 addition & 1 deletion Directory.Build.props
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
<Project>
<!-- Common default project properties for ALL projects-->
<PropertyGroup>
<VersionPrefix>1.10.0</VersionPrefix>
<VersionPrefix>1.10.1</VersionPrefix>
<VersionSuffix></VersionSuffix>
<!-- This is overidden in test projects by setting to true-->
<IsTestProject>false</IsTestProject>
Expand Down
25 changes: 25 additions & 0 deletions src/abstractions/Helpers/EnumHelpers.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
using System;
using System.Reflection;
using System.Runtime.Serialization;
using Microsoft.Kiota.Abstractions.Extensions;

#if NET5_0_OR_GREATER
using System.Diagnostics.CodeAnalysis;
Expand Down Expand Up @@ -154,5 +155,29 @@ private static bool TryGetFieldValueName(Type type, string rawValue, out string
}
return false;
}

/// <summary>
/// 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.
/// </summary>
/// <typeparam name="T">The Enum type</typeparam>
/// <param name="value">The enum value</param>
/// <returns></returns>
/// <exception cref="ArgumentException">If value is null</exception>
#if NET5_0_OR_GREATER
public static string? GetEnumStringValue<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicFields)] T>(T value) where T : struct, Enum
#else
public static string? GetEnumStringValue<T>(T value) where T : struct, Enum
#endif
{
var type = typeof(T);

if(Enum.GetName(type, value) is not { } name)
throw new ArgumentException($"Invalid Enum value {value} for enum of type {type}");

if(type.GetField(name)?.GetCustomAttribute<EnumMemberAttribute>() is { } attribute)
return attribute.Value;

return name;
}
}
}
11 changes: 6 additions & 5 deletions src/serialization/form/FormSerializationWriter.cs
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
#endif
using System;
using System.Collections.Generic;
using Microsoft.Kiota.Abstractions.Helpers;

namespace Microsoft.Kiota.Serialization.Form;
/// <summary>Represents a serialization writer that can be used to write a form url encoded string.</summary>
Expand Down Expand Up @@ -247,13 +248,13 @@ public void WriteCollectionOfEnumValues<T>(string? key, IEnumerable<T?>? values)
StringBuilder? valueNames = null;
foreach(var x in values)
{
if(x.HasValue && Enum.GetName(typeof(T), x.Value) is string valueName)
if(x.HasValue && EnumHelpers.GetEnumStringValue(x.Value) is string valueName)
{
if(valueNames == null)
valueNames = new StringBuilder();
else
valueNames.Append(",");
valueNames.Append(valueName.ToFirstCharacterLowerCase());
valueNames.Append(valueName);
}
}

Expand Down Expand Up @@ -281,16 +282,16 @@ public void WriteEnumValue<T>(string? key, T? value) where T : struct, Enum
StringBuilder valueNames = new StringBuilder();
foreach(var x in values)
{
if(value.Value.HasFlag(x) && Enum.GetName(typeof(T), x) is string valueName)
if(value.Value.HasFlag(x) && EnumHelpers.GetEnumStringValue(x) is string valueName)
{
if(valueNames.Length > 0)
valueNames.Append(",");
valueNames.Append(valueName.ToFirstCharacterLowerCase());
valueNames.Append(valueName);
}
}
WriteStringValue(key, valueNames.ToString());
}
else WriteStringValue(key, value.Value.ToString().ToFirstCharacterLowerCase());
else WriteStringValue(key, EnumHelpers.GetEnumStringValue(value.Value));
}
}
}
28 changes: 8 additions & 20 deletions src/serialization/json/JsonSerializationWriter.cs
Original file line number Diff line number Diff line change
Expand Up @@ -7,12 +7,11 @@
using System.Collections.Generic;
using System.IO;
using System.Reflection;
using System.Runtime.Serialization;
using System.Text;
using System.Text.Json;
using System.Xml;
using Microsoft.Kiota.Abstractions;
using Microsoft.Kiota.Abstractions.Extensions;
using Microsoft.Kiota.Abstractions.Helpers;
using Microsoft.Kiota.Abstractions.Serialization;

#if NET5_0_OR_GREATER
Expand Down Expand Up @@ -49,7 +48,11 @@ public JsonSerializationWriter()
public JsonSerializationWriter(KiotaJsonSerializationContext kiotaJsonSerializationContext)
{
_kiotaJsonSerializationContext = kiotaJsonSerializationContext;
writer = new Utf8JsonWriter(_stream);
writer = new Utf8JsonWriter(_stream, new JsonWriterOptions
{
Encoder = kiotaJsonSerializationContext.Options.Encoder,
Indented = kiotaJsonSerializationContext.Options.WriteIndented
});
}

/// <summary>
Expand Down Expand Up @@ -290,7 +293,7 @@ public void WriteEnumValue<T>(string? key, T? value) where T : struct, Enum
StringBuilder valueNames = new StringBuilder();
foreach(var x in values)
{
if(value.Value.HasFlag(x) && GetEnumName(x) is string valueName)
if(value.Value.HasFlag(x) && EnumHelpers.GetEnumStringValue(x) is string valueName)
{
if(valueNames.Length > 0)
valueNames.Append(",");
Expand All @@ -299,7 +302,7 @@ public void WriteEnumValue<T>(string? key, T? value) where T : struct, Enum
}
WriteStringValue(null, valueNames.ToString());
}
else WriteStringValue(null, GetEnumName(value.Value));
else WriteStringValue(null, EnumHelpers.GetEnumStringValue(value.Value));
}
}

Expand Down Expand Up @@ -559,22 +562,7 @@ public void Dispose()
writer.Dispose();
GC.SuppressFinalize(this);
}
#if NET5_0_OR_GREATER
private static string? GetEnumName<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicFields)] T>(T value) where T : struct, Enum
#else
private static string? GetEnumName<T>(T value) where T : struct, Enum
#endif
{
var type = typeof(T);

if(Enum.GetName(type, value) is not { } name)
throw new ArgumentException($"Invalid Enum value {value} for enum of type {type}");

if(type.GetField(name)?.GetCustomAttribute<EnumMemberAttribute>() is { } attribute)
return attribute.Value;

return name.ToFirstCharacterLowerCase();
}
/// <summary>
/// Writes a untyped value for the specified key.
/// </summary>
Expand Down
3 changes: 2 additions & 1 deletion src/serialization/text/TextSerializationWriter.cs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
using System.Xml;
using Microsoft.Kiota.Abstractions;
using Microsoft.Kiota.Abstractions.Extensions;
using Microsoft.Kiota.Abstractions.Helpers;
using Microsoft.Kiota.Abstractions.Serialization;

#if NET5_0_OR_GREATER
Expand Down Expand Up @@ -118,5 +119,5 @@ public void WriteCollectionOfEnumValues<T>(string? key, IEnumerable<T?>? values)
#else
public void WriteEnumValue<T>(string? key, T? value) where T : struct, Enum
#endif
=> WriteStringValue(key, value.HasValue ? value.Value.ToString().ToFirstCharacterLowerCase() : null);
=> WriteStringValue(key, value.HasValue ? EnumHelpers.GetEnumStringValue(value.Value) : null);
}
24 changes: 21 additions & 3 deletions tests/serialization/form/FormSerializationWriterTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ public void WritesSampleObjectValue()

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

// Assert
Assert.Equal("prop1=sixteen", serializedString);
Assert.Equal("prop1=Sixteen", serializedString);
}

[Fact]
public void WriteEnumValueWithAttribute_IsWrittenCorrectly()
{
// Arrange
var value = TestNamingEnum.Item2SubItem1;

using var formSerializationWriter = new FormSerializationWriter();

// Act
formSerializationWriter.WriteEnumValue<TestNamingEnum>("prop1", value);
var contentStream = formSerializationWriter.GetSerializedContent();
using var reader = new StreamReader(contentStream, Encoding.UTF8);
var serializedString = reader.ReadToEnd();

// Assert
Assert.Equal("prop1=Item2%3ASubItem1", serializedString);
}

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

// Assert
Assert.Equal("prop1=sixteen%2Ctwo", serializedString);
Assert.Equal("prop1=Sixteen%2CTwo", serializedString);
}
}
13 changes: 13 additions & 0 deletions tests/serialization/form/Mocks/TestNamingEnum.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
using System.Runtime.Serialization;

namespace Microsoft.Kiota.Serialization.Form.Tests.Mocks
{
public enum TestNamingEnum
{
Item1,
[EnumMember(Value = "Item2:SubItem1")]
Item2SubItem1,
[EnumMember(Value = "Item3:SubItem1")]
Item3SubItem1
}
}
67 changes: 65 additions & 2 deletions tests/serialization/json/JsonSerializationWriterTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
using System.Globalization;
using System.IO;
using System.Text;
using System.Text.Encodings.Web;
using System.Text.Json;
using System.Threading;
using Microsoft.Kiota.Abstractions;
Expand Down Expand Up @@ -156,7 +157,7 @@ public void WritesSampleCollectionOfObjectValues()
// Assert
var expectedString = "[{" +
"\"id\":\"48d31887-5fad-4d73-a9f5-3c356e68a038\"," +
"\"numbers\":\"one,two\"," +
"\"numbers\":\"One,Two\"," +
"\"testNamingEnum\":\"Item2:SubItem1\"," +
"\"mobilePhone\":null," +
"\"accountEnabled\":false," +
Expand Down Expand Up @@ -205,7 +206,7 @@ public void WritesEnumValuesAsCamelCasedIfNotEscaped()

// Assert
var expectedString = "[{" +
"\"testNamingEnum\":\"item1\"" + // Camel Cased
"\"testNamingEnum\":\"Item1\"" + // Camel Cased
"}]";
Assert.Equal(expectedString, serializedJsonString);
}
Expand Down Expand Up @@ -258,6 +259,68 @@ public void WriteGuidUsingConverter()
Assert.Equal(expectedString, serializedJsonString);
}

[Fact]
public void ForwardsOptionsToWriterFromSerializationContext()
{
// Arrange
var testEntity = new TestEntity
{
Id = "testId",
AdditionalData = new Dictionary<string, object>()
{
{"href", "https://graph.microsoft.com/users/{user-id}"},
{"unicodeName", "你好"}
}
};
var serializerOptions = new JsonSerializerOptions(JsonSerializerDefaults.General)
{
WriteIndented = true,
Encoder = JavaScriptEncoder.UnsafeRelaxedJsonEscaping
};
var serializationContext = new KiotaJsonSerializationContext(serializerOptions);
using var jsonSerializerWriter = new JsonSerializationWriter(serializationContext);

// Act
jsonSerializerWriter.WriteObjectValue(string.Empty, testEntity);
var serializedStream = jsonSerializerWriter.GetSerializedContent();
using var reader = new StreamReader(serializedStream, Encoding.UTF8);
var serializedJsonString = reader.ReadToEnd();

// Assert
const string expectedString = "{\n \"id\": \"testId\",\n \"href\": \"https://graph.microsoft.com/users/{user-id}\",\n \"unicodeName\": \"你好\"\n}";
Assert.Contains("\n", serializedJsonString); // string is indented and not escaped
Assert.Contains("你好", serializedJsonString); // string is indented and not escaped
Assert.Equal(expectedString, serializedJsonString.Replace("\r", string.Empty)); // string is indented and not escaped
}

[Fact]
public void UsesDefaultOptionsToWriterFromSerializationContext()
{
// Arrange
var testEntity = new TestEntity
{
Id = "testId",
AdditionalData = new Dictionary<string, object>()
{
{"href", "https://graph.microsoft.com/users/{user-id}"},
{"unicodeName", "你好"}
}
};
using var jsonSerializerWriter = new JsonSerializationWriter(new KiotaJsonSerializationContext());

// Act
jsonSerializerWriter.WriteObjectValue(string.Empty, testEntity);
var serializedStream = jsonSerializerWriter.GetSerializedContent();
using var reader = new StreamReader(serializedStream, Encoding.UTF8);
var serializedJsonString = reader.ReadToEnd();

// Assert
var expectedString = $"{{\"id\":\"testId\",\"href\":\"https://graph.microsoft.com/users/{{user-id}}\",\"unicodeName\":\"\\u4F60\\u597D\"}}";
Assert.DoesNotContain("\n", serializedJsonString); // string is not indented and not escaped
Assert.DoesNotContain("你好", serializedJsonString); // string is not indented and not escaped
Assert.Contains("\\u4F60\\u597D", serializedJsonString); // string is not indented and not escaped
Assert.Equal(expectedString, serializedJsonString); // string is indented and not escaped
}
[Fact]
public void WriteGuidUsingNoConverter()
{
Expand Down
6 changes: 1 addition & 5 deletions tests/serialization/text/Mocks/TestEnum.cs
Original file line number Diff line number Diff line change
@@ -1,12 +1,8 @@
using System.Runtime.Serialization;

namespace Microsoft.Kiota.Serialization.Text.Tests.Mocks
namespace Microsoft.Kiota.Serialization.Text.Tests.Mocks
{
public enum TestEnum
{
[EnumMember(Value = "Value_1")]
FirstItem,
[EnumMember(Value = "Value_2")]
SecondItem,
}
}
13 changes: 13 additions & 0 deletions tests/serialization/text/Mocks/TestNamingEnum.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
using System.Runtime.Serialization;

namespace Microsoft.Kiota.Serialization.Text.Tests.Mocks
{
public enum TestNamingEnum
{
Item1,
[EnumMember(Value = "Item2:SubItem1")]
Item2SubItem1,
[EnumMember(Value = "Item3:SubItem1")]
Item3SubItem1
}
}
6 changes: 3 additions & 3 deletions tests/serialization/text/TextParseNodeTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -41,12 +41,12 @@ public void TextParseNode_GetEnumFromString()
[Fact]
public void TextParseNode_GetEnumFromEnumMember()
{
var text = "Value_2";
var text = "Item2:SubItem1";
var parseNode = new TextParseNode(text);

var result = parseNode.GetEnumValue<TestEnum>();
var result = parseNode.GetEnumValue<TestNamingEnum>();

Assert.Equal(TestEnum.SecondItem, result);
Assert.Equal(TestNamingEnum.Item2SubItem1, result);
}

[Fact]
Expand Down
Loading

0 comments on commit 32c1353

Please sign in to comment.