Reputation: 662
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
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
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