Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Framework guidelines dictate that Enum names should be Pascal cased #224

Closed
oising opened this issue Nov 20, 2023 · 9 comments · Fixed by #225
Closed

Framework guidelines dictate that Enum names should be Pascal cased #224

oising opened this issue Nov 20, 2023 · 9 comments · Fixed by #225
Assignees

Comments

@oising
Copy link

oising commented Nov 20, 2023

Framework guidelines dictate that Enum names should be Pascal cased -- then you could avoid the ugly at escape on explicit.

Originally posted by @oising in #212 (comment)

@mtmk mtmk assigned mtmk and unassigned mtmk Nov 20, 2023
@mtmk
Copy link
Collaborator

mtmk commented Nov 20, 2023

The only solution I can come up with is to create a convertor:

public class JsonStringEnumConverter<TEnum> : JsonConverter<TEnum> where TEnum : struct, Enum
{
    public override TEnum Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
    {
        if (typeToConvert == typeof(MyEnum))
        {
            var type = reader.TokenType;
            if (type == JsonTokenType.String)
            {
                var stringValue = reader.GetString();

                switch (stringValue)
                {
                    case "explicit":
                        return (TEnum)(object)MyEnum.Explicit;
                    case "all":
                        return (TEnum)(object)MyEnum.All;
                    case "none":
                        return (TEnum)(object)MyEnum.None;
                }
            }

            return default;
        }
            
        throw new InvalidOperationException();
    }

    public override void Write(Utf8JsonWriter writer, TEnum value, JsonSerializerOptions options)
    {
        if (value is MyEnum myEnum)
        {
            switch (myEnum)
            {
                case MyEnum.None:
                    writer.WriteStringValue("none");
                    break;
                case MyEnum.All:
                    writer.WriteStringValue("all");
                    break;
                case MyEnum.Explicit:
                    writer.WriteStringValue("explicit");
                    break;
            }
        }
        else
        {
            throw new InvalidOperationException();
        }
    }
}

@oising
Copy link
Author

oising commented Nov 20, 2023

@mtmk -- net8.0 natively has JsonStringEnumConverter<T> -- is this for net6?

@mtmk
Copy link
Collaborator

mtmk commented Nov 20, 2023

yes but it seems to be ignoring the case options for me 😢
Also yes, we need to support net6.0 as well.

@oising
Copy link
Author

oising commented Nov 20, 2023

Okay, I definitely had it working in net6 -- let me get a spike going locally (console net6 app) to validate the settings and I'll get back to you here.

@oising
Copy link
Author

oising commented Nov 20, 2023

Alright, got it. This is targeting net6.0 but I'm referencing the 8.0.0 sys.text.json (as you probably are too)

// See https://aka.ms/new-console-template for more information

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

// object to serialize
var shape = new Shape { Color = Color.Red };

// override the default enum naming policy by passing our own string enum converter instance
var context = new ShapeSerializationContext(new JsonSerializerOptions
{
    PropertyNamingPolicy = JsonNamingPolicy.CamelCase,
    Converters = { new JsonStringEnumConverter(JsonNamingPolicy.CamelCase) }
});

// we pass our custom context instead of ShapeSerializationContext.Default
var json = JsonSerializer.Serialize(shape, context.Shape);
Console.WriteLine(json);

public enum Color
{
    Red,
    Green,
    Blue
}

public class Shape
{
    public Color Color { get; set; }
}

[JsonSerializable(typeof(Shape))]
[JsonSerializable(typeof(Color))]
[JsonSourceGenerationOptions(UseStringEnumConverter = true)] // i.e. "red" not 0
public partial class ShapeSerializationContext : JsonSerializerContext
{
}

outputs: { "color": "red" }

@oising
Copy link
Author

oising commented Nov 20, 2023

Figuring this out the first time took me hours.

@oising
Copy link
Author

oising commented Nov 20, 2023

Here's the variant for net8.0 that will permit AOT publish:

// See https://aka.ms/new-console-template for more information

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

// object to serialize
var shape = new Shape { Color = Color.Red };

// override the default enum naming policy by passing our own string enum converter instance
var context = new ShapeSerializationContext(new JsonSerializerOptions
{
    // ensures "Color" property will be camel-cased
    PropertyNamingPolicy = JsonNamingPolicy.CamelCase,
    
    // use new generic version of JsonStringEnumConverter to permit AOT
    // need to also pass in the naming policy to ensure the enum values are camel-cased
    Converters = { new JsonStringEnumConverter<Color>(JsonNamingPolicy.CamelCase) }
});

Debug.Assert(JsonSerializer.IsReflectionEnabledByDefault == false);

// we pass our custom context instead of ShapeSerializationContext.Default
var json = JsonSerializer.Serialize(shape, context.Shape);
Console.WriteLine(json);

public enum Color
{
    Red,
    Green,
    Blue
}

public class Shape
{
    public Color Color { get; set; }
}

[JsonSerializable(typeof(Shape))]
[JsonSerializable(typeof(Color))]
[JsonSourceGenerationOptions(
    UseStringEnumConverter = true)] // i.e. "red" not 0
public partial class ShapeSerializationContext : JsonSerializerContext
{
}

My csproj:

<Project Sdk="Microsoft.NET.Sdk">

    <PropertyGroup>
        <OutputType>Exe</OutputType>
        <TargetFramework>net8.0</TargetFramework>
        <ImplicitUsings>enable</ImplicitUsings>
        <Nullable>enable</Nullable>
        <JsonSerializerIsReflectionEnabledByDefault>false</JsonSerializerIsReflectionEnabledByDefault>
        <PublishAot>true</PublishAot>
    </PropertyGroup>

    <ItemGroup>
      <PackageReference Include="System.Text.Json" Version="8.0.0" />
    </ItemGroup>

</Project>

@mtmk
Copy link
Collaborator

mtmk commented Nov 21, 2023

this is great. worked for me in my little test app. I think I can apply it now. thank you @oising!

@oising
Copy link
Author

oising commented Nov 21, 2023

And thank you for your great work giving us a top class dotnet client!

@mtmk mtmk self-assigned this Nov 21, 2023
@mtmk mtmk linked a pull request Nov 21, 2023 that will close this issue
@mtmk mtmk closed this as completed in #225 Nov 21, 2023
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging a pull request may close this issue.

2 participants