Search code examples
.netserializationstructstaticconstants

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


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.


Solution

  • 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);