如何反序列化不同类型的集合?

时间:2014-06-15 12:47:24

标签: c# .net json json.net json-deserialization

我有一个看起来像这样的JSON提要(我删除了一些这个例子不需要的字段):

{
  "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"
      }
    }
 ]
}

此Feed中有两种类型的交易:具有recipient的内部交易,以及具有hshrecipient_address的外部交易。

我创建了以下类来容纳这种结构:

UML

因此,我们为所有分页结果(PagedResult)提供了一个基类,其中包含特定的事务实现(TransactionPagedResult)。此结果具有包含0 .. *事务(抽象类Transaction)的集合。它们不属于Transaction类型,但类型为InternalTransactionExternalTransaction,它们是Transaction的实现。

我的问题是如何让JSON.NET处理这个问题。我希望JSON.NET查看它正在解析的当前事务是InternalTransaction还是ExternalTransaction,并将相应类型添加到IEnumerable<Transaction>中的TransactionPagedResult集合。

我创建了自己的JsonConverter,我将其作为属性添加到具有IEnumerable<Transaction>属性的[JsonConverter(typeof(TransactionCreationConverter))],但这不起作用,我收到以下错误:

  

其他信息:从JsonReader读取JObject时出错。当前   JsonReader项不是对象:StartArray。路径'交易',   第1行,第218位。

我理解这是因为JSON.NET尝试反序列化整个集合,但我希望它逐个反序列化集合中的每个对象。

任何?

1 个答案:

答案 0 :(得分:6)

您的问题基本上是this one的副本,解决方案是相同的。您需要JsonConverter来实例化正确的对象。但是,我看到了一些不同之处。

如果从other answer查看转换器实现,可以看到它在JSON中查找布尔标志以确定要实例化的类型。在您的情况下,没有这样的标志,因此您需要使用字段的存在或不存在来做出此决定。此外,JSON中的事务列表实际上是包含事务的对象列表,因此转换器也需要考虑到这一点。

通过这些更改,您的转换器应如下所示:

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

假设您的类定义如下:

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

另外,要回答问题的最后部分,如果使用[JsonConverter]属性装饰列表,转换器应该处理整个列表。要处理单个项目,您需要在列表中使用[JsonProperty(ItemConverterType=typeof(TransactionConverter))]。我已经编辑了上面的类定义,以明确这一点。