【问题标题】:Flatten JSON data展平 JSON 数据
【发布时间】:2019-06-25 12:07:14
【问题描述】:

我正在尝试使用 Tabulator 创建票证列表,数据通过 AJAX url 从票证系统导入为 JSON,如下所示。

{
    "results": [
        {
            "cc_emails": [
                "ram@freshdesk.com",
                "diana@freshdesk.com"
            ],
            "fwd_emails": [],
            "reply_cc_emails": [
                "ram@freshdesk.com",
                "diana@freshdesk.com"
            ],
            "ticket_cc_emails": [
                "ram@freshdesk.com",
                "diana@freshdesk.com"
            ],
            "fr_escalated": false,
            "spam": false,
            "email_config_id": null,
            "group_id": 35000204315,
            "priority": 1,
            "requester_id": 35020281588,
            "responder_id": 35004154466,
            "source": 2,
            "company_id": null,
            "status": 2,
            "subject": "Support Needed...",
            "association_type": null,
            "to_emails": null,
            "product_id": null,
            "id": 188261,
            "type": null,
            "due_by": "2019-09-17T15:12:07Z",
            "fr_due_by": "2019-07-01T15:12:07Z",
            "is_escalated": false,
            "description": "<div>Details about the issue...</div>",
            "description_text": "Details about the issue...",
            "custom_fields": {
                "cf_category": null,
                "cf_firstname": null,
                "cf_surname": null,
                "cf_user_trainging": null,
                "cf_email_address": null,
                "cf_office_365": null,
                "cf_start_date": null,
                "cf_permission_level": null,
                "cf_hardware_type": null,
                "cf_additional_information_specsoftware_etc": null,
                "cf_vpn_access_required": false,
                "cf_securitydistribution_group_membership": null,
                "cf_mapped_network_driveslogin_script": null,
                "cf_printers": null,
                "cf_phone_extension": null,
                "cf_ddi": null,
                "cf_phone_group_membership": null,
                "cf_user_who_requires_the_equipment": null,
                "cf_requirment_date": null,
                "cf_correctclosureused": null,
                "cf_location": "A1"
            },
            "created_at": "2019-06-24T15:11:47Z",
            "updated_at": "2019-06-24T15:59:00Z",
            "associated_tickets_count": null,
            "tags": []
        }
    ],
    "total": 1
}

问题是“custom_fields”是主要 JSON 对象内的 JSON 对象,有没有办法将这些数据展平并将其显示为 Tabulator 中的所有一行?任何帮助表示赞赏?

我在 Tabulator 中的当前结果是它为 custom_fields 列返回 [object Object]。我希望能够看到行中的每个 custom_fields。

【问题讨论】:

  • 为什么结果是一个长度 == 1 的数组?它总是长度为 1 的数组吗?
  • 我从未使用过tabulator,但经过一番搜索:Complex JSON Object? #223nested data #125
  • 看看您如何处理这些数据来设置表格列等会很有趣。
  • 只是为了演示它唯一的一个结果,是的,我已经研究过它们,发现它们现在都相当老了,想知道是否有新的解决方案,我在 Repo 上找不到任何东西以获得更新的回复,因此在此处创建问题。
  • 如果嵌套的 json 对象的键已经存在于父对象中会怎样?

标签: javascript json tabulator


【解决方案1】:

处理嵌套数据

不需要展平对象,Tabulator 可以处理列的嵌套数据,如果在字段名称中使用点表示法:

var table = new Tabulator("#example-table", {
    columns:[
        {title:"Category", field:"custom_fields.cf_category"},  //link column to nested field
    ],
});

有关嵌套数据处理的完整详细信息,请参阅Columns Documentation

列分组

如果您愿意,您还可以使用列分组来显示字段是另一个属性的子集,例如,我们可以像往常一样定义顶级列,然后添加列组来保存自定义列

var table = new Tabulator("#example-table", {
    columns:[
        {title:"Subject", field:"subject"},  //standard column
        {title:"Priorty", field:"priority"}, //standard column
        {title:"Custom", columns:[ //column group to hold columns in custom_fields property
            {title:"Category", field:"custom_fields.cf_category"}, 
            {title:"First Name", field:"custom_fields.cf_firstname"}, 
        ]},
    ],
});

详情请见Column Grouping Documentation

