Skip to content

Commit

Permalink
Moved DictionaryKeyPolicy parsing code to WriteWithQuotes [dotnet#47765]
Browse files Browse the repository at this point in the history
  • Loading branch information
SkiFoD committed Jul 1, 2021
1 parent a06986b commit a6aebec
Show file tree
Hide file tree
Showing 2 changed files with 103 additions and 13 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -25,10 +25,14 @@ internal sealed class EnumConverter<T> : JsonConverter<T>

private readonly ConcurrentDictionary<ulong, JsonEncodedText> _nameCache;

private readonly Lazy<ConcurrentDictionary<ulong, JsonEncodedText>> _dictionaryKeyPolicyCache = new Lazy<ConcurrentDictionary<ulong, JsonEncodedText>>();

// This is used to prevent flooding the cache due to exponential bitwise combinations of flags.
// Since multiple threads can add to the cache, a few more values might be added.
private const int NameCacheSizeSoftLimit = 64;

private const int DictionaryKeyPolicyCacheSizeSoftLimit = 64;

public override bool CanConvert(Type type)
{
return type.IsEnum;
Expand Down Expand Up @@ -60,16 +64,7 @@ public EnumConverter(EnumConverterOptions converterOptions, JsonNamingPolicy? na

T value = (T)values.GetValue(i)!;
ulong key = ConvertToUInt64(value);
string name;

if (serializerOptions.DictionaryKeyPolicy != null)
{
name = serializerOptions.DictionaryKeyPolicy.ConvertName(names[i]);
}
else
{
name = names[i];
}
string name = names[i];

_nameCache.TryAdd(
key,
Expand Down Expand Up @@ -334,13 +329,56 @@ internal override void WriteWithQuotes(Utf8JsonWriter writer, T value, JsonSeria

ulong key = ConvertToUInt64(value);

if (_nameCache.TryGetValue(key, out JsonEncodedText formatted))
JsonEncodedText formatted;
string original;

if (options.DictionaryKeyPolicy != null && !state.Current.IgnoreDictionaryKeyPolicy)
{
if (_dictionaryKeyPolicyCache.Value.TryGetValue(key, out formatted))
{
writer.WritePropertyName(formatted);
return;
}

original = value.ToString();

if (IsValidIdentifier(original))
{
original = options.DictionaryKeyPolicy.ConvertName(original);

if (original == null)
{
ThrowHelper.ThrowInvalidOperationException_NamingPolicyReturnNull(options.DictionaryKeyPolicy);
}

if (_dictionaryKeyPolicyCache.Value.Count < DictionaryKeyPolicyCacheSizeSoftLimit)
{
JavaScriptEncoder? encoder = options.Encoder;

formatted = JsonEncodedText.Encode(original, encoder);

writer.WritePropertyName(formatted);

_dictionaryKeyPolicyCache.Value.TryAdd(key, formatted);
}
else
{
// We also do not create a JsonEncodedText instance here because passing the string
// directly to the writer is cheaper than creating one and not caching it for reuse.
writer.WritePropertyName(original);
}

return;
}
}

if (_nameCache.TryGetValue(key, out formatted))
{
writer.WritePropertyName(formatted);
return;
}

string original = value.ToString();
original = value.ToString();
if (IsValidIdentifier(original))
{
// We are dealing with a combination of flag constants since
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -189,7 +189,7 @@ public enum ETestEnum
TestValue1 = 1,
TestValue2 = 2,
}

[Fact]
public static void EnumSerialization_DictionaryPolicy_Honored_CamelCase()
{
Expand Down Expand Up @@ -227,6 +227,58 @@ public static void EnumSerialization_DictionaryPolicy_Honored_None()
Assert.Equal("{\"TestValue1\":1,\"TestValue2\":2}", value);
}

public class ClassWithEnumProperties
{
public ETestEnum TestEnumProperty1 { get; } = ETestEnum.TestValue2;
public DayOfWeek TestEnumProperty2 { get; } = DayOfWeek.Monday;
}

[Fact]
public static void EnumSerialization_DictionaryPolicy_NotApplied_WhenEnumsAreSerialized()
{
var options = new JsonSerializerOptions
{
DictionaryKeyPolicy = JsonNamingPolicy.CamelCase,
};

string value = JsonSerializer.Serialize(DayOfWeek.Friday, options);

Assert.Equal("5", value);

value = JsonSerializer.Serialize(ETestEnum.TestValue2, options);

Assert.Equal("2", value);


value = JsonSerializer.Serialize(new ClassWithEnumProperties(), options);

Assert.Equal("{\"TestEnumProperty1\":2,\"TestEnumProperty2\":1}", value);

value = JsonSerializer.Serialize(new List<DayOfWeek> { DayOfWeek.Sunday, DayOfWeek.Monday, DayOfWeek.Tuesday, DayOfWeek.Wednesday, DayOfWeek.Thursday, DayOfWeek.Friday, DayOfWeek.Saturday}, options);

Assert.Equal("[0,1,2,3,4,5,6]", value);
}

public class CustomJsonNamingPolicy : JsonNamingPolicy
{
public override string ConvertName(string name) => null;
}

[Fact]
public static void EnumSerialization_DictionaryPolicy_ThrowsException_WhenNamingPolicyReturnsNull()
{
var options = new JsonSerializerOptions
{
DictionaryKeyPolicy = new CustomJsonNamingPolicy(),
};

Dictionary<ETestEnum, ETestEnum> dict = new Dictionary<ETestEnum, ETestEnum> { [ETestEnum.TestValue1] = ETestEnum.TestValue1 };

InvalidOperationException ex = Assert.Throws<InvalidOperationException>(() => JsonSerializer.Serialize(dict, options));

Assert.Contains(typeof(CustomJsonNamingPolicy).ToString(), ex.Message);
}

[Fact]
public static void NullNamePolicy()
{
Expand Down

0 comments on commit a6aebec

Please sign in to comment.