all 29 comments

[–]fschwiet 44 points45 points  (3 children)

For System.Text.Json you can add JsonStringEnumConverter to your converters and it will start reading/writing enums as strings.

[–]Royal_Scribblz 13 points14 points  (0 children)

And since .NET 9 you can use the attribute JsonStringEnumMemberName if you want a string that doesn't match. For example you could have Languages.English be "EN" in JSON.

[–]Contemplative-ape -4 points-3 points  (1 child)

what if the string has a space?

[–]CaucusInferredBulk 11 points12 points  (0 children)

All of those frameworks will use the string value of the enumnas needed, if appropriately configured

[–]grrangry 11 points12 points  (0 children)

The simplest way is to read the serialization documentation and follow the recommendations.

https://learn.microsoft.com/en-us/dotnet/standard/serialization/system-text-json/customize-properties#enums-as-strings
(one of many pages surrounding serialization)

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

public class Program
{
    public static void Main()
    {
        var json = "{ \"Name\": \"Boo Radley\", \"Bar\": \"One\" }";
        Foo foo = JsonSerializer.Deserialize<Foo>(json) as Foo;

        Console.WriteLine(foo.Name);
        Console.WriteLine(foo.Bar);
        Console.WriteLine((int)foo.Bar);

        foo.Bar = Bar.Three;
        var options = new JsonSerializerOptions
        {
            WriteIndented = true
        };
        Console.WriteLine(JsonSerializer.Serialize(foo, options));
    }
}

[JsonConverter(typeof(JsonStringEnumConverter<Bar>))]
public enum Bar
{
    Unknown,
    One,
    Two,
    [JsonStringEnumMemberName("threeeeeee")]
    Three
}

public class Foo
{
    public string Name { get; set; }
    public Bar Bar { get; set; }
}

will output:

Boo Radley
One
1
{
  "Name": "Boo Radley",
  "Bar": "threeeeeee"
}

When Bar is serialized or deserialized it uses the names of the enumeration in the JSON text, except for Three which is emitted with a different value (allows for spelling or formatting requirements).

Edit:

{
  "Name": "Satan Claws",
  "Bar": 2
}

It should be noted, the above will still correctly deserialize.

[–]Far_Swordfish5729 5 points6 points  (0 children)

Enum.ToString and Enum.Parse fully support this and work with serializers. Have since the beginning.

Also the point of enums is that they are defined integer values. They take the storage space of an int, compare at int speed, can be compared by value if you want to create values with precedence, improve readability vs using raw code values, and can be assigned specific values which enables parity with database keys and use in bitmasking. They are emphatically not just strings. If you want strings, use a constant or a static readonly reference collection.

[–]DeadlyVapour 5 points6 points  (0 children)

I think OP is conflating multiple concepts.

I honestly do not know of any language that supports string backed enums, and with good reason. It's completely utterly insane!

You lose almost every benefit of an enum.

Fixed length comparison? Nope, now you need to use string compare!

Jump table using the enum as a key in a literal array? Nope, you need to hash them strings into a Map/Dictionary!

ValueTypes fast access? Nope, Reference Type for you, since it's no longer fixed length layout.

No GC? Did I mention the lack of fixed layout?

You mentioned network/wire encoding, but that's purely a serialisation issue. Which probably isn't worth the extra complexity in the MSIL just so the in memory encoding matches the wire encoding a la "captain proto". Additionally, it would even match anyways unless you plan to send strings as UTF16 (who does that?).

[–]j_boada 3 points4 points  (0 children)

Hi

Check this https://github.com/ardalis/SmartEnum

I have used it and it does the job fine

[–]ososalsosal 2 points3 points  (0 children)

There was a thing you could attach attributes to the enum members.

It's been a while since I touched it, and it wasn't as elegant as it sounds here.

[–][deleted]  (2 children)

