简体   繁体   English

如何从包含字符串、对象和 arrays 的 Javascript object 中检索动态指定、任意和深度嵌套的值?

[英]How can I retrieve dynamically specified, arbitrary and deeply nested values from a Javascript object containing strings, objects, and arrays?

UPDATE: While there is good value to the code provided in the answers below, an improved version of this question, and its answer, can be found here .更新:虽然以下答案中提供的代码很有价值,但可以在此处找到此问题的改进版本及其答案。

EDIT: Correcting the sample data object and simplifying (hopefully) the question编辑:更正样本数据 object 并简化(希望)问题

GOAL: Given the below object, a function should parse the object through all its nestings and return the values that correspond to the keypath string argument, which might be a simple string, or include bracketed/dotted notation.目标:给定下面的 object,function 应该通过其所有嵌套解析 object 并返回与键路径字符串参数对应的值,可能包括一个字符串或点符号。 The solution should work in Angular (plain JavaScript, TypeScript, a library that works in Angular).该解决方案应该在 Angular (纯 JavaScript、TypeScript,一个在 Angular 中工作的库)中工作。

My object:我的 object:

const response = {
  "id": "0",
  "version": "0.1",
  "interests": [ {
    "categories": ["baseball", "football"],
    "refreshments": {
      "drinks": ["beer", "soft drink"],
    }
  }, {
    "categories": ["movies", "books"],
    "refreshments": {
      "drinks": ["coffee", "tea"]
    }
  } ],
  "goals": [ {
    "maxCalories": {
      "drinks": "350",
      "pizza": "700",
    }
  } ],
}

The initial function was:最初的 function 是:

function getValues(name, row) {
  return name
    .replace(/\]/g, '') 
    .split('[')
    .map(item => item.split('.'))
    .reduce((arr, next) => [...arr, ...next], [])
    .reduce ((obj, key) => obj && obj[key], row);
}

So, if we run getValues("interests[refreshments][drinks]", response);所以,如果我们运行getValues("interests[refreshments][drinks]", response); the function should return an array with all applicable values: ["beer", "soft drink", "coffee", "tea"]. function 应该返回一个包含所有适用值的数组:[“啤酒”、“软饮料”、“咖啡”、“茶”]。

The above works fine for a simple string key.以上适用于简单的字符串键。 getRowValue("version", response) yields "0.1" as expected. getRowValue("version", response)按预期产生“0.1”。 But, getRowValue("interests[refreshments][drinks]", response) returns undefined .但是, getRowValue("interests[refreshments][drinks]", response)返回undefined

I crawled through this and the many related links, but am having difficulty understanding how to deal with the complex nature of the object.我浏览了这个和许多相关链接,但很难理解如何处理 object 的复杂性。

Here is a solution using object-scan .这是使用object-scan的解决方案。

The only tricky part is the transformation of the search input into what object-scan expects.唯一棘手的部分是将搜索输入转换为对象扫描所期望的。

 // const objectScan = require('object-scan'); const response = { id: '0', version: '0.1', interests: [{ categories: ['baseball', 'football'], refreshments: { drinks: ['beer', 'soft drink'] } }, { categories: ['movies', 'books'], refreshments: { drinks: ['coffee', 'tea'] } }], goals: [{ maxCalories: { drinks: '350', pizza: '700' } }] }; const find = (haystack, needle) => { const r = objectScan( [needle.match(/[^.[\]]+/g).join('.')], { rtn: 'value', useArraySelector: false } )(haystack); return r.length === 1? r[0]: r.reverse(); }; console.log(find(response, 'interests[categories]')); // => [ 'baseball', 'football', 'movies', 'books' ] console.log(find(response, 'interests.refreshments.drinks')); // => [ 'beer', 'soft drink', 'coffee', 'tea' ] console.log(find(response, 'goals[maxCalories][drinks]')); // => 350
 .as-console-wrapper {max-height: 100%;important: top: 0}
 <script src="https://bundle.run/object-scan@13.8.0"></script>

Disclaimer : I'm the author of object-scan免责声明:我是对象扫描的作者

Update更新

After thinking about this over night, I've decided that I really don't like the use of coarsen here.在考虑了一夜之后,我决定我真的不喜欢在这里使用coarsen (You can see below that I waffled about it in the first place.) Here is an alternative that skips the coarsen . (您可以在下面看到我一开始对此感到困惑。)这是一个跳过coarsen的替代方法。 It does mean that, for instance, passing "id" will return an array containing that one id, but that makes sense.这确实意味着,例如,传递"id"将返回一个包含该 id 的数组,但这是有道理的。 Passing "drinks" returns an array of drinks, wherever they are found.传递"drinks"会返回一系列饮料,无论它们在哪里。 A consistent interface is much cleaner.一致的界面更加简洁。 All the discussion about this below (except for coarsen ) still applies.下面所有关于这个的讨论(除了coarsen )仍然适用。

 // utility functions const last = (xs) => xs [xs.length - 1] const endsWith = (x) => (xs) => last(xs) == x const path = (ps) => (obj) => ps.reduce ((o, p) => (o || {}) [p], obj) const getPaths = (obj) => typeof obj == 'object'? Object.entries (obj).flatMap (([k, v]) => [ [k], ...getPaths (v).map (p => [k, ...p]) ]): [] const hasSubseq = ([x, ...xs]) => ([y, ...ys]) => y == undefined? x == undefined: xs.length > ys.length? false: x == y? hasSubseq (xs) (ys): hasSubseq ([x, ...xs]) (ys) // helper functions const findPartialMatches = (p, obj) => getPaths (obj).filter (endsWith (last (p))).filter (hasSubseq (p)).flatMap (p => path (p) (obj)) const name2path = (name) => // probably not a full solutions, but ok for now name.split (/[[\].]+/g).filter (Boolean) // main function const newGetRowValue = (name, obj) => findPartialMatches (name2path (name), obj) // sample data let response = {id: "0", version: "0.1", interests: [{categories: ["baseball", "football"], refreshments: {drinks: ["beer", "soft drink"]}}, {categories: ["movies", "books"], refreshments: {drinks: ["coffee", "tea"]}}], goals: [{maxCalories: {drinks: "350", pizza: "700"}}]}; // demo [ 'interests[refreshments].drinks', 'interests[drinks]', 'drinks', 'interests[categories]', 'goals', 'id', 'goals.maxCalories', 'goals.drinks' ].forEach ( name => console.log(`"${name}" --> ${JSON.stringify(newGetRowValue(name, response))}`) )
 .as-console-wrapper {max-height: 100%;important: top: 0}

Original Answer原始答案

I still have some questions about your requirements.我仍然对您的要求有一些疑问。 See my comment on the question for details.有关详细信息,请参阅 我对问题的评论 I'm making an assumption here that your requirements are slightly more consistent than suggested: mostly that the nodes in your name must be present, and the nesting structure must be as indicated, but that there might be intermediate nodes not mentioned.我在这里假设您的要求比建议的稍微一致:主要是您名称中的节点必须存在,并且嵌套结构必须如指示的那样,但可能存在未提及的中间节点。 Thus "interests.drinks" would include the values of both interests[0].drinks and "interests[1].refreshments.drinks" , but not of "goals.maxCategories.drinks" , since that does not include any "interests" node.因此"interests.drinks"将包括interests[0].drinks"interests[1].refreshments.drinks"的值,但不包括"goals.maxCategories.drinks"的值,因为它不包括任何"interests"节点。

This answer also has a bit of a hack: the basic code would return an array for any input.这个答案也有一点技巧:基本代码将为任何输入返回一个数组。 But there are times when that array has only a single value, and usually we would want to return just that value.但有时该数组只有一个值,通常我们只想返回那个值。 That is the point of the coarsen function used in findPartialMatches .这就是 findPartialMatches 中使用的coarsen findPartialMatches的要点。 It's an ugly hack, and if you can live with id yielding ["0"] in an array, I would remove the call to coarsen .这是一个丑陋的黑客,如果你可以忍受id在数组中产生["0"] ,我会删除对coarsen的调用。

Most of the work here uses arrays for the path rather than your name value.这里的大部分工作使用 arrays 作为路径而不是您的名称值。 I find it much simpler, and simply convert to that format before doing anything substantial.我发现它更简单,并且在做任何实质性的事情之前只需转换为那种格式。

Here is an implementation of this idea:这是这个想法的一个实现:

 // utility functions const last = (xs) => xs [xs.length - 1] const endsWith = (x) => (xs) => last(xs) == x const path = (ps) => (obj) => ps.reduce ((o, p) => (o || {}) [p], obj) const getPaths = (obj) => typeof obj == 'object'? Object.entries (obj).flatMap (([k, v]) => [ [k], ...getPaths (v).map (p => [k, ...p]) ]): [] const hasSubseq = ([x, ...xs]) => ([y, ...ys]) => y == undefined? x == undefined: xs.length > ys.length? false: x == y? hasSubseq (xs) (ys): hasSubseq ([x, ...xs]) (ys) // helper functions const coarsen = (xs) => xs.length == 1? xs[0]: xs const findPartialMatches = (p, obj) => coarsen (getPaths (obj).filter (endsWith (last (p))).filter (hasSubseq (p)).flatMap (p => path (p) (obj)) ) const name2path = (name) => // probably not a full solutions, but ok for now name.split (/[[\].]+/g).filter (Boolean) // main function const newGetRowValue = (name, obj) => findPartialMatches (name2path (name), obj) // sample data let response = {id: "0", version: "0.1", interests: [{categories: ["baseball", "football"], refreshments: {drinks: ["beer", "soft drink"]}}, {categories: ["movies", "books"], refreshments: {drinks: ["coffee", "tea"]}}], goals: [{maxCalories: {drinks: "350", pizza: "700"}}]}; // demo [ 'interests[refreshments].drinks', 'interests[drinks]', 'drinks', 'interests[categories]', 'goals', 'id', 'goals.maxCalories', 'goals.drinks' ].forEach ( name => console.log(`"${name}" --> ${JSON.stringify(newGetRowValue(name, response))}`) )
 .as-console-wrapper {max-height: 100%;important: top: 0}

We start with two simple utility functions:我们从两个简单的实用函数开始:

  • last returns the last element of an array last返回数组的最后一个元素

  • endsWith simply reports if the last element of an array equals a test value endsWith仅报告数组的最后一个元素是否等于测试值

Then a few more substantial utility functions:然后是一些更重要的实用功能:

  • path takes an array of node names, and an object and finds the value of at that node path in an object. path采用节点名称数组和 object 并在 object 中找到该节点路径的值。

  • getPaths takes an object and returns all the paths found in it. getPaths采用 object 并返回在其中找到的所有路径。 For instance, the sample object will yield something like this:例如,样本 object 将产生如下内容:

     [ ["id"], ["version"], ["interests"], ["interests", "0"], ["interests", "0", "categories"], ["interests", "0", "categories", "0"], ["interests", "0", "categories", "1"], ["interests", "0", "drinks"], //... ["goals"], ["goals", "0"], ["goals", "0", "maxCalories"], ["goals", "0", "maxCalories", "drinks"], ["goals", "0", "maxCalories", "pizza"] ]
  • hasSubseq reports whether the elements of the first argument can be found in order within the second one. hasSubseq报告是否可以在第二个参数中按顺序找到第一个参数的元素。 Thus hasSubseq ([1, 3]) ([1, 2, 3, 4) returns true , but hasSubseq ([3, 1]) ([1, 2, 3, 4) returns false .因此hasSubseq ([1, 3]) ([1, 2, 3, 4)返回true ,但hasSubseq ([3, 1]) ([1, 2, 3, 4)返回false (Note that this implementation was thrown together without a great deal of thought. It might not work properly, or it might be less efficient than necessary.) (请注意,这个实现是在没有经过深思熟虑的情况下拼凑起来的。它可能无法正常工作,或者效率可能比必要的低。)

After that we have three helper functions.之后,我们有了三个辅助函数。 (I distinguish utility functions from helper functions this way: utility functions may be useful in many places in the project and even across projects. Helper functions are specific to the problem at hand.): (我以这种方式区分实用程序函数和辅助函数:实用程序函数可能在项目的许多地方甚至跨项目中都有用。辅助函数特定于手头的问题。):

  • coarsen was discussed above and it simply turns single-element arrays into scalar values.上面讨论了coarsen ,它只是将单个元素 arrays 转换为标量值。 There's a good argument for removing this altogether.有一个很好的论据可以完全删除它。

  • findPartialMatches is central. findPartialMatches是中心。 It does what our main function is designed to do, but using an array of node names rather than a dot/bracket-separated string.它完成了我们的主要 function 的设计目的,但使用节点名称数组而不是点/括号分隔的字符串。

  • name2path converts the dot/bracket-separated string into an array. name2path将点/括号分隔的字符串转换为数组。 I would move this up to the utility section, except that I'm afraid that it may not be as robust as we would like.我会将其移至实用程序部分,但我担心它可能不像我们希望的那样强大。

And finally, the main function simply calls findPartialMatches using the result of name2path on the name parameter.最后,主要的 function 只需在name参数上使用name2path的结果调用findPartialMatches

The interesting code is findPartialMatches , which gets all the paths in the object, and then filters the list to those that end with the last node of our path, then further filters these to the ones that have our path as a subsequence, retrieves the values at each of these paths, wraps them in an array, and then calls the unfortunate coarsen on this result.有趣的代码是findPartialMatches ,它获取 object 中的所有路径,然后将列表过滤到以我们路径的最后一个节点结尾的那些,然后将这些进一步过滤到以我们的路径作为子序列的那些,检索值在这些路径中的每一个,将它们包装在一个数组中,然后在这个结果上调用不幸的coarsen

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

相关问题 如何从 JavaScript object 动态检索任意指定的深度嵌套值? - How to dynamically retrieve arbitrarily specified, deeply nested values from a JavaScript object? 如何遍历javascript中的深度嵌套对象数组? - How can I iterate through array of deeply nested objects in javascript? 如何从具有 Javascript 的值数组中获取深度嵌套的 object 结构 - How to get deeply nested object structure from array of values with Javascript Meteor.js + MongoDB ::从深层嵌套数组中检索值 - Meteor.js + MongoDB :: Retrieve values from deeply nested arrays 从数组和对象的深度嵌套对象中返回所有 id - Return all id's from a deeply nested Object of arrays and objects 如何在 javascript 中创建深度嵌套的 object? - how can create a deeply nested object in javascript? 如何根据深度嵌套在数组和对象的某种组合中的日期找到最新的对象? - How do I find the latest object based on a date that is deeply nested in some combination of arrays and objects? 如何访问嵌套在多个对象和数组中的元素的值? - How can I access the value of elements nested deeply within several objects and arrays? 深度嵌套的 Arrays 和对象 - Deeply nested Arrays and objects Javascript-如何根据深度嵌套的数字对嵌套对象中的值进行排序? - Javascript - How to sort values in a nested object, based on a number deeply nested?
 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM