Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Welcome To Ask or Share your Answers For Others

Categories

0 votes
427 views
in Technique[技术] by (71.8m points)

arrays - How to dynamically retrieve arbitrarily specified, deeply nested values from a JavaScript object?

Reposting as a corrected version of this question. I have reviewed this, but it didn't help me deal with unknown array nestings.

EDIT: Added information on why Lodash _.get won't work. Here's a Stackblitz. There's an npm library, Object-Scan that would work -- but, I can't get Angular to play nice with that package.

This is for a column renderer component in an Angular app. The user selects a column to display, and the column is mapped to whatever it is. So, in the below example, if user selects to display "Categories", and it is mapped to "interests[categories]", the function then needs to go find values that correspond to that mapping in the data.

Sample data:

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

For this data, the possible mappings are: id, version, drinks, interests[categories], interests[refreshments][drinks], goals[maxCalories][drinks], goals[maxCalories][pizza],

Requirement: I need a recursive function that will work in Angular, and accept two args: one for the data object response, and a second string of the selector mapping that will return the applicable, potentially nested values, iterating through any objects and/or arrays that are encountered. The mapping string may have either dotted or bracketed notation. Readable, self-documenting code is highly desired.

For example, based on the above data object:

  • getValues("id", response); should return ["1234"]
  • getValues("version", response); should return ["0.1"]
  • getValues("drinks", response); should return ["Scotch"]
  • getValues("interests.categories", response) should return ["baseball", "football", "movies", "books"]
  • getValues("interests[refreshments][drinks]", response); should return ["beer", "soft drink", "coffee", "tea", "soft drink" ], accounting for however many items may be in the interests array.
  • getValues("goals.maxCalories.drinks", response); should return ["350"]

Returned values will ultimately be deduped and sorted.

Original function:

function getValues(mapping, row) {
  let rtnValue = mapping
    .replace(/]/g, "")
    .split("[")
    .map(item => item.split("."))  
    .reduce((arr, next) => [...arr, ...next], [])
    .reduce((obj, key) => obj && obj[key], row)
    .sort();

  rtnValue = [...new Set(rtnValue)]; //dedupe the return values

  return rtnValue.join(", ");
}

The above works fine, just like Lodash' get: if you pass, for example: "interests[0][refreshments][drinks], it works perfectly. But, the mapping is not aware of nested arrays and passes in simply "interests[refreshments][drinks]", which results in undefined. Any subsequent array items have to be accounted for.

question from:https://stackoverflow.com/questions/65713553/how-to-dynamically-retrieve-arbitrarily-specified-deeply-nested-values-from-a-j

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome To Ask or Share your Answers For Others

1 Reply

0 votes
by (71.8m points)

Update

After I gave the answer below, I had some dinner. Food seemed to make me realize how ridiculous I was being. My response to the earlier question was clearly coloring how I thought about this one. But there's a much, much simpler approach, similar to how I would write a path function, with only a small amount of additional complexity to handle the mapping of arrays. Here's a cleaner approach:

// main function
const _getValues = ([p, ...ps]) => (obj) =>
  p == undefined 
    ? Array.isArray(obj) ? obj : [obj]
  : Array .isArray (obj)
    ?  obj .flatMap (o => _getValues (ps) (o[p] || []))
  : _getValues (ps) (obj [p] || [])


// public function
const getValues = (query, obj) =>
  _getValues (query .split (/[[].]+/g) .filter (Boolean)) (obj)


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


// demo
[
  'id',                               //=> ['1234']
  'version',                          //=> ['0.1']
  'drinks',                           //=> ['Scotch']
  'interests[categories]',            //=> ['baseball', 'football', 'movies', 'books']
  'interests[refreshments][drinks]',  //=> ['beer', 'soft drink', 'coffee', 'tea', 'soft drink']
  'goals.maxCalories.drinks',         //=> ['350']
  'goals.maxCalories.notFound',       //=> []
  'foo.bar.baz',                      //=> []
  'interests[categories][drinks]',    //=> []
] .forEach (
  name => console.log(`"${name}" --> ${JSON.stringify(getValues(name, response))}`)
)
.as-console-wrapper {max-height: 100% !important; top: 0}

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
OGeek|极客中国-欢迎来到极客的世界,一个免费开放的程序员编程交流平台!开放,进步,分享!让技术改变生活,让极客改变未来! Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Click Here to Ask a Question

...