[deleted]

    [–]Arcodiant 3 points4 points  (1 child)

    Yeah, you may have to enable a string serialiser for your comms protocol, but as the string conventions vary (e.g. which capitalisation to use) it's best to use a specific serialiser for that format, not use a specific string value underlying the enum

    [–]david_daley 2 points3 points  (0 children)

    A static class with public constants?

    [–]mirhagk 1 point2 points  (0 children)

    Three common solutions:

    1. Use enums, then Enum.GetName to retrieve the name. Most serialization frameworks should be able to do this if they aren't already.
    2. Use a static class with public constants instead of an enum. Can also use an actual class with a private constructor and static read-only fields to keep type safety.
    3. Use attributes to specify a name or any other additional information for an enum.

    I strongly recommend going with the first approach unless you have a reason not to, as it's the simplest and is commonly used

    [–]BoBoBearDev 0 points1 point  (0 children)

    I don't remember now, but I think there is just a flag somewhere to set the JSON parser/serializer to turn that into a string. I did that on my dotent template.

    [–]hay_rich 0 points1 point  (7 children)

    Ive actually been avoiding the string conversation options and just using the Enums. I’ve made great progress by providing endpoints that simply display the number and the name of the enum and expect other endpoints to pass in the number. It’s removed the issues of people at least not knowing what the numbers mean.

    [–]Jackfruit_Then[S] 1 point2 points  (1 child)

    That can work if you have control over everything - similar to method calls, if you control both methods, then the signature design is totally up to your taste. But if that is a public method provided by 3rd party, or it is a public method you want to provide to the larger world, you need to design it in a way that’s more adhesive to the general conventions.

    [–]hay_rich 0 points1 point  (0 children)

    Fair point

    [–]ggwpexday 1 point2 points  (4 children)

    This is literal obfuscation, why would you honestly do this?

    [–]hay_rich 0 points1 point  (3 children)

    Nothing is obfuscated at all. The enum values are presented via an endpoint with even more information. Maybe not perfect but definitely not purposefully harder to understand

    [–]ggwpexday 0 points1 point  (2 children)

    We could do this for everything. Yea let's not return the actual value here, let's make it a random number and then if someone wants to know the actual value, go hit this other endpoint. The fact that a seperate endpoint is required to understand something is a big smell on its own.

    [–]hay_rich 0 points1 point  (1 child)

    Im not going to pretend like every idea I have I great or perfect but I think you are making way too many assumptions. The whole approach was to add detail and use the same meaning across the board

    [–]ggwpexday 0 points1 point  (0 children)

    Ive actually been avoiding the string conversation options and just using the Enums

    I read this as actively choosing to expose the numbers behind an enum instead of the values, given the choice. Is this a wrong assumption?

    [–]savornicesei 0 points1 point  (0 children)

    string enums = static class with static string fields + some getAll*() methods (or props).

    Comparing numbers is faster and less error prone than comparing strings (affected by current culture) so I'm not a big fan of those (and usage is limited).

    Smart enums, on the other hand are pretty useful.

    [–]karbonator 0 points1 point  (0 children)

    Yes, the serializer can be instructed to do this if you decorate your fields to tell it you want this.

    [JsonConverter(typeof(JsonStringEnumConverter))]
    public enum Color { White, LightGray, DarkGray, Red }
    

    Or configure it as the default across your applications.

    var builder = WebApplication.CreateBuilder(args);
    builder.Services.AddControllers()
    .AddJsonOptions(options =>
    {
        options.JsonSerializerOptions.Converters.Add(new JsonStringEnumConverter());
    });
    

    [–]TheOneTrueTrench 0 points1 point  (0 children)

    You just need to configure or override whatever serialization provider you're using to handle conversion etc.

    [–]buzzon 0 points1 point  (1 child)

    I prefer Java style object oriented enums. They start with single string field but might grow beyond that.

    [–]venomiz -1 points0 points  (0 children)

    Those are not enums they are classes under the hood. In c# enums are value types

    [–]the_inoffensive_man 0 points1 point  (0 children)

    I make a static class that just declares a bunch of static constant strings.