【讨论】:

    【解决方案2】:

    如果您使用的是 es6+,您可以通过在对象解构和对象传播中使用 rest 轻松实现这一点。

    const input =  {
        "results": [
            {
                "custom_fields": {...},
                ...
            }
        ],
        "total": 1
    }
    
    const expanded = input.results.map(result => {
       const { custom_fields, ...rest } = result;
       return { ...rest, ...custom_fields };
    })
    

    【讨论】:

      【解决方案3】:

      这是一个稍微不同的解决方案,它依靠函数生成器来遍历原始对象,从而最终检测到一些重复的键。

      当然,可以通过添加进一步检查来更改此示例(例如,您是否要遍历主对象内的所有对象等等)。

      当前示例处理:

      • 通过排除原语和数组来遍历原始对象。
      • 提供一个 flattenObject 方法,该方法接受一个对象作为参数,并接受一个回调作为最终的第二个参数,当遇到重复键时将引发该参数。在这种情况下,默认行为是将“下一个”嵌套值作为新值。如果在回调中返回false,则保留当前值。回调将提供新值的键和值。

      因此,简而言之,获得所需结果的“真实”代码是这样的:

      // Case usage:
      // Map the existing values.
      input.results = input.results.map(i => flattenObject(i, (duplicatedKeyValuePair) => {
        return false; // <-- keep the existing value if a duplicate is matched.
      }));
      console.log(input.results)
      

      当然,它比仅仅扁平化所需的属性稍微复杂一些,但我想给它一个更有弹性的味道。

      const input = {
          "results": [
              {
                  "cc_emails": [
                      "ram@freshdesk.com",
                      "diana@freshdesk.com"
                  ],
                  "fwd_emails": [],
                  "reply_cc_emails": [
                      "ram@freshdesk.com",
                      "diana@freshdesk.com"
                  ],
                  "ticket_cc_emails": [
                      "ram@freshdesk.com",
                      "diana@freshdesk.com"
                  ],
                  "fr_escalated": false,
                  "spam": false,
                  "email_config_id": null,
                  "group_id": 35000204315,
                  "priority": 1,
                  "requester_id": 35020281588,
                  "responder_id": 35004154466,
                  "source": 2,
                  "company_id": null,
                  "status": 2,
                  "subject": "Support Needed...",
                  "association_type": null,
                  "to_emails": null,
                  "product_id": null,
                  "id": 188261,
                  "type": null,
                  "due_by": "2019-09-17T15:12:07Z",
                  "fr_due_by": "2019-07-01T15:12:07Z",
                  "is_escalated": false,
                  "description": "<div>Details about the issue...</div>",
                  "description_text": "Details about the issue...",
                  "test_duplicated_key": "hello! I should keep this!",
                  "custom_fields": {
                      "cf_category": null,
                      "cf_firstname": null,
                      "cf_surname": null,
                      "cf_user_trainging": null,
                      "cf_email_address": null,
                      "cf_office_365": null,
                      "cf_start_date": null,
                      "cf_permission_level": null,
                      "cf_hardware_type": null,
                      "cf_additional_information_specsoftware_etc": null,
                      "cf_vpn_access_required": false,
                      "cf_securitydistribution_group_membership": null,
                      "cf_mapped_network_driveslogin_script": null,
                      "cf_printers": null,
                      "cf_phone_extension": null,
                      "cf_ddi": null,
                      "cf_phone_group_membership": null,
                      "cf_user_who_requires_the_equipment": null,
                      "cf_requirment_date": null,
                      "cf_correctclosureused": null,
                      "cf_location": "A1",
                      "test_duplicated_key": "You should not see that."
                  },
                  "created_at": "2019-06-24T15:11:47Z",
                  "updated_at": "2019-06-24T15:59:00Z",
                  "associated_tickets_count": null,
                  "tags": []
              }
          ],
          "total": 1
      }
      
      /**
        Traverse every property of the desired object, by returning the currently key-value pair looped. If the value is an object, it keeps traversing.
      */
      function* traverseObject(obj) {
        for ([key, value] of Object.entries(obj)) {
          if (value && typeof(value) === 'object' && !Array.isArray(value)) {
            yield* traverseObject(obj[key]);
          }
          else yield {key: key, value: value};
        }
      }
      
      /**
        Flattens the object by traversing every object inside it.
      */
      function flattenObject(obj, onDuplicatedKey) {
        let res = {};
        for (keyValuePair of traverseObject(obj)) {
          let add = true;
          if (res.hasOwnProperty(keyValuePair.key)) {
            add = onDuplicatedKey ? onDuplicatedKey.call(onDuplicatedKey, keyValuePair) : true; // default behavior: override with nested propeties.
          }
          if (add) res[keyValuePair.key] = keyValuePair.value;
        }
        return res;
      }
      
      /*
      Sample usage.
      const flattened = flattenObject(input.results[0], (record) => {
        console.log('detected key value pair duplicate. Key:', record.key, ' value: ', record.value);
        // true will override the value, false will not override the value.
        return false;
      });
      */
      //console.log(flattened);
      
      // Case usage:
      // Map the existing values.
      input.results = input.results.map(i => flattenObject(i, (duplicatedKeyValuePair) => {
        return false; // <-- keep the existing value if a duplicate is matched.
      }));
      console.log(input.results);

      请注意,上述案例只是一个示例,我没有花太多时间测试每种属性类型,因此(当然)可以对其进行审查,并且可以提高代码质量和性能。这只是一个示例,展示了依赖于不同运算符和逻辑的不同方法。

      作为(最后的)旁注,我认为您可以使用制表符以某种方式处理它,但我不确定您是否可以从单个属性中渲染多个列,这使我相信更改原始对象可能是理想的解决方案。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2022-01-16
        • 2021-07-23
        • 1970-01-01
        • 1970-01-01
        • 2016-01-18
        • 2021-02-17
        • 2023-03-07
        • 1970-01-01
        相关资源
        最近更新 更多