javascript 递归地修剪对象键和值中的空格

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/33510625/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-10-28 16:33:48  来源:igfitidea点击:

Trim white spaces in both Object key and value recursively

javascriptobjectrecursiontrim

提问by vichsu

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

如何递归地修剪 JavaScript 对象中的键和值中的空格?

I came across one issue in which I was trying to "clean" a user supplied JSON string and send it into my other code for further processing.

我遇到了一个问题,我试图“清理”用户提供的 JSON 字符串并将其发送到我的其他代码中以进行进一步处理。

Let's say we've got a user supplied JSON string whose property key and value are of type "string". However, what's problematic in this case is that the keys and values are not as clean as desired. Say a { " key_with_leading_n_trailing_spaces ": " my_value_with_leading_spaces" }.

假设我们有一个用户提供的 JSON 字符串,其属性键和值是“字符串”类型。然而,在这种情况下的问题是键和值并不像期望的那样干净。说一个 { " key_with_leading_n_trailing_spaces ": " my_value_with_leading_spaces" }。

In this case, it can easily cause issue with your brilliantly written JavaScript program trying to make use of such data(or should we call it dirty data?) because when your code is trying to get the value out of this JSON object, not only the key is not matching but also the value can not be matched. I have looked around google and found a few tips but there is not one cure that cure it all.

在这种情况下,它很容易导致您编写出色的 JavaScript 程序尝试使用这些数据(或者我们应该称其为脏数据?)键不匹配,但值也无法匹配。我环顾了谷歌并找到了一些技巧,但没有一种方法可以治愈一切。

Given this JSON with lots of white spaces in keys and values.

鉴于此 JSON 在键和值中有很多空格。

var badJson = {
  "  some-key   ": "    let it go    ",
  "  mypuppy     ": "    donrio   ",
  "   age  ": "   12.3",
  "  children      ": [
    { 
      "   color": " yellow",
      "name    ": "    alice"
    },    { 
      "   color": " silver        ",
      "name    ": "    bruce"
    },    { 
      "   color": " brown       ",
      "     name    ": "    francis"
    },    { 
      "   color": " red",
      "      name    ": "    york"
    },

  ],
  "     house": [
    {
      "   name": "    mylovelyhouse     ",
      " address      " : { "number" : 2343, "road    "  : "   boardway", "city      " : "   Lexiton   "}
    }
  ]

};

So this is what I came up with ( with help of using lodash.js):

所以这就是我想出的(在使用 lodash.js 的帮助下):

//I made this function to "recursively" hunt down keys that may 
//contain leading and trailing white spaces
function trimKeys(targetObj) {

  _.forEach(targetObj, function(value, key) {

      if(_.isString(key)){
        var newKey = key.trim();
        if (newKey !== key) {
            targetObj[newKey] = value;
            delete targetObj[key];
        }

        if(_.isArray(targetObj[newKey]) || _.isObject(targetObj[newKey])){
            trimKeys(targetObj[newKey]);
        }
      }else{

        if(_.isArray(targetObj[key]) || _.isObject(targetObj[key])){
            trimKeys(targetObj[key]);
        }
      }
   });

}

//I stringify this is just to show it in a bad state
var badJson = JSON.stringify(badJson);

console.log(badJson);

//now it is partially fixed with value of string type trimed
badJson = JSON.parse(badJson,function(key,value){
    if(typeof value === 'string'){
        return value.trim();
    }
    return value;
});

trimKeys(badJson);

console.log(JSON.stringify(badJson));

Note here : I did this in a 1, 2 steps because I could not find a better one shot to deal it all solution. If there is issue in my code or anything better, please do share with us.

此处请注意:我分 1、2 个步骤执行此操作,因为我找不到更好的一次解决方案来解决所有问题。如果我的代码有问题或有更好的地方,请与我们分享。

Thanks!

谢谢!

回答by epascarello

You can just stringify it, string replace, and reparse it

您可以将其字符串化,字符串替换并重新解析它

JSON.parse(JSON.stringify(badJson).replace(/"\s+|\s+"/g,'"'))

回答by RobG

You can clean up the property names and attributes using Object.keysto get an array of the keys, then Array.prototype.reduceto 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.

您可以使用Object.keys清理属性名称和属性以获取键数组,然后使用Array.prototype.reduce迭代键并创建一个带有修剪过的键和值的新对象。该函数需要递归,以便它还可以修剪嵌套的对象和数组。

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

注意它只处理普通的数组和对象,如果你想处理其他类型的对象,对reduce的调用需要更复杂的来确定对象的类型(例如一个适当聪明的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)? []:{});
}

回答by Richard

epascarello's answer above plus some unit tests (just for me to be sure):

