Search code examples
enumssystem.text.json.net-8.0native-aot

Use a Blanket policy to serialize enums as strings with snake case


I'm moving some libraries to .net 8 and I'm trying to use the new JsonNamingPolicy.SnakeCaseLower for enums (I have a custom converter that I currently use that uses reflection but I want to drop it). I can serialize an enum to snake case using this JsonSerializerOptions

JsonSerializerOptions options = new()
{
    DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull,
    PropertyNamingPolicy = JsonNamingPolicy.SnakeCaseLower,
};

options.Converters.Add(new JsonStringEnumConverter(namingPolicy: JsonNamingPolicy.SnakeCaseLower));

The problem is that according to the documentation The non-generic JsonStringEnumConverter type is not supported for AOT. The solution given is using [JsonSourceGenerationOptions(UseStringEnumConverter = true)] in the class inheriting from JsonSerializerContext but then I lose the naming policy for enums.

Is there a way to use JsonNamingPolicy.SnakeCaseLower globally for all enums in an AOT friendly way?


Solution

  • This is not implemented as of .NET 8, see [API Proposal]: Add option to specify JsonNamingPolicy for enum serialization on JsonSourceGenerationOptions #92828.

    Recently there has been added UseStringEnumConverter flag to JsonSourceGenerationOptions, but there is no way to configure naming policy for these converters. -ithline.

    Sounds reasonable. -eiriktsarpalis.

    MSFT's Eirik Tsarpalis suggests, as a workaround, to define a JsonStringEnumConverter<TEnum> subtype for your required naming policy and, for each enum, add it to your serialization context, e.g. like so:

    public class SnakeCaseLowerStringEnumConverter<TEnum>() 
        : JsonStringEnumConverter<TEnum>(JsonNamingPolicy.SnakeCaseLower) 
        where TEnum : struct, Enum;
    
    [JsonSourceGenerationOptions(
        DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull,
        PropertyNamingPolicy = JsonKnownNamingPolicy.SnakeCaseLower,
        Converters = new[] { 
            // Add all known enums here
            typeof(SnakeCaseLowerStringEnumConverter<FirstEnum>), 
            typeof(SnakeCaseLowerStringEnumConverter<SecondEnum>),
            //...
            typeof(SnakeCaseLowerStringEnumConverter<LastEnum>)})]
    // Add all known enums here also
    [JsonSerializable(typeof(FirstEnum)), 
     JsonSerializable(typeof(SecondEnum)),
     //...
     JsonSerializable(typeof(LastEnum))]
    // And also add all other root types to be serialized in this context
    [JsonSerializable(typeof(MyModel)),] 
    public partial class SnakeCaseLowerContext : JsonSerializerContext { }
    

    This, however, requires knowing all the enum types in advance. There's no way to set a blanket policy.