diff --git a/Remora.Rest/Json/StringEnumConverter.cs b/Remora.Rest/Json/StringEnumConverter.cs index 5cd94ed..eb2ae91 100644 --- a/Remora.Rest/Json/StringEnumConverter.cs +++ b/Remora.Rest/Json/StringEnumConverter.cs @@ -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) @@ -117,4 +117,23 @@ public override void Write(Utf8JsonWriter writer, TEnum value, JsonSerializerOpt writer.WriteStringValue(_enumsToNames[value]); } + + /// + public override TEnum ReadAsPropertyName(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) + => this.Read(ref reader, typeToConvert, options); + + /// + 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]); + } } diff --git a/Tests/Remora.Rest.Tests/Tests/Json/StringEnumConverterTests.cs b/Tests/Remora.Rest.Tests/Tests/Json/StringEnumConverterTests.cs new file mode 100644 index 0000000..a2aabe4 --- /dev/null +++ b/Tests/Remora.Rest.Tests/Tests/Json/StringEnumConverterTests.cs @@ -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; + +/// +/// Tests for . +/// +public class StringEnumConverterTests +{ + /// + /// Tests that the converter can serialize a dictionary where the key is an enum. + /// + [Fact] + public void CanSerializeDictionaryKeyAsInteger() + { + // Arrange + var services = new ServiceCollection() + .Configure + ( + json => + { + json.PropertyNamingPolicy = new SnakeCaseNamingPolicy(); + json.Converters.Add(new StringEnumConverter(asInteger: true)); + }) + .BuildServiceProvider(); + + var dictionary = new Dictionary + { + { StringifiedEnum.First, "first" }, + { StringifiedEnum.Second, "second" }, + { StringifiedEnum.Third, "third" }, + }; + + var jsonOptions = services.GetRequiredService>().Value; + + // Act + var result = JsonSerializer.Serialize(dictionary, jsonOptions); + var expected = "{\"0\":\"first\",\"1\":\"second\",\"2\":\"third\"}"; + + // Assert + Assert.Equal(expected, result); + } + + /// + /// Tests that the converter can serialize a dictionary where the key is an enum. + /// + [Fact] + public void CanSerializeDictionaryKeyAsString() + { + // Arrange + var services = new ServiceCollection() + .Configure + ( + json => + { + json.PropertyNamingPolicy = new SnakeCaseNamingPolicy(); + json.Converters.Add(new StringEnumConverter(json.PropertyNamingPolicy)); + }) + .BuildServiceProvider(); + + var dictionary = new Dictionary + { + { StringifiedEnum.First, "first" }, + { StringifiedEnum.Second, "second" }, + { StringifiedEnum.Third, "third" }, + }; + + var jsonOptions = services.GetRequiredService>().Value; + + // Act + var result = JsonSerializer.Serialize(dictionary, jsonOptions); + var expected = "{\"first\":\"first\",\"second\":\"second\",\"third\":\"third\"}"; + + // Assert + Assert.Equal(expected, result); + } + + /// + /// Tests that the converter can read a dictionary where the key is an enum. + /// + [Fact] + public void CanDeserializeDictionaryKeyAsInteger() + { + // Arrange + var services = new ServiceCollection() + .Configure + ( + json => + { + json.PropertyNamingPolicy = new SnakeCaseNamingPolicy(); + json.Converters.Add(new StringEnumConverter(asInteger: true)); + }) + .BuildServiceProvider(); + + var jsonOptions = services.GetRequiredService>().Value; + + var json = "{\"0\":\"first\",\"1\":\"second\",\"2\":\"third\"}"; + + // Act + var result = JsonSerializer.Deserialize>(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]); + } + + /// + /// Tests that the converter can read a dictionary where the key is an enum. + /// + [Fact] + public void CanDeserializeDictionaryKeyAsString() + { + // Arrange + var services = new ServiceCollection() + .Configure + ( + json => + { + json.PropertyNamingPolicy = new SnakeCaseNamingPolicy(); + json.Converters.Add(new StringEnumConverter(json.PropertyNamingPolicy)); + }) + .BuildServiceProvider(); + + var jsonOptions = services.GetRequiredService>().Value; + + var json = "{\"first\":\"first\",\"second\":\"second\",\"third\":\"third\"}"; + + // Act + var result = JsonSerializer.Deserialize>(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]); + } +}