Mike K
Mike K

Reputation: 6491

A better way to trim all elements in an object recursively?

If I have an object like,

const obj = {
    field: {
        subfield: {
            innerObj: { a: ' asdasd  asdas . ' },
            innerArr: ['  s  ', '  ssad . '],
            innerArrObj: [ { b: '   adsad  ' } ],
        }
    }
}

I've come up with something like this,

const trimFields = (data) =>
  _.mapValues(data, (element, k) =>
    _.isArray(element)
      ? element.map((value) =>
          _.isObject(value) ? trimFields(value) : trimText(value)
        )
      : _.isObject(element)
      ? trimFields(element)
      : trimText(element)
  );

But I'm wondering if there is a better / more efficient way to do this?

JSFiddle

Upvotes: 1

Views: 339

Answers (3)

Scott Sauyet
Scott Sauyet

Reputation: 50797

I would write a more general deepMap function and then call it with trimText and your object. It then becomes easily reusable, and it separates out the handling of object navigation from your actual field transformation. It's not hard to write, either with or without lodash. Here's one version:

const deepMap = (fn) => (obj) => 
  Array .isArray (obj) 
    ? obj .map (deepMap (fn))
  : Object (obj) === obj
    ? Object .fromEntries (Object .entries (obj) .map (([k, v]) => [k, deepMap (fn) (v)]))
  : // else 
    fn (obj)

const trimText = field => typeof field === 'string' ? field .trim () : field;

const obj = {field: {subfield: {innerObj: { a: ' asdasd  asdas . ' }, innerArr: ['  s  ', '  ssad . '], innerArrObj: [ { b: '   adsad  ' } ]}}}

console .log (
  deepMap (trimText) (obj)
)

Note that I simplified trimText, since trim is built into String.prototype.

Writing this generic version is pretty much no more difficult than a one-off version, and you can reuse it for other purposes.

deepMap (square) ({a: 1, b: [2, 3, 4], c: [{d: 5}, {d: 6}]})
//=> {a: 1, b: [4, 9, 16], c: [{d: 25}, {d: 36}]}

Upvotes: 1

Ori Drori
Ori Drori

Reputation: 191986

The lodash's _.transform() function iterates both objects and arrays. You can create a recursive mapValues() function using _.transform(), and then apply a transformer function (_.trim() in this case) to handle all values:

const recursiveMapValues = _.curry((fn, obj) => 
  _.transform(obj, (acc, value, key) => {
      acc[key] = _.isObject(value) ?
        recursiveMapValues(fn, value)
        :
        fn(value)
  }))
  
const trimFields = recursiveMapValues(v => _.isString(v) ? _.trim(v) : v)

const obj = {"field":{"subfield":{"innerObj":{"a":" asdasd  asdas . "},"innerArr":["  s  ","  ssad . ", 3],"innerArrObj":[{"b":"   adsad  "}]}}}

const result = trimFields(obj)

console.log(result)
<script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.17.15/lodash.js"></script>

Upvotes: 0

Jonas Wilms
Jonas Wilms

Reputation: 138277

I'd switch for array / object / other directly in the function, simplifying the recursive call:

 const trimFields = (data) =>
    _.isArray(data) 
      ? data.map(trimFields)
      :  _.isObject(data) 
        ? _.mapValues(trimFields)
        : trimText(data);

Upvotes: 1

Related Questions