Reputation: 49
I know that there is a way but what is the right way to convert(serialize) .NET Core Model Class (C# class properties) from this:
[Required]
[DataType(DataType.Text)]
public string Name {get;set;}
[Required, DataType(DataType.EmailAddress)]
public string Email {get;set;}
[Required]
[DataType(DataType.Text)]
public string Subject{get;set;}
[Required]
[MaxLength(500)]
[Required, DataType(DataType.MultilineText)]
public string Message{get;set;}
[Required]
[MaxLength(500)]
[Required, DataType(DataType.DateTime)]
public DateTime DateOfBirth{get;set;}
public int AnyNumber{get;set;}
To this:
[
{
"name":
{
"value": "",
"type": "text",
"validations":
{
"required": true
}
}
},
{
"email":
{
"value": "",
"type": "email",
"validations":
{
"required": true
}
},
"message":
{
"value": "",
"type": "multilineText",
"validations":
{
"required": true,
"maxLength": 500
}
},
"anyNumber":
{
"value": 0,
"type": "number",
"validations":
{
"required": false
}
}
},
]
So any class that I provide structured like in the provided snipped image to get JSON serialized response.
I have found and tried some solutions but things for me get very complicated and I know that reflection is expensive so I have to use it smartly.
Thank you in advance.
Upvotes: 0
Views: 2133
Reputation: 593
Here is a rough implementation of what you need, using the .NET Core System.Text.Json
How to write custom converters for JSON serialization (marshalling) in .NET
public static class SerializationTester
{
public static void TestSerialize()
{
TestClass test = new TestClass()
{
AnyNumber = 3,
DateOfBirth = DateTime.UtcNow,
Email = "[email protected]",
Message = "hello world!",
Name = "john smith",
Subject = "some subject"
};
var options = new JsonSerializerOptions()
{
WriteIndented = true,
IgnoreNullValues = true
};
options.Converters.Add(new MetadataConverter());
var serialized = JsonSerializer.Serialize(test, options);
Console.WriteLine(serialized);
}
}
public class MetadataConverter : JsonConverterFactory
{
/// <summary>
/// contain nul metadata for types that don't have metdata attributes and dont' need custom converters
/// each type will only be parsed once with reflections, obviously the attribute values are identical for all class instances
/// </summary>
private Dictionary<Type, Dictionary<PropertyInfo, Metadata>> typesMetadataCache = new Dictionary<Type, Dictionary<PropertyInfo, Metadata>>();
public override bool CanConvert(Type typeToConvert)
{
Dictionary<PropertyInfo, Metadata> typeMeta;
if (!typesMetadataCache.TryGetValue(typeToConvert, out typeMeta))
{
typesMetadataCache[typeToConvert] = typeMeta = GetTypeMeta(typeToConvert);
}
return typeMeta != null;
}
private Dictionary<PropertyInfo, Metadata> GetTypeMeta(Type typeToConvert)
{
Dictionary<PropertyInfo, Metadata> theReturn = new Dictionary<PropertyInfo, Metadata>();
bool metadataSpecified = false;
foreach (var currentProperty in typeToConvert.GetProperties(BindingFlags.Public | BindingFlags.Instance))
{
var required = currentProperty.GetCustomAttributes<RequiredAttribute>()?.FirstOrDefault();
var dataType = currentProperty.GetCustomAttributes<DataTypeAttribute>()?.FirstOrDefault();
var maxLength = currentProperty.GetCustomAttributes<MaxLengthAttribute>()?.FirstOrDefault();
if (required != null || dataType != null || maxLength != null)
{
metadataSpecified = true;
}
var currentMeta = theReturn[currentProperty] = new Metadata()
{
type = dataType?.DataType.ToString() ?? currentProperty.PropertyType.Name,
validations = new Validations()
{
maxLength = maxLength?.MaxLength,
required = (required != null)
}
};
}
if (metadataSpecified)
{
return theReturn;
}
// metadata not specified for any property, don't use custom converter
return null;
}
public override JsonConverter CreateConverter(Type typeToConvert, JsonSerializerOptions options)
{
return (JsonConverter)Activator.CreateInstance(typeof(MetadataTypeConverter<>).MakeGenericType(typeToConvert), options, typesMetadataCache[typeToConvert]);
}
private class MetadataTypeConverter<TValue> : JsonConverter<TValue>
{
private Dictionary<PropertyInfo, JsonConverter> propertyConverters = new Dictionary<PropertyInfo, JsonConverter>();
public MetadataTypeConverter(JsonSerializerOptions options, Dictionary<PropertyInfo, Metadata> typeMetadata)
{
foreach (var currentMeta in typeMetadata)
{
if (currentMeta.Value == null)
{
propertyConverters[currentMeta.Key] = options.GetConverter(currentMeta.Key.PropertyType);
}
else
{
propertyConverters[currentMeta.Key] = (JsonConverter)Activator.CreateInstance(typeof(MetadataValueConverter<>).MakeGenericType(currentMeta.Key.PropertyType), options, currentMeta.Value);
}
}
}
public override TValue Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
{
throw new NotImplementedException();
}
public override void Write(Utf8JsonWriter writer, TValue value, JsonSerializerOptions options)
{
writer.WriteStartObject();
foreach (var currentConverter in propertyConverters)
{
var currentPropertyValue = currentConverter.Key.GetValue(value);
if (currentConverter.Value is IMetadataValueConverter currentMetadataValueConverter)
{
currentMetadataValueConverter.Write(writer, currentPropertyValue, options);
}
else
{
var currentWriteMethod = currentConverter.Value.GetType().GetMethod("Write", BindingFlags.Public | BindingFlags.Instance);
currentWriteMethod.Invoke(currentConverter.Value, new object[] { writer, currentPropertyValue, options });
}
}
writer.WriteEndObject();
}
}
private class MetadataValueConverter<TValue> : JsonConverter<TValue>, IMetadataValueConverter
{
private Metadata metadata;
public MetadataValueConverter(JsonSerializerOptions options, Metadata metadata)
{
this.metadata = metadata;
}
public override TValue Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
{
var value = JsonSerializer.Deserialize<MetadataWithValue>(ref reader, options);
return value.value;
}
public override void Write(Utf8JsonWriter writer, TValue value, JsonSerializerOptions options)
{
JsonSerializer.Serialize(writer, new MetadataWithValue(metadata, value), options);
}
void IMetadataValueConverter.Write(Utf8JsonWriter writer, object value, JsonSerializerOptions options) => Write(writer, (TValue)value, options);
public class MetadataWithValue : Metadata
{
public MetadataWithValue() { }
public MetadataWithValue(Metadata metadata, TValue value)
{
type = metadata.type;
validations = metadata.validations;
this.value = value;
}
public TValue value { get; set; }
}
}
private interface IMetadataValueConverter
{
void Write(Utf8JsonWriter writer, object value, JsonSerializerOptions options);
}
private class Metadata
{
public string type { get; set; }
public Validations validations { get; set; }
}
private class Validations
{
public bool required { get; set; }
public int? maxLength { get; set; }
}
}
[AttributeUsage(AttributeTargets.Property, AllowMultiple = true, Inherited = true)]
public class RequiredAttribute : Attribute { }
public enum DataType
{
Text,
EmailAddress,
MultilineText,
DateTime
}
[AttributeUsage(AttributeTargets.Property, AllowMultiple = false, Inherited = true)]
public class DataTypeAttribute : Attribute
{
public DataTypeAttribute(DataType dataType)
{
DataType = dataType;
}
public DataType DataType { get; private set; }
}
[AttributeUsage(AttributeTargets.Property, AllowMultiple = false, Inherited = true)]
public class MaxLengthAttribute : Attribute
{
public MaxLengthAttribute(int maxLength)
{
MaxLength = maxLength;
}
public int MaxLength { get; private set; }
}
public class TestClass
{
[Required]
[DataType(DataType.Text)]
public string Name { get; set; }
[Required, DataType(DataType.EmailAddress)]
public string Email { get; set; }
[Required]
[DataType(DataType.Text)]
public string Subject { get; set; }
[Required]
[MaxLength(500)]
[Required, DataType(DataType.MultilineText)]
public string Message { get; set; }
[Required]
[MaxLength(500)]
[Required, DataType(DataType.DateTime)]
public DateTime DateOfBirth { get; set; }
public int AnyNumber { get; set; }
}
Upvotes: 1