Skip to content

Commit

Permalink
Merge pull request #29 from VelvetToroyashi/velvet/feat/dictionary-en…
Browse files Browse the repository at this point in the history
…um-keys

Add support for enum dictionary keys
  • Loading branch information
Nihlus authored Mar 19, 2024
2 parents b4ab622 + aec822e commit c4b6a44
Show file tree
Hide file tree
Showing 2 changed files with 174 additions and 1 deletion.
21 changes: 20 additions & 1 deletion Remora.Rest/Json/StringEnumConverter.cs
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@ public override TEnum Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSe

switch (reader.TokenType)
{
case JsonTokenType.String:
case JsonTokenType.String or JsonTokenType.PropertyName:
{
var value = reader.GetString();
if (value is null)
Expand Down Expand Up @@ -117,4 +117,23 @@ public override void Write(Utf8JsonWriter writer, TEnum value, JsonSerializerOpt

writer.WriteStringValue(_enumsToNames[value]);
}

/// <inheritdoc />
public override TEnum ReadAsPropertyName(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
=> this.Read(ref reader, typeToConvert, options);

/// <inheritdoc/>
public override void WriteAsPropertyName(Utf8JsonWriter writer, TEnum value, JsonSerializerOptions options)
{
if (_asInteger)
{
writer.WritePropertyName(Enum.GetUnderlyingType(typeof(TEnum)).IsUnsigned()
? Convert.ToUInt64(value).ToString()
: Convert.ToInt64(value).ToString());

return;
}

writer.WritePropertyName(_enumsToNames[value]);
}
}
154 changes: 154 additions & 0 deletions Tests/Remora.Rest.Tests/Tests/Json/StringEnumConverterTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,154 @@
//
// SPDX-FileName: StringEnumConverterTests.cs
// SPDX-FileCopyrightText: Copyright (c) Jarl Gullberg
// SPDX-License-Identifier: LGPL-3.0-or-later
//

using System.Collections.Generic;
using System.Text.Json;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Options;
using Remora.Rest.Json;
using Remora.Rest.Json.Policies;
using Remora.Rest.Tests.Data.DataObjects;
using Xunit;

namespace Remora.Rest.Tests.Json;

/// <summary>
/// Tests for <see cref="StringEnumConverter{TEnum}"/>.
/// </summary>
public class StringEnumConverterTests
{
/// <summary>
/// Tests that the converter can serialize a dictionary where the key is an enum.
/// </summary>
[Fact]
public void CanSerializeDictionaryKeyAsInteger()
{
// Arrange
var services = new ServiceCollection()
.Configure<JsonSerializerOptions>
(
json =>
{
json.PropertyNamingPolicy = new SnakeCaseNamingPolicy();
json.Converters.Add(new StringEnumConverter<StringifiedEnum>(asInteger: true));
})
.BuildServiceProvider();

var dictionary = new Dictionary<StringifiedEnum, string>
{
{ StringifiedEnum.First, "first" },
{ StringifiedEnum.Second, "second" },
{ StringifiedEnum.Third, "third" },
};

var jsonOptions = services.GetRequiredService<IOptions<JsonSerializerOptions>>().Value;

// Act
var result = JsonSerializer.Serialize(dictionary, jsonOptions);
var expected = "{\"0\":\"first\",\"1\":\"second\",\"2\":\"third\"}";

// Assert
Assert.Equal(expected, result);
}

/// <summary>
/// Tests that the converter can serialize a dictionary where the key is an enum.
/// </summary>
[Fact]
public void CanSerializeDictionaryKeyAsString()
{
// Arrange
var services = new ServiceCollection()
.Configure<JsonSerializerOptions>
(
json =>
{
json.PropertyNamingPolicy = new SnakeCaseNamingPolicy();
json.Converters.Add(new StringEnumConverter<StringifiedEnum>(json.PropertyNamingPolicy));
})
.BuildServiceProvider();

var dictionary = new Dictionary<StringifiedEnum, string>
{
{ StringifiedEnum.First, "first" },
{ StringifiedEnum.Second, "second" },
{ StringifiedEnum.Third, "third" },
};

var jsonOptions = services.GetRequiredService<IOptions<JsonSerializerOptions>>().Value;

// Act
var result = JsonSerializer.Serialize(dictionary, jsonOptions);
var expected = "{\"first\":\"first\",\"second\":\"second\",\"third\":\"third\"}";

// Assert
Assert.Equal(expected, result);
}

/// <summary>
/// Tests that the converter can read a dictionary where the key is an enum.
/// </summary>
[Fact]
public void CanDeserializeDictionaryKeyAsInteger()
{
// Arrange
var services = new ServiceCollection()
.Configure<JsonSerializerOptions>
(
json =>
{
json.PropertyNamingPolicy = new SnakeCaseNamingPolicy();
json.Converters.Add(new StringEnumConverter<StringifiedEnum>(asInteger: true));
})
.BuildServiceProvider();

var jsonOptions = services.GetRequiredService<IOptions<JsonSerializerOptions>>().Value;

var json = "{\"0\":\"first\",\"1\":\"second\",\"2\":\"third\"}";

// Act
var result = JsonSerializer.Deserialize<Dictionary<StringifiedEnum, string>>(json, jsonOptions);

// Assert
Assert.NotNull(result);
Assert.Equal(3, result.Count);
Assert.Equal("first", result[StringifiedEnum.First]);
Assert.Equal("second", result[StringifiedEnum.Second]);
Assert.Equal("third", result[StringifiedEnum.Third]);
}

/// <summary>
/// Tests that the converter can read a dictionary where the key is an enum.
/// </summary>
[Fact]
public void CanDeserializeDictionaryKeyAsString()
{
// Arrange
var services = new ServiceCollection()
.Configure<JsonSerializerOptions>
(
json =>
{
json.PropertyNamingPolicy = new SnakeCaseNamingPolicy();
json.Converters.Add(new StringEnumConverter<StringifiedEnum>(json.PropertyNamingPolicy));
})
.BuildServiceProvider();

var jsonOptions = services.GetRequiredService<IOptions<JsonSerializerOptions>>().Value;

var json = "{\"first\":\"first\",\"second\":\"second\",\"third\":\"third\"}";

// Act
var result = JsonSerializer.Deserialize<Dictionary<StringifiedEnum, string>>(json, jsonOptions);

// Assert
Assert.NotNull(result);
Assert.Equal(3, result.Count);
Assert.Equal("first", result[StringifiedEnum.First]);
Assert.Equal("second", result[StringifiedEnum.Second]);
Assert.Equal("third", result[StringifiedEnum.Third]);
}
}

0 comments on commit c4b6a44

Please sign in to comment.