How to deserialize a collection with different types?

I have a JSON channel that looks like this (I deleted some fields that are not needed 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": "user2@example.com" }, "recipient": { "id": "5011f33df8182b142400000a", "name": "User One", "email": "user1@example.com" } } }, { "transaction": { "id": "5018f833f8182b129c00002e", "created_at": "2012-08-01T02:36:43-07:00", "hsh": "9d6a7d1112c3db9de5315b421a5153d71413f5f752aff75bf504b77df4e646a3", "sender": { "id": "5011f33df8182b142400000e", "name": "User Two", "email": "user2@example.com" }, "recipient_address": "37muSN5ZrukVTvyVh3mT5Zc5ew9L9CBare" } } ] } 

There are two types of transactions in this feed: internal transactions with recipient and external transactions with hsh and recipient_address .

I created the following classes to accommodate this structure:

UML

So, we have a base class for all paged results ( PagedResult ) with a specific transaction implementation ( TransactionPagedResult ). This result contains a collection containing 0 .. * transactions (abstract Transaction class). They are not of type Transaction , but of type InternalTransaction or ExternalTransaction , which are Transaction implementations.

My question is how can I let JSON.NET handle this. I want JSON.NET to find out if the current transaction is the InternalTransaction or ExternalTransaction syntax, and add the appropriate type to the IEnumerable<Transaction> collection in TransactionPagedResult .

I created my own JsonConverter, which I added as a property for IEnumerable<Transaction> with the attribute [JsonConverter(typeof(TransactionCreationConverter))] , but this did not work, I get the following error:

Additional Information: Error reading JObject from JsonReader. The current JsonReader Element is not an object: StartArray. The "transaction" path, line 1, position 218.

I understand this because JSON.NET is trying to deserialize the entire collection, but I want it to deserialize each object within the collection one at a time.

Is anyone

+8
json c # json-deserialization
source share
1 answer

Your question is essentially a duplicate of this , and the solution remains the same. To create the correct object you will need a JsonConverter . However, there are a few differences that I see.

If you look at the implementation of the converter from another answer , you will see that it looks for a logical flag in JSON to determine the type to instantiate. In your case there is no such flag, so you will need to use the presence or absence of a field for this definition. In addition, your list of transactions in JSON is actually a list of objects that contain transactions, so the converter should also take this into account.

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 your classes are defined as follows:

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

In addition, to answer the last part of your question, if you decorate your list with the [JsonConverter] attribute, the converter is expected to process the entire list. To handle individual items, you need to use [JsonProperty(ItemConverterType=typeof(TransactionConverter))] in the list instead. I edited the class definitions above to make this clear.

+6
source share

All Articles