dotnet / runtime

.NET is a cross-platform runtime for cloud, mobile, desktop, and IoT apps.
https://docs.microsoft.com/dotnet/core/
MIT License
15.25k stars 4.73k forks source link

JsonStringEnumMemberNameAttribute does not support empty strings. #107367

Open Drake53 opened 1 month ago

Drake53 commented 1 month ago

Description

For my use case I have the following third party XSD (simplified) for which I generate an enum:

<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema">
    <xs:simpleType name="YesOrNoOrEmpty">
        <xs:restriction base="xs:string">
            <xs:enumeration value="y" />
            <xs:enumeration value="n" />
            <xs:enumeration value="" />
        </xs:restriction>
    </xs:simpleType>
</xs:schema>

I want the generated enum to be serializable in both XML and JSON, so I annotate the members with both XmlEnumAttribute as well as the new JsonStringEnumMemberNameAttribute from System.Text.Json v9-preview.

For XML this works fine, but with JSON I get an exception, because empty strings are not allowed. I assume this might have something to do with support for Flags enums? But for enums without FlagsAttribute this restriction should not apply.

Reproduction Steps

using System.Text.Json.Serialization;
using System.Xml.Serialization;

public enum YesOrNoOrEmpty
{
    [JsonStringEnumMemberName("y")]
    [XmlEnum("y")]
    Yes,

    [JsonStringEnumMemberName("n")]
    [XmlEnum("n")]
    No,

    [JsonStringEnumMemberName("")]
    [XmlEnum("")]
    Empty,
}

Expected behavior

JsonStringEnumConverter is able to serialize and deserialize enums where the string representation of one of the enum members is an empty string. This matches behaviour of System.Xml's XmlEnumAttribute, and EnumMemberAttribute which is used by Newtonsoft.Json.

Actual behavior

JsonStringEnumConverter (using EnumConverter internally) does a string.IsNullOrEmpty check and throws an InvalidOperationException: Enum type '{0}' uses unsupported identifier '{1}'. It must not be null, empty, or containing leading or trailing whitespace. Flags enums must additionally not contain commas.

Regression?

No response

Known Workarounds

Implementing a custom JsonConverter for enums

Configuration

No response

Other information

No response

dotnet-policy-service[bot] commented 1 month ago

Tagging subscribers to this area: @dotnet/area-system-text-json, @gregsdennis See info in area-owners.md if you want to be subscribed.

eiriktsarpalis commented 1 month ago

There's nothing technically preventing the use of empty strings (even in the case of flags, where you might be able to write something like y, , n). It got added intentionally principally to avoid pathological outputs (like the one mentioned above) and discourage it from being used.

We might consider relaxing that restriction in the future, provided there is sufficient demand for it.

Drake53 commented 1 month ago

There's nothing technically preventing the use of empty strings (even in the case of flags, where you might be able to write something like y, , n).

It's true the example you give could work, even though it looks a bit weird, but the problem is that the empty string in json will become ambiguous, because it can be either no flags, or only the empty string member's flag:

[Flags]
public enum FlagsEnum
{
    [JsonStringEnumMemberName("")]
    Empty = 1,
    [JsonStringEnumMemberName("1")]
    One = 2,
}

JsonSerializer.Serialize(FlagsEnum.Empty); // returns ""
JsonSerializer.Serialize((FlagsEnum)0); // returns ""

JsonSerializer.Deserialize<FlagsEnum>(""); // ambiguous
IvanJosipovic commented 1 month ago

I would also like the library to support [JsonStringEnumMemberName("")] and [JsonStringEnumMemberName(null)]

public enum EnumMemberedEnum
{
    No = 0,

    [System.Text.Json.Serialization.JsonStringEnumMemberName("goodbye")]
    Hello = 1,

    [System.Text.Json.Serialization.JsonStringEnumMemberName("")]
    EmptyValue = 2,

    [System.Text.Json.Serialization.JsonStringEnumMemberName(null)]
    NullValue = 3
}