Matt M
Matt M

Reputation: 662

Json.Net deserialize and serialize properties with names that change

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; }
}

Upvotes: 1

Views: 1511

Answers (2)

Brian Rogers
Brian Rogers

Reputation: 129807

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

Upvotes: 2

Lucero
Lucero

Reputation: 60276

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.

Upvotes: 0

Related Questions