Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Serialize a const with System.Text.Json.Serialization

In .NET 7, is there a way to serialize a const?

internal sealed class CreateResourceCommand : BaseCommand
{
    public const string CommandName = "CreateResourceCommand";
}

I have many Commands derived from a base who are sent to a message queue. The remote consumer will deserialize the payload and not get the CommandName.

It is straightforward with Newtonsoft.Json but I can't make it work with System.Text.Json.Serialization.

I tried :

  • [JsonIgnore(Condition = JsonIgnoreCondition.Never)]
  • a JsonConverter but the const won't appear in the value
like image 600
Vincent Avatar asked Oct 12 '25 12:10

Vincent


1 Answers

As mentioned in comments by Serge, serializing const values is a bit of an odd thing to do, and is apparently not implemented out of the box even if you apply [JsonInclude]. Thus you're going to need to create a typeInfo modifier to serialize them.

First define the following modifiers to force serialization of const values when some attribute TOptInAttribute is applied:

public static partial class JsonExtensions
{
    // Include opted-in constants for the specified type.
    public static Action<JsonTypeInfo> AddOptInConstMembers<TOptInAttribute>(Type type) where TOptInAttribute : System.Attribute =>
        typeInfo =>
        {
            if (typeInfo.Type == type)
                AddOptInConstMembers<TOptInAttribute>(typeInfo);
        };

    // Include opted-in constants for all types.
    public static void AddOptInConstMembers<TOptInAttribute>(JsonTypeInfo typeInfo) where TOptInAttribute : System.Attribute
    {
        if (typeInfo.Kind != JsonTypeInfoKind.Object)
            return;
        foreach (var field in typeInfo.Type.GetConstants().Where(f => Attribute.IsDefined(f, typeof(TOptInAttribute))))
        {
            var name = field.GetCustomAttribute<JsonPropertyNameAttribute>()?.Name ?? typeInfo.Options.PropertyNamingPolicy?.ConvertName(field.Name) ?? field.Name;
            var value = field.GetValue(null); // field.GetValue(null); returns enums as enums rathen raw integers.
            var propertyInfo = typeInfo.CreateJsonPropertyInfo(value?.GetType() ?? field.FieldType, name);
            propertyInfo.Get = (o) => value;
            propertyInfo.CustomConverter = field.GetCustomAttribute<JsonConverterAttribute>()?.ConverterType is {} converterType
                ? (JsonConverter?)Activator.CreateInstance(converterType)
                : null;
            typeInfo.Properties.Add(propertyInfo);
        }
    }

    static IEnumerable<FieldInfo> GetConstants(this Type type) =>
        // From the answer https://stackoverflow.com/a/10261848
        // By https://stackoverflow.com/users/601179/gdoron
        // To https://stackoverflow.com/questions/10261824/how-can-i-get-all-constants-of-a-type-by-reflection
        type.GetFields(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Static | BindingFlags.FlattenHierarchy)
        .Where(fi => fi.IsLiteral && !fi.IsInitOnly);
}

Then apply your chosen TOptInAttribute attribute to the const fields to be serialized. It could be your own custom attribute or [JsonInclude] as per your preference:

internal sealed class CreateResourceCommand : BaseCommand
{
    [JsonInclude]
    public const string CommandName = "CreateResourceCommand";
}

Finally, to serialize const fields with that attribute applied (here [JsonInclude]), use the following options:

var command = new CreateResourceCommand();

var options = new JsonSerializerOptions
{
    TypeInfoResolver = new DefaultJsonTypeInfoResolver
    {
        Modifiers = { JsonExtensions.AddOptInConstMembers<JsonIncludeAttribute> },
    },
};
var json = JsonSerializer.Serialize(command, options);

Console.WriteLine(json); // Prints {"CommandName":"CreateResourceCommand"}
Assert.AreEqual("""{"CommandName":"CreateResourceCommand"}""", json);

Of course the const values are read-only and will not be deserialized.

Demo fiddle here.

like image 130
dbc Avatar answered Oct 14 '25 02:10

dbc