Aleks Vujic
Aleks Vujic

Reputation: 2251

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?

Upvotes: 4

Views: 2000

Answers (1)

Brian Rogers
Brian Rogers

Reputation: 129667

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

Upvotes: 3

Related Questions