I have a JSON feed that looks like this (I removed some fields that aren't necessary for this example):
{
"total_count": 2,
"num_pages": 1,
"current_page": 1,
"balance": {
"amount": "0.00001199",
"currency": "BTC"
},
"transactions": [
{
"transaction": {
"id": "5018f833f8182b129c00002f",
"created_at": "2012-08-01T02:34:43-07:00",
"sender": {
"id": "5011f33df8182b142400000e",
"name": "User Two",
"email": "[email protected]"
},
"recipient": {
"id": "5011f33df8182b142400000a",
"name": "User One",
"email": "[email protected]"
}
}
},
{
"transaction": {
"id": "5018f833f8182b129c00002e",
"created_at": "2012-08-01T02:36:43-07:00",
"hsh": "9d6a7d1112c3db9de5315b421a5153d71413f5f752aff75bf504b77df4e646a3",
"sender": {
"id": "5011f33df8182b142400000e",
"name": "User Two",
"email": "[email protected]"
},
"recipient_address": "37muSN5ZrukVTvyVh3mT5Zc5ew9L9CBare"
}
}
]
}
There are two types of transactions in this feed: internal transactions that have a recipient
, and external transactions that have a hsh
and recipient_address
.
I created the following classes to accomodate this structure:
So we have a base class for all paged results (PagedResult
) with a specific implementation for transactions (TransactionPagedResult
). This result has a collection containing 0..* transactions (abstract class Transaction
). They're not of the type Transaction
though, but of type InternalTransaction
or ExternalTransaction
which are implementations of Transaction
.
My question is how I can let JSON.NET handle this. I want JSON.NET to see whether the current transaction it's parsing is an InternalTransaction
or an ExternalTransaction
, and add the according type to the IEnumerable<Transaction>
collection in TransactionPagedResult
.
I created my own JsonConverter that I added as a property to the IEnumerable<Transaction>
with the [JsonConverter(typeof(TransactionCreationConverter))]
attribute, but this didn't work, I get the following error:
Additional information: Error reading JObject from JsonReader. Current JsonReader item is not an object: StartArray. Path 'transactions', line 1, position 218.
I understand this is because JSON.NET tries to deserialize the whole collection, but I want it to deserialize each object inside the collection one by one.
Anyone?
Your question is essentially a duplicate of this one, and the solution is the same. You need a JsonConverter
to instantiate the correct object. However, there are a couple of differences that I see.
If you look at the converter implementation from the other answer, you can see that it looks for a boolean flag in the JSON to determine the type to instantiate. In your case, there is not such a flag, so you'd need to use the existence or absence of a field to make this determination. Also, your list of transactions in the JSON is actually a list of objects that contain transactions, so the converter needs to account for that as well.
With these changes, your converter should look something like this:
public class TransactionConverter : JsonConverter
{
public override bool CanConvert(Type objectType)
{
return typeof(Transaction).IsAssignableFrom(objectType);
}
public override object ReadJson(JsonReader reader,
Type objectType, object existingValue, JsonSerializer serializer)
{
JToken transaction = JToken.Load(reader)["transaction"];
if (transaction["recipient"] != null)
{
return transaction.ToObject<InternalTransaction>();
}
else
{
return transaction.ToObject<ExternalTransaction>();
}
}
public override void WriteJson(JsonWriter writer,
object value, JsonSerializer serializer)
{
throw new NotImplementedException();
}
}
assuming that your classes are defined like this:
class TransactionPagedResult
{
[JsonProperty(ItemConverterType=typeof(TransactionConverter))]
public IEnumerable<Transaction> Transactions { get; set; }
}
class Transaction
{
public string Id { get; set; }
[JsonProperty("created_at")]
public DateTime CreatedAt { get; set; }
}
class InternalTransaction : Transaction
{
public User Recipient { get; set; }
}
class ExternalTransaction : Transaction
{
public string Hsh { get; set; }
[JsonProperty("recipient_address")]
public string RecipientAddress { get; set; }
}
class User
{
public string Id { get; set; }
public string Name { get; set; }
public string Email { get; set; }
}
Also, to answer the last part of your question, if you decorate your list with a [JsonConverter]
attribute, the converter is expected to handle the entire list. To handle the individual items, you need to use [JsonProperty(ItemConverterType=typeof(TransactionConverter))]
on the list instead. I've edited the class definitions above to make this clear.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With