Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to change property names depending on the type when serializing with Json.net?

I have a property of type object and I have to change the name depending on which type it has. Should be pretty similar to [XmlElement("PropertyName", typeof(PropertyType))] attribute for XML.

For example, I have a property public object Item { get; set; }

If at runtime my property has a type of Vehicle, I want to change the name of my property to "Vehicle"; if it has a type of Profile , I want to change the name of my property to "Profile".

like image 653
csharpgirl Avatar asked Mar 08 '23 20:03

csharpgirl


1 Answers

There is no built-in way to dynamically change the name of a property based on its runtime type, but you could make a custom JsonConverter coupled with a custom Attribute class to do what you want. The converter would need to be made to operate at the class level in order to be able to control the names of the properties written to JSON. It could iterate through the properties of the target class using reflection, and check if any property declared as object has the custom attribute applied. If it does and the object's runtime type matches the type specified in the attribute, then use the property name from the attribute, otherwise just use the original property name.

Here's what the custom attribute would look like:

[AttributeUsage(AttributeTargets.Property, AllowMultiple = true)]
class JsonPropertyNameByTypeAttribute : Attribute
{
    public string PropertyName { get; set; }
    public Type ObjectType { get; set; }

    public JsonPropertyNameByTypeAttribute(string propertyName, Type objectType)
    {
        PropertyName = propertyName;
        ObjectType = objectType;
    }
}

And here is the code for the converter:

public class DynamicPropertyNameConverter : JsonConverter
{
    public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
    {
        Type type = value.GetType();
        JObject jo = new JObject();

        foreach (PropertyInfo prop in type.GetProperties().Where(p => p.CanRead))
        {
            string propName = prop.Name;
            object propValue = prop.GetValue(value, null);
            JToken token = (propValue != null) ? JToken.FromObject(propValue, serializer) : JValue.CreateNull();

            if (propValue != null && prop.PropertyType == typeof(object))
            {
                JsonPropertyNameByTypeAttribute att = prop.GetCustomAttributes<JsonPropertyNameByTypeAttribute>()
                    .FirstOrDefault(a => a.ObjectType.IsAssignableFrom(propValue.GetType()));

                if (att != null)
                    propName = att.PropertyName;
            }

            jo.Add(propName, token);
        }

        jo.WriteTo(writer);
    }

    public override bool CanRead
    {
        get { return false; }
    }

    public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
    {
        // ReadJson is not called if CanRead returns false.
        throw new NotImplementedException();
    }

    public override bool CanConvert(Type objectType)
    {
        // CanConvert is not called if a [JsonConverter] attribute is used
        return false;
    }
}

To use the converter, first add a [JsonConverter] attribute to the target class containing the property (or properties) that you want to be dynamically named. Then, add the custom attribute to the target property (or properties) in that class. You can add as many of the attribute as needed to cover the range of types you are expecting.

For example:

[JsonConverter(typeof(DynamicPropertyNameConverter))]
class Foo
{
    public int Id { get; set; }
    public string Name { get; set; }

    [JsonPropertyNameByType("Vehicle", typeof(Vehicle))]
    [JsonPropertyNameByType("Profile", typeof(Profile))]
    public object Item { get; set; }
}

Then, serialize as you normally would:

string json = JsonConvert.SerializeObject(foo, Formatting.Indented);

Here is a working demo: https://dotnetfiddle.net/75HwrV

like image 79
Brian Rogers Avatar answered Apr 07 '23 19:04

Brian Rogers