Mauro
Mauro

Reputation: 4511

Using property attributes to define which JsonConverter to use

I have a need to specify a JsonConverter for properties which are decorated with a specific attribute, in this case [DataType(DataType.PostalCode)].

I already have a custom JsonConverter for which I have set the CanConvert method as follows:

public override bool CanConvert(Type objectType) => objectType == typeof(string);

How can I make sure the PostcodeJsonConverter is used instead when the API encounters a PostalCode property?

[DataType(DataType.PostalCode)]
public string Postcode { get; set; }

I've tried the following but I suspect the DataType attribute is not available at this point.

public override bool CanConvert(Type objectType) => 
    objectType == typeof(string) && 
    objectType.GetCustomAttributes(true)
              .OfType<DataTypeAttribute>()
              .Any(dta => dta.DataType == DataType.PostalCode);

Do I need to decorate my model as follows instead?

[DataType(DataType.PostalCode)]
[JsonConverter(typeof(PostcodeJsonConverter))]
public string Postcode { get; set; }

Upvotes: 2

Views: 1360

Answers (2)

Brian Rogers
Brian Rogers

Reputation: 129707

You can make a custom ContractResolver that looks for your DataType attribute on each property and maps the values to the appropriate converter. Here is the code you would need:

public class DataTypeResolver : DefaultContractResolver
{
    private Dictionary<DataType, JsonConverter> ConvertersByDataType { get; set; }

    public DataTypeResolver()
    {
        // Adjust this list to match your actual data types and converters
        ConvertersByDataType = new Dictionary<DataType, JsonConverter>
        {
            { DataType.PostalCode, new PostalCodeConverter() },
            { DataType.PhoneNumber, new PhoneNumberConverter() },
        };
    }

    protected override JsonProperty CreateProperty(MemberInfo member, MemberSerialization memberSerialization)
    {
        JsonProperty prop = base.CreateProperty(member, memberSerialization);
        var att = prop.AttributeProvider.GetAttributes(true).OfType<DataTypeAttribute>().FirstOrDefault();
        if (att != null)
        {
            JsonConverter converter;
            if (ConvertersByDataType.TryGetValue(att.DataType, out converter))
            {
                prop.Converter = converter;
            }
        }
        return prop;
    }
}

Then pass the resolver to SerializeObject and/or DeserializeObject via the settings:

var settings = new JsonSerializerSettings
{
    ContractResolver = new DataTypeResolver()
};
string json = JsonConvert.SerializeObject(yourObject, settings);

Here is a working demo: https://dotnetfiddle.net/k1kWv5

Upvotes: 3

Markus Dresch
Markus Dresch

Reputation: 5574

You can add Converters to the JsonSerializerSettings. So instead of decorating everything, you may as well add your PostcodeJsonConverter there (depending on how often it is used, a decorator may be better though):

For aspnet core defaults:

services.AddMvc().AddJsonOptions(o => o.SerializerSettings.Converters.Add(new PostcodeJsonConverter()))

For JsonConvert:

JsonConvert.SerializeObject(obj, new JsonSerializerSettings
{
    Converters = { new PostcodeJsonConverter() }
});

Upvotes: 1

Related Questions