Search code examples
c#asp.net-corejson.net

Ignore property with attribute in custom JSON converter


We are using .NET Core 3.1. We have a custom JSON converter for DateTime and DateTime? properties.

JsonDateTimeConverter.cs

public class JsonDateTimeConverter : DateTimeConverterBase
{
    public override bool CanConvert(Type objectType)
    {
        // I want to return false for object properties which have attribute "TimeZoneIgnore"
        
        return objectType == typeof(DateTime) || objectType == typeof(DateTime?);
    }

    public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
    {
        throw new NotImplementedException();
    }

    public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
    {
        // convert datetime to timezone
        DateTime? dateTime = (value as DateTime?).ConvertToTimeZone("CEST");

        writer.WriteValue(dateTime);
        writer.Flush();
    }
}

TimeZoneIgnore.cs

[AttributeUsage(AttributeTargets.Property)]
public class TimeZoneIgnore : Attribute { }

Bank.cs

public class Bank
{
    public string Name { get; set; }
    public DateTime ConvertThis { get; set; }
    [TimeZoneIgnore]
    public DateTime DontConvertThis { get; set; }
}

TestController.cs

[HttpGet]
public IActionResult Test123()
{
    JsonSerializerSettings settings = new JsonSerializerSettings();
    settings.Converters.Add(new JsonDateTimeConverter());
    
    return Json(new Bank()
    {
        Name = "Test bank",
        ConvertThis = new DateTime(2020, 8, 18, 15, 7, 1),
        DontConvertThis = new DateTime(2020, 8, 18, 15, 7, 1)
    }, settings);
}

How can I return false in CanConvert(Type objectType) for object properties with TimeZoneIgnore attribute?


Solution

  • A JsonConverter doesn't have the context to determine which property it is being applied to, so there is not an easy way to get the attributes from within it. On the other hand, a ContractResolver does have the context information, because it is responsible for mapping JSON properties to class properties. It turns out you can also use a ContractResolver to programmatically apply or remove JsonConverters.

    So, instead of applying your JsonDateTimeConverter globally in settings, you can use a custom ContractResolver to apply it conditionally based on the presence or absence of the [TimeZoneIgnore] attribute. Here is the code you would need for the resolver:

    public class ConditionalDateTimeResolver : DefaultContractResolver
    {
        static readonly JsonConverter DateTimeConverter = new JsonDateTimeConverter();
    
        protected override JsonProperty CreateProperty(MemberInfo member, MemberSerialization memberSerialization)
        {
            JsonProperty prop = base.CreateProperty(member, memberSerialization);
            if (DateTimeConverter.CanConvert(prop.PropertyType) && 
                !prop.AttributeProvider.GetAttributes(typeof(TimeZoneIgnore), true).Any())
            {
                prop.Converter = DateTimeConverter;
            }
            return prop;
        }
    }
    

    To use the resolver, add it to the JsonSerializerSettings instead of adding your converter:

    JsonSerializerSettings settings = new JsonSerializerSettings();
    settings.ContractResolver = new ConditionalDateTimeResolver();
    // settings.Converters.Add(new JsonDateTimeConverter());          <-- remove this line
    

    Here is a working demo: https://dotnetfiddle.net/8LBZ4S