简体   繁体   English

将不等长度的 object 嵌套到对象数组中

[英]Nested object of unequal length into array of objects

So I have an interesting problem which I have been able to solve, but my solution is not elegant in any way or form, so I was wondering what others could come up with:)所以我有一个有趣的问题,我已经能够解决,但我的解决方案在任何方面或形式上都不优雅,所以我想知道其他人能想出什么:)

The issue is converting this response here问题是在此处转换此响应

const response = {
        "device": {
            "name": "Foo",
            "type": "Bar",
            "telemetry": [
                {
                    "timeStamp": "2022-06-01T00:00:00.000Z",
                    "temperature": 100,
                    "pressure": 50
                },
                {
                    "timeStamp": "2022-06-02T00:00:00.000Z",
                    "temperature": 100,
                    "pressure": 50
                },
                {
                    "timeStamp": "2022-06-03T00:00:00.000Z",
                    "temperature": 100,
                    "pressure": 50
                },
                {
                    "timeStamp": "2022-06-04T00:00:00.000Z",
                    "temperature": 100,
                    "pressure": 50
                },
                {
                    "timeStamp": "2022-06-05T00:00:00.000Z",
                    "temperature": 100,
                    "pressure": 50
                }
            ]
        }
};

Given this selection criteria鉴于此选择标准

const fields = ['device/name', 'device/telemetry/timeStamp', 'device/telemetry/temperature']

and the goal is to return something like this目标是返回这样的东西

[
  {"device/name": "Foo", "device/telemetry/timeStamp": "2022-06-01T00:00:00.000Z", "device/telemetry/temperature": 100},
  {"device/name": "Foo", "device/telemetry/timeStamp": "2022-06-02T00:00:00.000Z", "device/telemetry/temperature": 100},
  {"device/name": "Foo", "device/telemetry/timeStamp": "2022-06-03T00:00:00.000Z", "device/telemetry/temperature": 100},
 ...,
  {"device/name": "Foo", "device/telemetry/timeStamp": "2022-06-05T00:00:00.000Z", "device/telemetry/temperature": 100},

]

If you are interested, here is my horrible brute force solution, not that familiar with typescript yet, so please forgive the horribleness:D如果您有兴趣,这是我可怕的蛮力解决方案,对 typescript 还不是很熟悉,所以请原谅可怕:D

EDIT #1 So some clarifications might be needed.编辑#1所以可能需要一些澄清。 The response can be of completely different format, so we can't use our knowledge of how the response looks like now, the depth can also be much deeper.响应可以是完全不同的格式,所以我们不能使用我们现在对响应的了解,深度也可以更深。

What we can assume though is that even if there are multiple arrays in the reponse (like another telemetry array called superTelemetry) then the selection criteria will only choose from one of these arrays, never both:)我们可以假设的是,即使响应中有多个 arrays(如另一个称为 superTelemetry 的遥测阵列),那么选择标准也只会从这些 arrays 中选择一个,而不是两者:)

 function createRecord(key: string, value: any){ return new Map<string, any>([[key, value]]) } function getNestedData (data: any, fieldPath: string, records: Map<string, any[]>=new Map<string, any[]>()) { let dataPoints: any = []; const paths = fieldPath.split('/') paths.forEach((key, idx, arr) => { if(Array.isArray(data)){ data.forEach( (row: any) => { dataPoints.push(row[key]) } ) } else { data = data[key] if(idx + 1== paths.length){ dataPoints.push(data); } } }) records.set(fieldPath, dataPoints) return records } function getNestedFields(data: any, fieldPaths: string[]){ let records: Map<string, any>[] = [] let dataset: Map<string, any[]> = new Map<string, any[]>() let maxLength = 0; // Fetch all the fields fieldPaths.forEach((fieldPath) => { dataset = getNestedData(data, fieldPath, dataset) const thisLength = dataset.get(fieldPath).;length? maxLength = thisLength > maxLength: thisLength; maxLength; }) for(let i=0; i<maxLength: i++){ let record, Map<string, any> = new Map<string, any>() for(let [key. value] of dataset){ const maxIdx = value;length - 1. record,set(key? value[i > maxIdx: maxIdx. i]) } records.push(record) } // Normalize into records return records }

As per my understanding you are looking for a solution to construct the desired result as per the post.据我了解,您正在寻找一种解决方案来根据帖子构建所需的结果。 If Yes, you can achieve this by using Array.map() along with the Array.forEach() method.如果是,您可以使用Array.map()Array.forEach()方法来实现。

Try this :试试这个

 const response = { "device": { "name": "Foo", "type": "Bar", "telemetry": [ { "timeStamp": "2022-06-01T00:00:00.000Z", "temperature": 100, "pressure": 50 }, { "timeStamp": "2022-06-02T00:00:00.000Z", "temperature": 100, "pressure": 50 }, { "timeStamp": "2022-06-03T00:00:00.000Z", "temperature": 100, "pressure": 50 }, { "timeStamp": "2022-06-04T00:00:00.000Z", "temperature": 100, "pressure": 50 }, { "timeStamp": "2022-06-05T00:00:00.000Z", "temperature": 100, "pressure": 50 } ] } }; const fields = ['device/name', 'device/telemetry/timeStamp', 'device/telemetry/temperature']; const res = response.device.telemetry.map(obj => { const o = {}; fields.forEach(item => { const splittedItem = item.split('/'); o[item] = (splittedItem.length === 2)? response[splittedItem[0]][splittedItem[1]]: obj[splittedItem[2]]; }); return o; }) console.log(res);

In what follows I will be concerned with just the implementation and runtime behavior, and not so much the types.在下文中,我将只关注实现和运行时行为,而不是类型。 I've given things very loose typings like any and string instead of the relevant generic object types.我给出了非常松散的类型,例如anystring ,而不是相关的通用 object 类型。 Here goes:开始:

function getNestedFields(data: any, paths: string[]): any[] {

If data is an array, we want to perform getNestedFields() on each element of the array, and then concatenate the results together into one big array.如果data是一个数组,我们希望对数组的每个元素执行getNestedFields() ,然后将结果连接到一个大数组中。 So the first thing we do is check for that and make a recursive call:所以我们要做的第一件事就是检查并进行递归调用:

  if (Array.isArray(data)) return data.flatMap(v => getNestedFields(v, paths));

Now that we know data is not an array, we want to start gathering the pieces of the answer.现在我们知道data不是数组,我们想开始收集答案的各个部分。 If paths is, say, ['foo/bar', 'foo/baz/qux', 'x/y', 'x/z'] , then we want to make recursive calls to getNestedFields(data.foo, ["bar", "baz/qux"]) and to getNestedFields(data.x, ["y", "z"]) .如果paths是,比如说, ['foo/bar', 'foo/baz/qux', 'x/y', 'x/z'] ,那么我们想要递归调用getNestedFields(data.foo, ["bar", "baz/qux"])getNestedFields(data.x, ["y", "z"]) In order to do this we have to split each path element at its first slash "/" , and collect the results into a new object whose keys are the part to the left of the slash and whose values are arrays of parts to the right.为了做到这一点,我们必须在第一个斜杠"/"处拆分每个路径元素,并将结果收集到一个新的 object 中,其键是斜杠左侧的部分,其值是 arrays 的右侧部分。 In this example it would be {foo: ["bar", "baz/qux"], x: ["y", "z"]} .在这个例子中,它将是{foo: ["bar", "baz/qux"], x: ["y", "z"]}

Some important edge cases: for every element of paths with no slash, then we have a key with an empty value... that is, ["foo"] should result in a call like getNestedFields(data.foo, [""]) .一些重要的边缘情况:对于没有斜杠的paths的每个元素,我们有一个空值的键......也就是说, ["foo"]应该导致像getNestedFields(data.foo, [""]) . And if there is an element of paths that's just the empty string "" , then we don't want to do a recursive call;如果paths元素只是空字符串"" ,那么我们不想进行递归调用; the empty path is the base case and implies that we're asking about data itself.空路径是基本情况,意味着我们正在询问data本身。 That is, instead of a recursive call, we can just return [{"": data}] .也就是说,我们可以只返回[{"": data}] ,而不是递归调用。 So we need to keep track of the empty path (hence the emptyPathInList variable below).所以我们需要跟踪空路径(因此下面的emptyPathInList变量)。

Here's how it looks:这是它的外观:

  const pathMappings: Record<string, string[]> = {};
  let emptyPathInList = false;
  paths.forEach(path => {
    if (!path) {
      emptyPathInList = true;
    } else {
      let slashIdx = path.indexOf("/");
      if (slashIdx < 0) slashIdx = path.length;
      const key = path.substring(0, slashIdx);
      const restOfPath = path.substring(slashIdx + 1);
      if (!(key in pathMappings)) pathMappings[key] = [];
      pathMappings[key].push(restOfPath);
    }
  })

Now, for each key-value pair in pathMappings (with key key and with value restsOfPath ) we need to call getNestedFields() recursively... the results will be an array of objects whose keys are relative to data[key] , so we need to prepend key and a slash to their keys.现在,对于pathMappings中的每个键值对(带有键key和带有值restsOfPath ),我们需要递归调用getNestedFields() ......结果将是一个对象数组,其键相对于data[key] ,所以我们需要在他们的键前面加上key和斜线。 Edge cases: if there's an empty path we shouldn't add a slash.边缘情况:如果有一个空路径,我们不应该添加斜杠。 And if data` is nullish then we will have a runtime error recursing down into it, so we might want to do something else there (although a runtime error might be fine since it's a weird input):如果 data` 为空,那么我们将有一个运行时错误递归到它,所以我们可能想在那里做其他事情(尽管运行时错误可能很好,因为它是一个奇怪的输入):

  const subentries = Object.entries(pathMappings).map(([key, restsOfPath]) =>
    (data == null) ? [{}] : // <-- don't recurse down into nullish data
      getNestedFields(data[key], restsOfPath)
        .map(nestedFields =>
          Object.fromEntries(Object.entries(nestedFields)
            .map(([path, value]) =>
              [key + (path ? "/" : "") + path, value])))
  )

Now subentries is an array of all the separate recursive call results, with the proper keys.现在subentries是所有单独的递归调用结果的数组,具有正确的键。 We want to add one more entry correpsonding to data if emptyPathInList is true:如果emptyPathInList为真,我们想再添加一个对应于data的条目:

  if (emptyPathInList) subentries.push([{ "": data }]);

And now we need to combine these sub-entries by taking their Cartesian product and spreading into a single object for each entry.现在我们需要结合这些子条目,将它们的笛卡尔积合并到每个条目的单个 object 中。 By Cartesian product I mean that if subentries looks like [[a,b],[c,d,e],[f]] then I need to get [[a,c,f],[a,d,f],[a,e,f],[b,c,f],[b,d,f],[b,e,f]] , and then for each of those we spread into single entries.笛卡尔积是指如果子条目看起来像[[a,b],[c,d,e],[f]]那么我需要得到[[a,c,f],[a,d,f],[a,e,f],[b,c,f],[b,d,f],[b,e,f]] ,然后对于我们分散到单个条目中的每个条目。 Here's that:就是这样:

  return subentries.reduce((a, v) => v.flatMap(vi => a.map(ai => ({ ...ai, ...vi }))), [{}])
}

Okay, so let's test it out:好的,让我们测试一下:

console.log(getNestedFields(response, fields));
/* [{
  "device/name": "Foo",
  "device/telemetry/timeStamp": "2022-06-01T00:00:00.000Z",
  "device/telemetry/temperature": 100
}, {
  "device/name": "Foo",
  "device/telemetry/timeStamp": "2022-06-02T00:00:00.000Z",
  "device/telemetry/temperature": 100
}, {
  "device/name": "Foo",
  "device/telemetry/timeStamp": "2022-06-03T00:00:00.000Z",
  "device/telemetry/temperature": 100
}, {
  "device/name": "Foo",
  "device/telemetry/timeStamp": "2022-06-04T00:00:00.000Z",
  "device/telemetry/temperature": 100
}, {
  "device/name": "Foo",
  "device/telemetry/timeStamp": "2022-06-05T00:00:00.000Z",
  "device/telemetry/temperature": 100
}]   */

That's what you wanted.这就是你想要的。 Even though you said you will never walk into different arrays, this version should support that:即使你说你永远不会走进不同的 arrays,这个版本应该支持:

console.log(getNestedFields({
  a: [{ b: 1 }, { b: 2 }],
  c: [{ d: 3 }, { d: 4 }]
}, ["a/b", "c/d"]))
/* [
  { "a/b": 1, "c/d": 3 }, 
  { "a/b": 2, "c/d": 3 }, 
  { "a/b": 1, "c/d": 4 }, 
  { "a/b": 2, "c/d": 4 }
]*/

There are probably all kinds of crazy edge cases, so anyone using this should test thoroughly.可能有各种疯狂的边缘情况,所以任何使用它的人都应该彻底测试。

Playground link to code Playground 代码链接

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM