.Net 8 API: Serialize a class of structs with consts

24 views Asked by At

What I'm trying to do may look odd at first, but I'll explain why I'm trying to achieve that then.

In my Domain layer I created a constants class where I define all my AWS DynamoDB database structure to be used like a sort of Enum wherever I need database tables and fields names.

DBStructure.cs:

public class DBStructure
{
    public struct DynamoDB
    {
        public struct Tables 
        {
            public struct Tests
            {
                public const string TableName = "test_test";

                public struct Fields
                {
                    [JsonProperty("DynamoDB")]
                    public const string TestId = "test_id";
                    public const string Test = "test";
                    public const string TestLevel = "test_level";
                    public const string TestNumber = "test_level_number";
                    public const string TestType = "test_type";
                    public const string TestSubject = "test_subject";
                    public const string FP = "f_p";
                    public const string PermittedErrors = "permitted_errors";
                    public const string TimeLimit = "time_limit";
                    public const string TestCreator = "test_author";
                    public const string TestCreatorEmail = "email";
                    public const string Active = "active";
                    public const string TestComments = "comments";
                    public const string TestImage = "image";
                    public const string TestLanguage = "language_id";
                    public const string TestLanguage2 = "test_lng";
                    public const string TestLanguage3 = "test_language";
                    public const string TestTimestamp = "timestamp";
                }

                public struct Indexes
                {
                    public const string TestTypeIndex = "test_type-index";
                    public const string TestSubjectIndex = "test_subject-index";
                }
            }

            public struct Questions
            {
                public const string TableName = "test_questions";

                public struct Fields
                {
                    public const string QuestionId = "question_id";
                    public const string TestId = "test_id";
                    public const string Instructions = "instructions";
                    public const string Question = "question";
                    public const string Explanations = "explanations";
                    public const string QuestionNumber = "question_number";
                    public const string Timestamp = "timestamp";
                }

                public struct Indexes
                {
                    public const string TestIdQuestionNumberIndex = "test_id-question_number-index";
                }
            }

            ... More Structs
        }
    }
}

I need to serialize this complete class in order to be able to pass the string consts to Javascript through an Ajax call.

The reason for that is because I need the fields -and tables- names as in Javascript, when the Test is to be saved, I create a json with its data (read from html form fields) to be sent to the controller.

I definitely know this is not the way (or at least not the best one) to accomplish this, but I'm in the middle of a big refactor and just going step by step.

I've tried Is serialization possible for a struct, but the problem is that in my case the members are constants, and so it's not working.

Any way to accomplish my class serialization? Any comments are welcome.

1

There are 1 answers

0
Diego Perez On BEST ANSWER

Thanks to: Serialize a const with System.Text.Json.Serialization

I was able to sort my issue using the provided JsonExtensions class.

JsonExtensions:

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 in my code:

public class DBStructure
{
    public struct DynamoDB
    {
        public struct Tables 
        {
            public struct Tests
            {
                public const string TableName = "test_test";

                public struct Fields
                {
                    [JsonInclude]
                    public const string TestId = "test_id";
                    [JsonInclude]
                    public const string Test = "test";
                    [JsonInclude]
                    public const string TestLevel = "test_level";
                    ...
                }
            }

            public struct Questions
            {
                public const string TableName = "test_questions";

                public struct Fields
                {
                    [JsonInclude]
                    public const string QuestionId = "question_id";
                    [JsonInclude]
                    public const string TestId = "test_id";
                    ...
                }
            }

            ... More Structs
        }
    }
}

And I use it more or less like this (still need to adjust it a little bit in order to return an object with all json, but I'm close to what I expect):

var testsFiels = new DBStructure.DynamoDB.Tables.Tests.Fields();
var questionsFiels = new DBStructure.DynamoDB.Tables.Questions.Fields();

var options = new JsonSerializerOptions
{
    TypeInfoResolver = new DefaultJsonTypeInfoResolver
    {
        Modifiers = { JsonExtensions.AddOptInConstMembers<JsonIncludeAttribute> },
    },
};

var testsJson = System.Text.Json.JsonSerializer.Serialize(testsFiels, options);
var questionsJson = System.Text.Json.JsonSerializer.Serialize(questionsFiels, options);