dotnet / dotnet/runtime

[API Proposal]: allow multiple JsonStringEnumMemberNameAttribute Names

Open
#132,642 6 comments 0 reactions 0 assignees View on GitHub
api-suggestion area-System.Text.Json
Dominant language
C#
Stars
18.3k
Forks
5.6k
PR merge metrics
PR metrics pending

Description

### Background and motivation

**Background**
Using `JsonStringEnumMemberNameAttribute`, I want to deserialize multiple different strings to a single Enum member. Right now, only one `JsonStringEnumMemberName` can be used per enum member. It's very natural to want to do this:
```csharp
enum TranportationMode
{
Truck,
Ocean,
[JsonStringEnumMemberName("Air Freight")]
[JsonStringEnumMemberName("Air Cargo")] //This throws an error, as AllowMultiple = false
AirCargo
}
```
This would work for deserialization, as there's no indeterminism to worry about. But for serialization, we're missing a way to set a "primary" string value.

**Motivation:**
Here are some reasons why one would want multiple strings to deserialize to a single enum member.
- Keeping backwards deserialization compatibility when changing the representative string value of the enum member.
- Removes the need to write and hook up a custom json serializer
- Useful in mapping from one set of string enums to another set of enums.
- Especially useful in the context of .net api model binding.

### API Proposal

Add a `params string[] names` constructor to `JsonStringEnumMemberNameAttribute`.

```diff
[AttributeUsage(AttributeTargets.Field, AllowMultiple = false)]
public class JsonStringEnumMemberNameAttribute : JsonAttribute
{
public string Name { get; }

+ public string[]? AlternateNames { get; init; }

public JsonStringEnumMemberNameAttribute (string name)
{
Name = name;
}

+ public JsonStringEnumMemberNameAttribute (params string[] names)
+ {
+ Name = names[0];
+ AlternateNames = names[1..^0];
+ }
}
```

### API Usage

```csharp
enum TransportationMode
{
Truck,
Ocean,
[JsonStringEnumMemberName("Air Cargo", "Air Freight")]
AirCargo
}
```
When deserializing a string to enum, the deserializer would check all names in the list for a match. When serializing, it would take the first item in the list, so "Air Cargo" here. Runtime/aot validation (or an analyzer) would throw if there are any overlaps of string names for different enum members.

Alternative usage if additional clarity is desired
```csharp
enum TransportationMode
{
Truck,
Ocean,
[JsonStringEnumMemberName("Air Cargo", AlternateNames = ["Air Freight"])]
AirCargo
}
```

### Alternative Designs

Not as clean, but could still work:
Change the attribute to allow multiple. It's very natural for a dev to try this. Serialization is nondeterministic because attribute order is not guaranteed. Could add an optional `Primary` flag for serialization.
```csharp
enum TranportationMode
{
Truck,
Ocean,
[JsonStringEnumMemberName("Air Freight", Primary = true)]
[JsonStringEnumMemberName("Air Cargo")]
AirCargo
}
```

### Risks

Backwards compatibility is maintained since the single `string` constructor in use today takes precedence over the `params string[]` constructor.

Contributor guide

Open the contributing guide

Research direction

The issue names JsonStringEnumMemberNameAttribute and enum serialization/deserialization as the entry points; begin by locating the attribute and current enum name handling. Confirm the API shape and serialization behavior, then add coverage for alternate names and overlapping-name validation if the design is accepted.

Written by the indexing model from the issue text.

Assessment

Tech stack
csharp
Domain
backend-api-design
Issue type
Feature
Difficulty
5/5
Estimated time
Over a week
Activity status
Active
Clarity
Mostly clear
Newbie friendliness
38/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.