4

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".

Brian Rogers
  • 125,747
  • 31
  • 299
  • 300
csharpgirl
  • 107
  • 3
  • 9
  • 2
    Can you please expand your question into a [mcve] showing the JSON you want to create and the type(s) you are serializing? See [ask]. With just this information we would be guessing what your problem is. – dbc Jun 15 '17 at 22:16
  • Json.Net == Newtonsoft.Json - there is no **or** – Sir Rufo Jun 15 '17 at 22:21
  • Are you looking for [JsonPropertyAttribute](http://www.newtonsoft.com/json/help/html/T_Newtonsoft_Json_JsonPropertyAttribute.htm)? – Sir Rufo Jun 15 '17 at 22:24
  • @SirRufo thanks for the correction. JsonPropertyAttribute actually changes the name is given in the constructor, but I need to change the property name depending on the type of the property. – csharpgirl Jun 15 '17 at 22:43
  • I *suspect* you'd need to look into custom serializers, custom converters, and/or custom contract resolvers here.... – Marc Gravell Jun 15 '17 at 23:05

1 Answers1

7

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

Brian Rogers
  • 125,747
  • 31
  • 299
  • 300