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.