Вот примерная реализация того, что вам нужно, с использованием. NET Core System.Text. Json
Как написать собственные преобразователи для JSON сериализации (маршалинга) в . NET
public static class SerializationTester
{
public static void TestSerialize()
{
TestClass test = new TestClass()
{
AnyNumber = 3,
DateOfBirth = DateTime.UtcNow,
Email = "hello@world.com",
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; }
}