簡體   English   中英

如何從包含字符串、對象和 arrays 的 Javascript object 中檢索動態指定、任意和深度嵌套的值?

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

更新:雖然以下答案中提供的代碼很有價值,但可以在此處找到此問題的改進版本及其答案。

編輯:更正樣本數據 object 並簡化(希望)問題

目標:給定下面的 object,function 應該通過其所有嵌套解析 object 並返回與鍵路徑字符串參數對應的值,可能包括一個字符串或點符號。 該解決方案應該在 Angular (純 JavaScript、TypeScript,一個在 Angular 中工作的庫)中工作。

我的 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",
    }
  } ],
}

最初的 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);
}

所以,如果我們運行getValues("interests[refreshments][drinks]", response); function 應該返回一個包含所有適用值的數組:[“啤酒”、“軟飲料”、“咖啡”、“茶”]。

以上適用於簡單的字符串鍵。 getRowValue("version", response)按預期產生“0.1”。 但是, getRowValue("interests[refreshments][drinks]", response)返回undefined

我瀏覽了這個和許多相關鏈接,但很難理解如何處理 object 的復雜性。

這是使用object-scan的解決方案。

唯一棘手的部分是將搜索輸入轉換為對象掃描所期望的。

 // 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>

免責聲明:我是對象掃描的作者

更新

在考慮了一夜之后,我決定我真的不喜歡在這里使用coarsen (您可以在下面看到我一開始對此感到困惑。)這是一個跳過coarsen的替代方法。 這確實意味着,例如,傳遞"id"將返回一個包含該 id 的數組,但這是有道理的。 傳遞"drinks"會返回一系列飲料,無論它們在哪里。 一致的界面更加簡潔。 下面所有關於這個的討論(除了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}

原始答案

我仍然對您的要求有一些疑問。 有關詳細信息,請參閱 我對問題的評論 我在這里假設您的要求比建議的稍微一致:主要是您名稱中的節點必須存在,並且嵌套結構必須如指示的那樣,但可能存在未提及的中間節點。 因此"interests.drinks"將包括interests[0].drinks"interests[1].refreshments.drinks"的值,但不包括"goals.maxCategories.drinks"的值,因為它不包括任何"interests"節點。

這個答案也有一點技巧:基本代碼將為任何輸入返回一個數組。 但有時該數組只有一個值,通常我們只想返回那個值。 這就是 findPartialMatches 中使用的coarsen findPartialMatches的要點。 這是一個丑陋的黑客,如果你可以忍受id在數組中產生["0"] ,我會刪除對coarsen的調用。

這里的大部分工作使用 arrays 作為路徑而不是您的名稱值。 我發現它更簡單,並且在做任何實質性的事情之前只需轉換為那種格式。

這是這個想法的一個實現:

 // 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}

我們從兩個簡單的實用函數開始:

  • last返回數組的最后一個元素

  • endsWith僅報告數組的最后一個元素是否等於測試值

然后是一些更重要的實用功能:

  • path采用節點名稱數組和 object 並在 object 中找到該節點路徑的值。

  • getPaths采用 object 並返回在其中找到的所有路徑。 例如,樣本 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報告是否可以在第二個參數中按順序找到第一個參數的元素。 因此hasSubseq ([1, 3]) ([1, 2, 3, 4)返回true ,但hasSubseq ([3, 1]) ([1, 2, 3, 4)返回false (請注意,這個實現是在沒有經過深思熟慮的情況下拼湊起來的。它可能無法正常工作,或者效率可能比必要的低。)

之后,我們有了三個輔助函數。 (我以這種方式區分實用程序函數和輔助函數:實用程序函數可能在項目的許多地方甚至跨項目中都有用。輔助函數特定於手頭的問題。):

  • 上面討論了coarsen ,它只是將單個元素 arrays 轉換為標量值。 有一個很好的論據可以完全刪除它。

  • findPartialMatches是中心。 它完成了我們的主要 function 的設計目的,但使用節點名稱數組而不是點/括號分隔的字符串。

  • name2path將點/括號分隔的字符串轉換為數組。 我會將其移至實用程序部分,但我擔心它可能不像我們希望的那樣強大。

最后,主要的 function 只需在name參數上使用name2path的結果調用findPartialMatches

有趣的代碼是findPartialMatches ,它獲取 object 中的所有路徑,然后將列表過濾到以我們路徑的最后一個節點結尾的那些,然后將這些進一步過濾到以我們的路徑作為子序列的那些,檢索值在這些路徑中的每一個,將它們包裝在一個數組中,然后在這個結果上調用不幸的coarsen

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM