如何将动态 json 属性 反序列化为对象?

How do I deserialize a dynamic json property to object?

我正在尝试将动态 JSON(从 API)反序列化为正确的对象,但是有些项目没有类型。在示例 JSON 中,"fulfillment" 属性 具有值 "F1" 和 "F2" 并且可能有更多(问题一)。其中,item properties有产品订单信息,但没有item type,以产品名称开头(即“03.64.0005_11_10”)可以有上千个选项(问题二)。 如何反序列化此 JSON 以填充正确的对象?我尝试了 RestCharp,Json.net,但我总是卡在无法动态读取和填充的产品 属性 上。

我尝试了以下答案,但没有成功:

How I deserialize a dynamic json property with RestSharp in C#? Deserialize JSON into C# dynamic object?

你能帮帮我吗?

  "billingAddress": {
    "zip": "64001340",
    "state": "PI",
    "number": "3443",
    "status": "ACTIVE",
    "firstName": "Fulano",
    "telephone": {
      "type": "billing",
      "number": "88112244"
    },
    "neighbourhood": "Centro"
  },
  "clientId": "cliente3",
  "documents": [
    {
      "type": "cpf",
      "number": "12345678901"
    }
  ],
  "fulfillments": {
    "F1": {
      "id": "F1",
      "orderId": "4017116",
      "channelId": "channel2",
      "clientId": "cliente3",
      "locationId": "708",
      "shipment": {
        "method": "Economica",
        "carrierName": "Transportadora"
      },
      "status": "CANCELED",
      "type": "SHIPMENT",
      "enablePrePicking": false,
      "items": {
        "03.64.0005_11_10": {
          "sku": "03.64.0005_11_10",
          "quantity": 0,
          "stockType": "PHYSICAL",
          "orderedQuantity": 1,
          "returnedQuantity": 0,
          "canceledQuantity": 1,
          "itemType": "OTHER",
          "presale": false,
          "enablePicking": true
        },
        "18.06.0220_48_2": {
          "sku": "18.06.0220_48_2",
          "quantity": 0,
          "stockType": "PHYSICAL",
          "orderedQuantity": 1,
          "returnedQuantity": 0,
          "canceledQuantity": 1,
          "itemType": "OTHER",
          "presale": false,
          "enablePicking": true
        }
      }
    },
    "F2": {
      "id": "F2",
      "orderId": "4017116",
      "channelId": "channel2",
      "clientId": "cliente3",
      "locationId": "003",
      "operator": {
        "id": "5188",
        "name": "Loja da Vila"
      },
      "ownership": "oms",
      "shipment": {
        "method": "Economica",
        "carrierName": "Transportadora"
      },
      "status": "SHIPPING_READY",
      "type": "SHIPMENT",
      "enablePrePicking": true,
      "items": {
        "18.04.1465_01_3": {
          "sku": "18.04.1465_01_3",
          "quantity": 1,
          "stockType": "PHYSICAL",
          "orderedQuantity": 1,
          "returnedQuantity": 0,
          "canceledQuantity": 0,
          "itemType": "OTHER",
          "presale": false,
          "enablePicking": true
        },
        "18.16.0630_13_10": {
          "sku": "18.16.0630_13_10",
          "quantity": 1,
          "stockType": "PHYSICAL",
          "orderedQuantity": 1,
          "returnedQuantity": 0,
          "canceledQuantity": 0,
          "itemType": "OTHER",
          "presale": false,
          "enablePicking": true
        }
      }
    }
  },
  "createdAt": "2019-06-08T21:41:12.000Z",
  "updatedAt": "2019-06-08T21:41:12.000Z"
}

To

public class BillingAddress
{
    public string zip { get; set; }
    public string state { get; set; }
    public string number { get; set; }
    public string status { get; set; }
    public string firstName { get; set; }
    public Telephone telephone { get; set; }
    public string neighbourhood { get; set; }
}

public class Fulfillment
{
    public string id { get; set; }
    public string orderId { get; set; }
    public string channelId { get; set; }
    public string clientId { get; set; }
    public string locationId { get; set; }
    public Shipment shipment { get; set; }
    public string status { get; set; }
    public string type { get; set; }
    public bool enablePrePicking { get; set; }
    public List<Item> items { get; set; }
}

public class Item
{
    public string sku { get; set; }
    public int quantity { get; set; }
    public string stockType { get; set; }
    public int orderedQuantity { get; set; }
    public int returnedQuantity { get; set; }
    public int canceledQuantity { get; set; }
    public string itemType { get; set; }
    public bool presale { get; set; }
    public bool enablePicking { get; set; }
}

是的,该结构确实不适合 JSON。看起来 fulfillments 属性 应该是这些对象的数组,而不是具有那些编号的属性。看起来这是从 EDI 文件或其他文件中 auto-generated 生成的,即使大多数好的转换工具都足够聪明,不会这样做。

选项 A:查看为您生成该文件的人是否可以更正他们的过程。

选项 B:如果这不可能,请将您的履行 属性 设为字典类型,其中履行是 class 您拥有的内部履行对象。然后将其反序列化 "properly" 并为您提供一个字典,您可以使用 "F1" 键引用直到 "FN" 但理想情况下,您会在使用字典时从字典中创建一个列表或数组。即使顺序很重要,您也始终可以使用 id 字段进行排序。

// Property on you deserialization object
public Dictionary<string, Fullfillment> fulfillmentDictionary {get; set;}

// Creating the list for easier use of the data
List<Fullfillment> fulfillments = fulfillmentDictionary.Values.ToList();

类似的逻辑将适用于您的项目列表。

如果问题只是键是动态的,而那些 dynamically-keyed 对象的结构是 well-defined,那么您可以使用 Dictionary<string, T>(而不是 List<T>) 来处理动态键。从你的样本 JSON 看来是这样的。因此,您需要根级别的 fulfillmentsfulfillments 中的 items 的字典。您的 classes 应如下所示:

public class RootObject
{
    public BillingAddress billingAddress { get; set; }
    public string clientId { get; set; }
    public List<Document> documents { get; set; }
    public Dictionary<string, Fulfillment> fulfillments { get; set; }
    public DateTime createdAt { get; set; }
    public DateTime updatedAt { get; set; }
}

public class BillingAddress
{
    public string zip { get; set; }
    public string state { get; set; }
    public string number { get; set; }
    public string status { get; set; }
    public string firstName { get; set; }
    public Telephone telephone { get; set; }
    public string neighbourhood { get; set; }
}

public class Telephone
{
    public string type { get; set; }
    public string number { get; set; }
}

public class Document
{
    public string type { get; set; }
    public string number { get; set; }
}

public class Fulfillment
{
    public string id { get; set; }
    public string orderId { get; set; }
    public string channelId { get; set; }
    public string clientId { get; set; }
    public string locationId { get; set; }
    public Operator @operator { get; set; }
    public string ownership { get; set; }
    public Shipment shipment { get; set; }
    public string status { get; set; }
    public string type { get; set; }
    public bool enablePrePicking { get; set; }
    public Dictionary<string, Item> items { get; set; }
}

public class Operator
{
    public string id { get; set; }
    public string name { get; set; }
}

public class Shipment
{
    public string method { get; set; }
    public string carrierName { get; set; }
}

public class Item
{
    public string sku { get; set; }
    public int quantity { get; set; }
    public string stockType { get; set; }
    public int orderedQuantity { get; set; }
    public int returnedQuantity { get; set; }
    public int canceledQuantity { get; set; }
    public string itemType { get; set; }
    public bool presale { get; set; }
    public bool enablePicking { get; set; }
}

然后将JSON反序列化为RootObject class:

var root = JsonConvert.DeserializeObject<RootObject>(json);

这是一个工作演示:https://dotnetfiddle.net/xReEQh

对于 JSON 具有动态键的消息(即消息之间的变化),您应该从解码为动态 C# 对象开始。一个解码(大多数 JSON 解析器支持它)你枚举每个动态 属性,然后将其值转换为 POCO,如 Fulfillment、Item 等(或者继续使用动态对象)。

这是一个适用于您的 JSON https://dotnetfiddle.net/U5NfzC

的示例