Trim white spaces in both Object key and value recursively

后端 未结 8 1947
一生所求
一生所求 2020-12-05 05:53

How do you trim white spaces in both the keys and values in a JavaScript Object recursively?

I came across one issue in which I was trying to \"clean\" a user suppli

相关标签:
8条回答
  • 2020-12-05 06:25

    I think a generic map function handles this well. It separates the deep object traversal and transformation from the particular action we wish to perform -

    const identity = x =>
      x
    
    const map = (f = identity, x = null) =>
      Array.isArray(x)
        ? x.map(v => map(f, v))
    : Object(x) === x
        ? Object.fromEntries(Object.entries(x).map(([ k, v ]) => [ map(f, k), map(f, v) ]))
        : f(x)
    
    const dirty = 
    ` { "  a  ": "  one "
      , " b": [ null,  { "c ": 2, " d ": { "e": "  three" }}, 4 ]
      , "  f": { "  g" : [ "  five", 6] }
      , "h " : [[ [" seven  ", 8 ], null, { " i": " nine " } ]]
      , " keep  space  ": [ " betweeen   words.  only  trim  ends   " ]
      }
    `
      
    const result =
      map
       ( x => String(x) === x ? x.trim() : x // x.trim() only if x is a String
       , JSON.parse(dirty)
       )
       
    console.log(JSON.stringify(result))
    // {"a":"one","b":[null,{"c":2,"d":{"e":"three"}},4],"f":{"g":["five",6]},"h":[[["seven",8],null,{"i":"nine"}]],"keep  space":["betweeen   words.  only  trim  ends"]}

    map can be reused to easily apply a different transformation -

    const result =
      map
       ( x => String(x) === x ? x.trim().toUpperCase() : x
       , JSON.parse(dirty)
       )
    
    console.log(JSON.stringify(result))
    // {"A":"ONE","B":[null,{"C":2,"D":{"E":"THREE"}},4],"F":{"G":["FIVE",6]},"H":[[["SEVEN",8],null,{"I":"NINE"}]],"KEEP  SPACE":["BETWEEEN   WORDS.  ONLY  TRIM  ENDS"]}
    

    making map practical

    Thanks to Scott's comment, we add some ergonomics to map. In this example, we write trim as a function -

    const trim = (dirty = "") =>
      map
       ( k => k.trim().toUpperCase()          // transform keys
       , v => String(v) === v ? v.trim() : v  // transform values
       , JSON.parse(dirty)                    // init
       )
    

    That means map must accept two functional arguments now -

    const map = (fk = identity, fv = identity, x = null) =>
      Array.isArray(x)
        ? x.map(v => map(fk, fv, v)) // recur into arrays
    : Object(x) === x
        ? Object.fromEntries(
            Object.entries(x).map(([ k, v ]) =>
              [ fk(k)           // call fk on keys
              , map(fk, fv, v)  // recur into objects
              ] 
            )
          )
    : fv(x) // call fv on values
    

    Now we can see key transformation working as separated from value transformation. String values get a simple .trim while keys get .trim() and .toUpperCase() -

    console.log(JSON.stringify(trim(dirty)))
    // {"A":"one","B":[null,{"C":2,"D":{"E":"three"}},4],"F":{"G":["five",6]},"H":[[["seven",8],null,{"I":"nine"}]],"KEEP  SPACES":["betweeen   words.  only  trim  ends"]}
    

    Expand the snippet below to verify the results in your own browser -

    const identity = x =>
      x
    
    const map = (fk = identity, fv = identity, x = null) =>
      Array.isArray(x)
        ? x.map(v => map(fk, fv, v))
    : Object(x) === x
        ? Object.fromEntries(
            Object.entries(x).map(([ k, v ]) =>
              [ fk(k), map(fk, fv, v) ]
            )
          )
    : fv(x)
    
    const dirty = 
    ` { "  a  ": "  one "
      , " b": [ null,  { "c ": 2, " d ": { "e": "  three" }}, 4 ]
      , "  f": { "  g" : [ "  five", 6] }
      , "h " : [[ [" seven  ", 8 ], null, { " i": " nine " } ]]
      , " keep  spaces  ": [ " betweeen   words.  only  trim  ends   " ]
      }
    `
    
    const trim = (dirty = "") =>
      map
       ( k => k.trim().toUpperCase()
       , v => String(v) === v ? v.trim() : v
       , JSON.parse(dirty)
       )
       
    console.log(JSON.stringify(trim(dirty)))
    // {"A":"one","B":[null,{"C":2,"D":{"E":"three"}},4],"F":{"G":["five",6]},"H":[[["seven",8],null,{"I":"nine"}]],"KEEP  SPACES":["betweeen   words.  only  trim  ends"]}

    0 讨论(0)
  • 2020-12-05 06:26

    You can clean up the property names and attributes using Object.keys to get an array of the keys, then Array.prototype.reduce to iterate over the keys and create a new object with trimmed keys and values. The function needs to be recursive so that it also trims nested Objects and Arrays.

    Note that it only deals with plain Arrays and Objects, if you want to deal with other types of object, the call to reduce needs to be more sophisticated to determine the type of object (e.g. a suitably clever version of new obj.constructor()).

    function trimObj(obj) {
      if (!Array.isArray(obj) && typeof obj != 'object') return obj;
      return Object.keys(obj).reduce(function(acc, key) {
        acc[key.trim()] = typeof obj[key] == 'string'? obj[key].trim() : trimObj(obj[key]);
        return acc;
      }, Array.isArray(obj)? []:{});
    }
    
    0 讨论(0)
提交回复
热议问题