A Method Example for Securely Obtaining Object Deep Objects in Js

  • 2021-11-14 04:38:06
  • OfStack

Preface to the table of contents
Text
Parameter example
Implementation of lodash:
tokey function: castPath function: stringToPath function:
memoizeCapped function:
memoize function: The complete code is as follows:
Reference: Summary

Preface

The front-end partner 1 will encounter the situation that the data returned by the back-end is nested in multiple layers. When I want to get the value of the deep object, in order to prevent errors, I will do layer-by-layer non-empty verification, such as:


const obj = {
    goods: {
        name: 'a',
        tags: {
            name: ' Fast ',
            id: 1,
            tagType: {
                name: ' Label '
            }
        }
    }
}

When I need to get tagType. name, the judgment is like this


if (obj.goods !== null
    && obj.goods.tags !== null
    && obj.goods.tags.tagType !== null) {
  
}

If the attribute name is lengthy, the broken code can't be seen.

Of course, ECMAScript 2020 has been launched? . to solve the problem:


let name = obj?.goods?.tags?.tageType?.name;

But what about browsers that are not compatible with ES2020?

Text

Students who have used lodash may know that there is an get method in lodash. Official website said this:


_.get(object, path, [defaultValue])

Get the value according to the path path of the object object. If value is parsed as undefined, it will be replaced by defaultValue.

Parameter

object (Object): The object to retrieve. path (Arraystring): The path to get the property. [defaultValue] () *: If the resolved value is undefined, this value is returned.

Example


var object = { 'a': [{ 'b': { 'c': 3 } }] };
​
_.get(object, 'a[0].b.c');
// => 3 
​
_.get(object, ['a', '0', 'b', 'c']);
// => 3
​
_.get(object, 'a.b.c', 'default');
// => 'default'

So the problem is solved, but (I am afraid of "but")

What if I can't use the lodash library for various reasons, such as project, company requirements, etc.?

Yes, let's see how lodash is realized. Can't you pick out the code? So you can move bricks happily again ~ ~

Implementation of lodash:


function get(object, path, defaultValue) {
  const result = object == null ? undefined : baseGet(object, path)
  return result === undefined ? defaultValue : result
}

What is done here is very simple, first look back, if object returns the default value, the core code is in baseGet, then let's look at the implementation of baseGet again


function baseGet(object, path) {
  //  Converts the input string path to an array, 
  path = castPath(path, object)
​
  let index = 0
  const length = path.length
  //  Traverse the array to get every 1 Layer object 
  while (object != null && index < length) {
    object = object[toKey(path[index++])] // toKey Method 
  }
  return (index && index == length) ? object : undefined
}
​

Two more functions are used here, castPath (converting input paths to arrays) and toKey (converting real key)

tokey function:


/** Used as references for various `Number` constants. */
const INFINITY = 1 / 0
​
/**
 * Converts `value` to a string key if it's not a string or symbol.
 *
 * @private
 * @param {*} value The value to inspect.
 * @returns {string|symbol} Returns the key.
 */
function toKey(value) {
  if (typeof value === 'string' || isSymbol(value)) {
    return value
  }
  const result = `${value}`
  return (result == '0' && (1 / value) == -INFINITY) ? '-0' : result
}

There are two main things done here,

If the key type is String or symbol, it will be returned directly If key is of another type, converting to String returns

Here also used the isSymbol function to determine whether it is Symbol type, the code is not posted, interested students can view the source code of lodash

castPath function:


import isKey from './isKey.js'
import stringToPath from './stringToPath.js'
​
/**
 * Casts `value` to a path array if it's not one.
 *
 * @private
 * @param {*} value The value to inspect.
 * @param {Object} [object] The object to query keys on.
 * @returns {Array} Returns the cast property path array.
 */
function castPath(value, object) {
  if (Array.isArray(value)) {
    return value
  }
  return isKey(value, object) ? [value] : stringToPath(value)
}
​

castPath mainly converts the input path into an array, which prepares for later traversal to obtain deep objects.

isKey () and stringToPath () are used here.

isKey is relatively simple to judge whether the current value is key of object.

stringToPath mainly handles cases where the input path is a string, such as' a. b. c [0]. d '

stringToPath function:


import memoizeCapped from './memoizeCapped.js'
​
const charCodeOfDot = '.'.charCodeAt(0)
const reEscapeChar = /\(\)?/g
const rePropName = RegExp(
  // Match anything that isn't a dot or bracket.
  '[^.[\]]+' + '|' +
  // Or match property names within brackets.
  '\[(?:' +
    // Match a non-string expression.
    '([^"'][^[]*)' + '|' +
    // Or match strings (supports escaping characters).
    '(["'])((?:(?!\2)[^\\]|\\.)*?)\2' +
  ')\]'+ '|' +
  // Or match "" as the space between consecutive dots or empty brackets.
  '(?=(?:\.|\[\])(?:\.|\[\]|$))'
  , 'g')
​
/**
 * Converts `string` to a property path array.
 *
 * @private
 * @param {string} string The string to convert.
 * @returns {Array} Returns the property path array.
 */
const stringToPath = memoizeCapped((string) => {
  const result = []
  if (string.charCodeAt(0) === charCodeOfDot) {
    result.push('')
  }
  string.replace(rePropName, (match, expression, quote, subString) => {
    let key = match
    if (quote) {
      key = subString.replace(reEscapeChar, '$1')
    }
    else if (expression) {
      key = expression.trim()
    }
    result.push(key)
  })
  return result
})
​

This is mainly to exclude the. and [] in the path, and parse out the real key and add it to the array

memoizeCapped function:


if (obj.goods !== null
    && obj.goods.tags !== null
    && obj.goods.tags.tagType !== null) {
  
}
0

Here is a limit on the key of the cache, and empty the cache when it reaches 500

memoize function:


if (obj.goods !== null
    && obj.goods.tags !== null
    && obj.goods.tags.tagType !== null) {
  
}
1

In fact, the last two functions are not quite understood. If the input path is' a. b. c ', can't you directly convert it into an array? Why do you want to use closures for caching?

I hope that the big brother who understands can give an answer 1

Because the source code uses many functions and is in different files, I have simplified them.

The complete code is as follows:


if (obj.goods !== null
    && obj.goods.tags !== null
    && obj.goods.tags.tagType !== null) {
  
}
2

The code is borrowed from lodash

References:

lodash Official Document Github

Summarize


Related articles: