Yes, you can do that with reflection.
Let's suppose you have the following data model:
class Example
{
public Guid Id { get; set; }
public int InternalId { get; set; }
public DateTime DateOnly { get; set; }
public DateTime DateTime { get; set; }
}
- As you can see we have two
DateTime
properties
- One of them ends with
Time
- We also have two other properties just to make sure that we are
not loosing type information during serialization
You can create a JsonConverter against the whole object like this:
class ExampleConverter : JsonConverter<Example>
{
public override Example Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
{
throw new NotImplementedException();
}
public override void Write(Utf8JsonWriter writer, Example value, JsonSerializerOptions options)
{
var objectProperties = value.GetType().GetProperties();
var objectFieldNameValuePairs = new Dictionary<string, object>();
foreach (var objectProperty in objectProperties)
{
if (objectProperty.PropertyType == typeof(DateTime))
{
var datetimeFieldValue = (DateTime)objectProperty.GetValue(value);
var transformedValue = datetimeFieldValue.ToString(objectProperty.Name.EndsWith("Time") ? "g" : "d");
objectFieldNameValuePairs.Add(objectProperty.Name, transformedValue);
}
else
objectFieldNameValuePairs.Add(objectProperty.Name, objectProperty.GetValue(value));
}
writer.WriteStartObject();
foreach (KeyValuePair<string,object> fieldNameAndValue in objectFieldNameValuePairs)
{
writer.WritePropertyName(fieldNameAndValue.Key);
JsonSerializer.Serialize(writer, fieldNameAndValue.Value, options);
}
writer.WriteEndObject();
}
}
- First we get all properties of the
Example
- Then we iterate through them
- If the property holds a
DateTime
then based on your provided heuristic we are using different date formatting string
- Otherwise we don't do any conversion
- Finally we serialize the
Dictionary
manually
Usage
var example = new Example { DateOnly = DateTime.UtcNow, DateTime = DateTime.UtcNow, Id = Guid.NewGuid(), InternalId = 100 };
var serializedExample = JsonSerializer.Serialize(example, new JsonSerializerOptions { Converters = { new ExampleConverter() } });
Console.WriteLine(serializedExample);
Output
{
"Id":"eddd0620-b27c-4041-bd28-af6bfbd70583",
"InternalId":100,
"DateOnly":"7/23/2021",
"DateTime":"7/23/2021 7:10 AM"
}
UPDATE Make the converter more generic
As it was discussed in the comments section the converter should not be tied to a specific class. It should be able to handle any class. Unfortunately we can't specify object
as the type parameter of the JsonConverter
.
On the other hand we can receive the type parameter when we create an instance from our converter. So, here is the revised converter code:
class DateTimeConverter<T> : JsonConverter<T>
{
public override T Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
{
throw new NotImplementedException();
}
public override void Write(Utf8JsonWriter writer, T value, JsonSerializerOptions options)
{
PropertyInfo[] props = value.GetType().GetProperties();
Dictionary<string, object> data = new Dictionary<string, object>();
foreach (var prop in props)
{
if (prop.PropertyType == typeof(DateTime))
{
var date = (DateTime)prop.GetValue(value);
data.Add(prop.Name, date.ToString(prop.Name.EndsWith("Time") ? "g" : "d"));
}
else
data.Add(prop.Name, prop.GetValue(value));
}
writer.WriteStartObject();
foreach (KeyValuePair<string,object> item in data)
{
writer.WritePropertyName(item.Key);
JsonSerializer.Serialize(writer, item.Value, options);
}
writer.WriteEndObject();
}
}
Usage
var ex = new Example
{
DateOnly = DateTime.UtcNow,
DateTime = DateTime.UtcNow,
Id = Guid.NewGuid(),
InternalId = 100
};
var aex = new AnotherExample
{
CreationDate = DateTime.UtcNow,
Description = "Testing"
};
var options = new JsonSerializerOptions
{
Converters =
{
new DateTimeConverter<Example>(),
new DateTimeConverter<AnotherExample>()
}
};
Console.WriteLine(JsonSerializer.Serialize(ex, options));
Console.WriteLine(JsonSerializer.Serialize(aex, options));
Known limitation: This converter can't be used against anonymous types.