【问题标题】:Converting JSON field with array index as keys into an array field以数组索引为键的 JSON 字段转换为数组字段
【发布时间】:2021-05-15 02:12:49
【问题描述】:

我有一种情况,我需要从服务器 (SA) 获取信息,而该服务器又从另一台服务器 (SB) 获取信息。 SA 以 JSON 结果进行响应,但查看响应似乎 SB 使用来自 SOAP API 的 XML 进行响应,并且 SA 在将 XML 发送回之前将其转换为 JSON。

SA 的响应示例如下:

{
    fieldA : "some string", // "hello stackoverflow"
    fieldB : JSON object, // {"a" : "b" , "cd" : "ef" }
    fieldC : JSON object with fields representing array indices // { "0": "interesting" , "1" : "not helpful" }
}

当我得到响应时,我不知道根中的“fieldA”、“fieldB”等会是什么,也不知道 JSON 字段的深度。

所以我做了一个如下的递归函数,它适用于我拥有的示例数据。有什么我在这里忽略的东西可以打破这个循环,还是有更优化的版本?

var prunedResponse = (function prune(obj){
    let val = new Object();
    for(let [k,v] of Object.entries(obj)){
        if( k !== "attributes" ){
            if( typeof v !== 'object' ) val[k] = v;
            else if( !Object.keys(v).filter( _k => isNaN(_k*1) ).length ) val[k] = Object.values(v).map(_v => prune(_v));
            else val[k] = prune(v);
        }
    }
    return val;
})(responseData);

下面是想要的输出

{
    fieldA : "some string",
    fieldB : { "a" : "b" , "cd" : "ef" },
    fieldC : [ "interesting" , "not helpful" ]
}

【问题讨论】:

  • 您还没有描述prune 实际应该做什么。提供具体的输入/输出示例将使我们能够更有效地帮助您
  • @Thankyou .. 正如我在问题标题中所说,基本目标是遍历整个 JSON 对象,无论它嵌套有多深,如果有任何字段以数字为键,将该字段转换为数组,而不是其他所有内容。前任。将 {"y":{"x":{"0":"a","1":b"},"z":"alpha"}} 转换为 {"y":{x:["a","b"],"z":"alpha"}} 。 [编辑:在问题中添加了所需的输出]

标签: javascript arrays json recursion


【解决方案1】:

我不知道你想要打破什么循环。

这是一个替代实现。我不是说它更好。它更适合我的编程风格。你可能会也可能不会觉得有趣:

const prune = (o) =>
  Object (o) === o
    ?  Object .keys (o) .every (k => !isNaN (k * 1))
      ? Object .values (o) .map (prune)
      : Object .fromEntries (Object .entries (o)
          .filter (([k, v]) => k !== 'attributes')
          .map (([k, v]) => [k, prune (v)])
        )
    : o

const input = {attributes: "ignore", fieldA : "hello stackoverflow", fieldB : {"a" : "b" , "cd" : "ef"}, fieldC : {"0": {some: {"0": "foo", "3": "bar"}} , "1": "not helpful"}}

console .log (prune (input))
.as-console-wrapper {max-height: 100% !important; top: 0}

如果您确实需要处理索引中的空白,如另一个答案中所述,您可以替换此行:

      ? Object .values (o) .map (prune)

类似这样的:

      ? Object.assign([], Object.fromEntries(Object.entries(o) .map(([k, v]) => [k, prune(v)])))

我还可以提取一个辅助函数

const isIndexed = (o) =>
  Object .keys (o) .every (k => !isNaN (k * 1))

并使用它代替第一个嵌套测试。

【讨论】:

  • 确实很有趣。学习完成事情的新方法总是很好。在 javascript 中处理 XML 和 SOAP API 时,我将把它作为我未来项目的参考。
  • @HymnZzy 这是一个通用的对象转换,不限于 XML、SOAP 或其他 API 调用。它可以用于所有 JavaScript 对象。
  • @Thankyou .. 是的,我知道这一点,但我偶然发现这种结构仅在与 XML 到 JSON 转换库和模块相关的示例中。
  • 请注意,虽然attributes 节点的删除不太通用,最好分两遍编写,一遍将索引对象压缩成数组,另一遍删除属性,构建在通用 remove-this-property-name-at-every-level 函数之上。
【解决方案2】:

在检查任何键是否为非数字时,您可能会提前中断

if( !Object.keys(v).some( _k => isNaN(_k*1) ) )

而不是

if( !Object.keys(v).filter( _k => isNaN(_k*1) ).length )

此外,对于您的示例 fieldC,您的代码将中断,因为您的 prune 需要一个对象,并且您传递一个字符串 .map(_v => prune(_v));,您会将每个字符串转换为一个数组。

您应该添加一个初始检查以尽早返回传递的非对象值。

var prunedResponse = (function prune(obj){
    if (typeof obj !== 'object') return obj;
    let val = new Object();
    for(let [k,v] of Object.entries(obj)){
        if( k !== "attributes" ){
            if( typeof v !== 'object' ) val[k] = v;
            else if( !Object.keys(v).some( _k => isNaN(_k*1) ) ) val[k] = Object.values(v).map(_v => prune(_v));
            else val[k] = prune(v);
        }
    }
    return val;
})(responseData);

我看到的最后一个可能的问题是某些fieldC 类型数据不连续的情况。例如,如果它有{'1':'some', '4':'some-other'},你应该把它当作一个数组来处理吗? (目前您将创建不同的索引

【讨论】:

  • 谢谢。 {'1':'some', '4':'some-other'} 这部分很好,因为所有字段都已序列化。您提到的fieldC 错误已经由第一个if 子句处理,它检查字段值是否不是对象。但是,我确实在 NodeJS 上遇到了一个问题,但在浏览器中却没有。如果字段值是时间戳,则浏览器将其视为字符串,但 NodeJS 将其视为对象并返回空数组。
  • 关于fieldC,当索引值为字符串时,您的原始代码会失败。您prune 将始终返回object (let val = new Object();)。但是您为索引对象中的每个值调用了一个映射,因此如果它们是字符串,它将失败。
  • 啊。明白了。谢谢。
猜你喜欢
  • 2013-11-01
  • 2013-03-23
  • 2018-05-12
  • 2021-04-18
  • 1970-01-01
  • 2016-01-23
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多