j0w
j0w

Reputation: 545

Is using an "object" property a proper thing to do?

I have to get back an object the frontend is sending me. Thing is, front team wanted their component to be as generic as it can be so, in the dto, i'll recieve a Value property that can be different things (a boolean, a string, a list a strings, a numeric value...), as following :

"conditions": [
        {
          "alias": "FSTNM",
          "providerKey": "Marketing",
          "hasValue": true,
          "conditionType": "Text",
          "values": "john",
          "startDate": null,
          "endDate": null
        },
        {
          "alias": "LSTNM",
          "providerKey": "Marketing",
          "hasValue": true,
          "conditionType": "Text",
          "values": null,
          "startDate": null,
          "endDate": null
        },
        {
          "alias": "BTHDT",
          "providerKey": "Marketing",
          "hasValue": true,
          "conditionType": "DateTime",
          "values": null,
          "startDate": "02-10-1980",
          "endDate": "17-08-1989"
        },
        {
          "alias": "AMECH",
          "providerKey": "Custom",
          "hasValue": true,
          "conditionType": "Boolean",
          "values": true,
          "startDate": null,
          "endDate": null
        },
        {
          "alias": "CMBCH",
          "providerKey": "Custom",
          "hasValue": true,
          "conditionType": "Number",
          "values": 2,
          "startDate": null,
          "endDate": null
        },
         {
          "alias": "FVRDR",
          "providerKey": "Custom",
          "hasValue": true,
          "conditionType": "List",
          "values": [
            1,
            3
          ],
          "startDate": null,
          "endDate": null
        }

      ]

So, I tried to set the Values property as object like this :

 public class DataTableFilterValueDTO
    {            
        public string Alias { get; set; }
        public string ProviderKey { get; set; }
        public bool HasValue { get; set; }
        public string ConditionType { get; set; }
        public object Values { get; set; }
        public string StartDate { get; set; }
        public string EndDate { get; set; }       
    }

It seems that it works. When the request reack my controller, Values seems to be the right type, and depending on conditionType, I can cast it to the object i'm supposed to retrieve, like this :

If conditionType = "Text" :

var values = condition.Values as string;

If conditionType = "Boolean" :

var values = condition.Values as bool;

If conditionType = "List" :

var values = condition.Values as List<string>;

But is this a thing to do? I mean, it does not feel right using this but I've never worked with the objecttype and can't really tell when it's good to use it.

Upvotes: 1

Views: 62

Answers (1)

Innat3
Innat3

Reputation: 3576

You could implement a Custom JsonConverter. To do this, declare your class as abstract, and define a class that inherits it for each data type you expect to receive, like so:

[JsonConverter(typeof(ValueConverter))]
public abstract class Value
{
    public string Alias { get; set; }
    public string ProviderKey { get; set; }
    public bool HasValue { get; set; }
    public string ConditionType { get; set; }
    public string StartDate { get; set; }
    public string EndDate { get; set; }
}

public class BooleanValue : Value { public bool? Values { get; set; } }

public class ListValue : Value { public List<string> Values { get; set; } }

public class StringValue : Value { public string Values { get; set; } }

public class DateTimeValue : Value { public DateTime? Values { get; set; } }

public class IntegerValue : Value { public int? Values { get; set; } }

Then you need to define your custom JsonConverter where you perform the type conversion according to the value of the conditionType property:

public class ValueConverter : JsonConverter
{
    static readonly JsonSerializerSettings SpecifiedSubclassConversion  = new JsonSerializerSettings() { ContractResolver = new CustomResolver() };

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

    public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
    {
        JObject obj = JObject.Load(reader);
        return (obj["conditionType"].Value<string>()) switch
        {
            "Text" => JsonConvert.DeserializeObject<StringValue>(obj.ToString(), SpecifiedSubclassConversion),
            "DateTime" => JsonConvert.DeserializeObject<DateTimeValue>(obj.ToString(), SpecifiedSubclassConversion),
            "Boolean" => JsonConvert.DeserializeObject<BooleanValue>(obj.ToString(), SpecifiedSubclassConversion),
            "Number" => JsonConvert.DeserializeObject<IntegerValue>(obj.ToString(), SpecifiedSubclassConversion),
            "List" => JsonConvert.DeserializeObject<ListValue>(obj.ToString(), SpecifiedSubclassConversion),
            _ => throw new Exception("Unknown conditionType"),
        };
        throw new NotImplementedException();
    }

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

    public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
    {
        throw new NotImplementedException();
    }
}

public class CustomResolver: DefaultContractResolver
{
    protected override JsonConverter ResolveContractConverter(Type objectType)
    {
        if (typeof(Value).IsAssignableFrom(objectType) && !objectType.IsAbstract)
            return null;

        return base.ResolveContractConverter(objectType);
    }
}

And finally the usage:

foreach (var value in JsonConvert.DeserializeObject<List<Value>>(json))
{
    if (value is StringValue)
    {
        string s = ((StringValue)value).Values;
    }
}

Upvotes: 1

Related Questions