上面 epascarello 的回答加上一些单元测试(仅供我确定):

function trimAllFieldsInObjectAndChildren(o: any) {
  return JSON.parse(JSON.stringify(o).replace(/"\s+|\s+"/g, '"'));
}

import * as _ from 'lodash';
assert.true(_.isEqual(trimAllFieldsInObjectAndChildren(' bob '), 'bob'));
assert.true(_.isEqual(trimAllFieldsInObjectAndChildren('2 '), '2'));
assert.true(_.isEqual(trimAllFieldsInObjectAndChildren(['2 ', ' bob ']), ['2', 'bob']));
assert.true(_.isEqual(trimAllFieldsInObjectAndChildren({'b ': ' bob '}), {'b': 'bob'}));
assert.true(_.isEqual(trimAllFieldsInObjectAndChildren({'b ': ' bob ', 'c': 5, d: true }), {'b': 'bob', 'c': 5, d: true}));
assert.true(_.isEqual(trimAllFieldsInObjectAndChildren({'b ': ' bob ', 'c': {' d': 'alica c c '}}), {'b': 'bob', 'c': {'d': 'alica c c'}}));
assert.true(_.isEqual(trimAllFieldsInObjectAndChildren({'a ': ' bob ', 'b': {'c ': {'d': 'e '}}}), {'a': 'bob', 'b': {'c': {'d': 'e'}}}));
assert.true(_.isEqual(trimAllFieldsInObjectAndChildren({'a ': ' bob ', 'b': [{'c ': {'d': 'e '}}, {' f ': ' g ' }]}), {'a': 'bob', 'b': [{'c': {'d': 'e'}}, {'f': 'g' }]}));

回答by Thank you

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

我认为通用map函数可以很好地处理这个问题。它将深度对象遍历和转换与我们希望执行的特定动作分开——

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"]}

mapcan be reused to easily apply a different transformation -

map可以重用以轻松应用不同的转换 -

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 mappractical

制作map实用

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

感谢 Scott 的评论,我们为map. 在这个例子中,我们写成trim一个函数 -

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 mapmust accept twofunctional arguments now -

这意味着现在map必须接受两个函数参数 -

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 .trimwhile keys get .trim()and .toUpperCase()-

现在我们可以看到键转换与值转换分开工作。字符串值变得简单,.trim而键得到.trim().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"]}

回答by Sankalp Mehta

@RobG Thank you for the solution. Adding one more condition will not create more nested objects

@RobG 谢谢你的解决方案。添加更多条件不会创建更多嵌套对象

function trimObj(obj) {
      if (obj === null && !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() : typeof obj[key] === 'object' ?  trimObj(obj[key]) : obj[key];
        return acc;
      }, Array.isArray(obj)? []:{});
    }

回答by some random guy

Similar to epascarello's answer. This is what I did :

类似于 epascarello 的回答。这就是我所做的:

import java.util.regex.Matcher;
import java.util.regex.Pattern;

........

public String trimWhiteSpaceAroundBoundary(String inputJson) {
    String result;
    final String regex = "\"\s+|\s+\"";
    final Pattern pattern = Pattern.compile(regex);
    final Matcher matcher = pattern.matcher(inputJson.trim());
    // replacing the pattern twice to cover the edge case of extra white space around ','
    result = pattern.matcher(matcher.replaceAll("\"")).replaceAll("\"");
    return result;
}

Test cases

测试用例

assertEquals("\"2\"", trimWhiteSpace("\" 2 \""));
assertEquals("2", trimWhiteSpace(" 2 "));
assertEquals("{   }", trimWhiteSpace("   {   }   "));
assertEquals("\"bob\"", trimWhiteSpace("\" bob \""));
assertEquals("[\"2\",\"bob\"]", trimWhiteSpace("[\"  2  \",  \"  bob  \"]"));
assertEquals("{\"b\":\"bob\",\"c c\": 5,\"d\": true }",
              trimWhiteSpace("{\"b \": \" bob \", \"c c\": 5, \"d\": true }"));

回答by JaredM

I tried the solution JSON.stringify solution above, but it will not work with a string like '"this is \'my\' test"'. You can get around it using stringify's replacer function and just trim the values going in.

我尝试了上面的解决方案 JSON.stringify 解决方案,但它不适用于像“这是\'我的\'测试”这样的字符串。您可以使用 stringify 的 replacer 函数绕过它,只需修剪进入的值即可。

JSON.parse(JSON.stringify(obj, (key, value) => (typeof value === 'string' ? value.trim() : value)))

JSON.parse(JSON.stringify(obj, (key, value) => (typeof value === 'string' ? value.trim() : value)))