1

I am integrating with a third party and the JSON they are returning is throwing me for a loop. I want to deserialize it into a class. but most of the property names can change.

{
  "transaction_id":1,
  "status":"Reviewing",
  "changelog":{
    "2016-Mar-15 10:28 AM":{
      "status":{
        "from":"Approved",
        "to":"Reviewing"
      },
      "total":{
        "from":123.45,
        "to":246.90
      },
      "shipping_address_1":{
        "from":"321 S Main St.",
        "to":"8355 NW 74th St"
      },
      "shipping_city":{
        "from":"New York",
        "to":"Medley"
      },
      "shipping_state":{
        "from":"NY",
        "to":"FL"
      },
      "shipping_postal":{
        "from":"10002",
        "to":"33166"
      }
    }
  }
}

I would like to have a class that is similar to this.

public class TransactionChangeLog
{
    [JsonProperty(PropertyName = "transaction_id")]
    public int TransactionId { get; set; }

    [JsonProperty(PropertyName = "status")]
    public TransactionStatus Status { get; set; }

    [JsonProperty(PropertyName = "changelog")]
    public ICollection<TransactionChange> Changelog { get; set; }
}

public class TransactionChange
{
    // ?? What to do with this.
    public DateTime ChangeDate { get; set; }

    // ?? What to do with this.
    public string Field { get; set; }

    public string From { get; set; }

    public string To { get; set; }
}
0

2 Answers 2

2

There are a couple of approaches you could take to handle this. The first (and simplest) approach is to use nested dictionaries in your TransactionChangeLog class:

public class TransactionChangeLog
{
    [JsonProperty(PropertyName = "transaction_id")]
    public int TransactionId { get; set; }

    [JsonProperty(PropertyName = "status")]
    public TransactionStatus Status { get; set; }

    [JsonProperty(PropertyName = "changelog")]
    public Dictionary<DateTime, Dictionary<string, TransactionChange>> Changelog { get; set; }
}

public class TransactionChange
{
    public string From { get; set; }
    public string To { get; set; }
}

You can then deserialize and dump out the data like this:

TransactionChangeLog changeLog = JsonConvert.DeserializeObject<TransactionChangeLog>(json);

Console.WriteLine("TransactionId: " + changeLog.TransactionId);
Console.WriteLine("TransactionStatus: " + changeLog.Status);
foreach (var dateKvp in changeLog.Changelog)
{
    Console.WriteLine(dateKvp.Key);  // change date
    foreach (var fieldKvp in dateKvp.Value)
    {
        Console.WriteLine("   changed " + fieldKvp.Key + " from '" + fieldKvp.Value.From + "' to '" + fieldKvp.Value.To + "'");
    }
}

Fiddle: https://dotnetfiddle.net/vXNcKi


Although the above will work, it's a little bit awkward to work with the nested dictionaries. Another approach is to use a JsonConverter to handle the deserialization of the varying JSON. This will allow you to use the classes as you defined them in your question. Here is how you might write the converter:

public class ChangeLogConverter : JsonConverter
{
    public override bool CanConvert(Type objectType)
    {
        return objectType == typeof(ICollection<TransactionChange>);
    }

    public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
    {
        List<TransactionChange> changes = new List<TransactionChange>();
        JObject changelog = JObject.Load(reader);
        foreach (JProperty dateProp in changelog.Children<JProperty>())
        {
            DateTime changeDate = DateTime.ParseExact(dateProp.Name, "yyyy-MMM-dd hh:mm tt", CultureInfo.InvariantCulture);
            foreach (JProperty fieldProp in dateProp.Value.Children<JProperty>())
            {
                TransactionChange change = new TransactionChange();
                change.ChangeDate = changeDate;
                change.Field = fieldProp.Name;
                change.From = (string)fieldProp.Value["from"];
                change.To = (string)fieldProp.Value["to"];
                changes.Add(change);
            }
        }
        return changes;
    }

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

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

To use the converter, add a [JsonConverter] attribute to the Changelog property in your TransactionChangeLog class:

public class TransactionChangeLog
{
    [JsonProperty(PropertyName = "transaction_id")]
    public int TransactionId { get; set; }

    [JsonProperty(PropertyName = "status")]
    public TransactionStatus Status { get; set; }

    [JsonProperty(PropertyName = "changelog")]
    [JsonConverter(typeof(ChangeLogConverter))]
    public ICollection<TransactionChange> Changelog { get; set; }
}

You can then deserialize and dump out the data as you normally would:

TransactionChangeLog changeLog = JsonConvert.DeserializeObject<TransactionChangeLog>(json);
Console.WriteLine("TransactionId: " + changeLog.TransactionId);
Console.WriteLine("TransactionStatus: " + changeLog.Status);
foreach (TransactionChange change in changeLog.Changelog)
{
    Console.WriteLine(change.ChangeDate + " - changed " + change.Field + " from '" + change.From + "' to '" + change.To + "'");
}

Fiddle: https://dotnetfiddle.net/1d3pUa

Sign up to request clarification or add additional context in comments.

2 Comments

Brian. Thanks for the well detailed example. this was what I was looking for.
No problem; glad I could help.
0

If I understand you correctly, the content of the changelog can vary, and you don't know the possible property names at compile time.

If that is the case, you need to deserialize to something which is not statically typed, for instance the JObject which can be used for LINQ-style querying on the data afterwards.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.