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

10

我有一个 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"
      }
    }
 ]
}
在该源中,有两种交易类型:内部交易由 recipient 标识,外部交易由 hshrecipient_address 标识。为了处理这种结构,我创建了以下类:PagedResult 作为所有页面结果的基类,TransactionPagedResult 作为特定交易结果的实现类。该结果包含一个集合,其中包含0到多个交易(抽象类Transaction)。它们不属于 Transaction 类型,而是属于Transaction 的实现类 InternalTransactionExternalTransaction。我的问题是如何让 JSON.NET 处理此问题。我希望 JSON.NET 能够判断当前要解析的交易是 InternalTransaction 还是 ExternalTransaction,并将相应类型添加到 TransactionPagedResult 中的 IEnumerable<Transaction> 集合中。我已经创建了自己的 JsonConverter,并将其作为属性添加到 IEnumerable<Transaction> 中,并使用 [JsonConverter(typeof(TransactionCreationConverter))] 特性进行标注,但是这没有起作用,我得到了以下错误消息:“Error reading JObject from JsonReader. Current JsonReader item is not an object: StartArray. Path 'transactions', line 1, position 218.” 我理解这是因为 JSON.NET 尝试反序列化整个集合,但我希望它逐个反序列化集合中的每个对象。请问如何实现?

是的,我没有添加那个,因为我不想让帖子变得太长。我收到一个错误,JsonReader项不是对象而是数组(这是正确的,因为我将属性放在IEnumerable集合上)。但是我希望JsonConverter查看IEnumerable中的每个单独项,它们都是对象。 - Leon Cullens
@Jack,我觉得你链接到了错误的帖子? - Leon Cullens
@LeonCullens:已经修复了。 :) - Jack
这可能会有所帮助:https://dev59.com/CWw05IYBdhLWcg3wrDtS - jdehlin
显示剩余3条评论
1个回答

7
您的问题实际上是这个问题的重复,解决方案也是相同的。您需要一个JsonConverter来实例化正确的对象。然而,我看到有几个不同之处。
如果您查看其他答案中的转换器实现,您会发现它会查找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))]。我已经编辑了上面的类定义以使其更加清晰明了。

太棒了!这就解决了问题 :) 非常感谢你的示例和解释! :) - Leon Cullens
没问题,很高兴能帮助到你。 - Brian Rogers

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接