Vimukthi Guruge
Vimukthi Guruge

Reputation: 285

C# Convert Json to object with duplicated property name with different data type

i have a json string with duplicated property name with different data types. if i removed all duplicated other data types, it will work fine.

{
   "data":[
      {
         "ctr":0,
         "spend":11839.8600,
         "clicks":6402
      },
      {
         "ctr":0,
         "spend":12320.5000,
         "clicks":5789
      },
      {
         "clicks":{
            "value":13156.0,
            "prior_year":0.0,
            "prior_month":14122.0,
            "prior_month_perc":0.0684039087947882736156351792,
            "prior_year_perc":0.0
         }
      }
   ],
   "timing":null,
   "warnings":[

   ],
   "success":true
}

here is the my model class

  public class MyTestModel
    {
        public int? ctr { get; set; }
        public decimal? spend { get; set; }
        public int? clicks { get; set; }
    }

if i removed this snipped json part, program will work.

        {
         "clicks":{
            "value":13156.0,
            "prior_year":0.0,
            "prior_month":14122.0,
            "prior_month_perc":0.0684039087947882736156351792,
            "prior_year_perc":0.0
         }

are there any method to stop binding unsupported types to model property.

Upvotes: 0

Views: 606

Answers (2)

as-if-i-code
as-if-i-code

Reputation: 2340

I assume you are using JsonConvert.DeserializeObject to deserialize your json string.
While doing so, the data type missmatch causes error and you can safely handle this error using JsonSerializerSettings as below

MyTestModel result = JsonConvert.DeserializeObject<MyTestModel>("{json string}", 
                                                           new JsonSerializerSettings {
                                                              Error = MethodToHandleError
                                                           });

and then define MethodToHandleError something like below. This will be called whenever deserialization error occurs:

public void MethodToHandleError(object sender, ErrorEventArgs args)
{
    // log error message -> args.ErrorContext.Error.Message
    // you can also write your own logic here
    args.ErrorContext.Handled = true;
}

Upvotes: 0

Neil
Neil

Reputation: 11889

You can use a JsonConverter to support either/both types in a 'union' class.

public partial class ClicksClass
{
    [JsonProperty("value")]
    public long Value { get; set; }

    [JsonProperty("prior_year")]
    public long PriorYear { get; set; }

    [JsonProperty("prior_month")]
    public long PriorMonth { get; set; }

    [JsonProperty("prior_month_perc")]
    public double PriorMonthPerc { get; set; }

    [JsonProperty("prior_year_perc")]
    public long PriorYearPerc { get; set; }
}

public partial struct ClicksUnion
{
    public ClicksClass ClicksClass;
    public long? Integer;

    public static implicit operator ClicksUnion(ClicksClass ClicksClass) => new ClicksUnion { ClicksClass = ClicksClass };
    public static implicit operator ClicksUnion(long Integer) => new ClicksUnion { Integer = Integer };
}

internal static class Converter
{
    public static readonly JsonSerializerSettings Settings = new JsonSerializerSettings
    {
        MetadataPropertyHandling = MetadataPropertyHandling.Ignore,
        DateParseHandling = DateParseHandling.None,
        Converters =
        {
            ClicksUnionConverter.Singleton,
            new IsoDateTimeConverter { DateTimeStyles = DateTimeStyles.AssumeUniversal }
        },
    };
}

internal class ClicksUnionConverter : JsonConverter
{
    public override bool CanConvert(Type t) => t == typeof(ClicksUnion) || t == typeof(ClicksUnion?);

    public override object ReadJson(JsonReader reader, Type t, object existingValue, JsonSerializer serializer)
    {
        switch (reader.TokenType)
        {
            case JsonToken.Integer:
                var integerValue = serializer.Deserialize<long>(reader);
                return new ClicksUnion { Integer = integerValue };
            case JsonToken.StartObject:
                var objectValue = serializer.Deserialize<ClicksClass>(reader);
                return new ClicksUnion { ClicksClass = objectValue };
        }
        throw new Exception("Cannot unmarshal type ClicksUnion");
    }

    public override void WriteJson(JsonWriter writer, object untypedValue, JsonSerializer serializer)
    {
        var value = (ClicksUnion)untypedValue;
        if (value.Integer != null)
        {
            serializer.Serialize(writer, value.Integer.Value);
            return;
        }
        if (value.ClicksClass != null)
        {
            serializer.Serialize(writer, value.ClicksClass);
            return;
        }
        throw new Exception("Cannot marshal type ClicksUnion");
    }

    public static readonly ClicksUnionConverter Singleton = new ClicksUnionConverter();
}

This means that, once parsed, whenever you get to the ClickUnion instances, you can check which field is not null, and that is the one you use. It depends on your usecase of course.

BTW, I used this web site to write the classes from the json

Upvotes: 2

Related Questions