如何以递归方式修剪JavaScript对象中的键和值中的空格?
我遇到了一个问题,我正试图清理"用户提供了JSON字符串并将其发送到我的其他代码中以供进一步处理。
我们假设我们有一个用户提供的JSON字符串,其属性键和值的类型为"字符串"。但是,在这种情况下的问题是键和值不如所希望的那样干净。说一个{" key_with_leading_n_trailing_spaces":" my_value_with_leading_spaces" }。
在这种情况下,它很容易导致你出色的JavaScript程序试图利用这些数据(或者我们应该把它称为脏数据?),因为当你的代码试图从这个JSON对象中获取值时,不仅关键不匹配而且价值也无法匹配。我已经浏览了谷歌并找到了一些提示,但没有一种方法可以治愈这一切。
鉴于这个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 "}
}
]
};
所以这就是我提出的(在使用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));
请注意:我是在1,2步中做到这一点,因为我无法找到更好的一击来解决所有问题。如果我的代码中存在问题或更好,请与我们分享。
谢谢!
答案 0 :(得分:30)
您可以对其进行字符串化,字符串替换和重新分析
JSON.parse(JSON.stringify(badJson).replace(/"\s+|\s+"/g,'"'))
答案 1 :(得分:18)
您可以使用 Object.keys 清除属性名称和属性以获取密钥数组,然后使用 Array.prototype.reduce 来迭代密钥和使用修剪的键和值创建一个新对象。该函数需要递归,以便它还修剪嵌套的对象和数组。
请注意,它只处理普通数组和对象,如果你想处理其他类型的对象,对 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)? []:{});
}
答案 2 :(得分:3)
{{1}}
答案 3 :(得分:1)
我认为通用的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"]}
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"]}
使map
实用
感谢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
)
这意味着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
现在,我们可以看到键转换与值转换是分开的。字符串值获取简单的.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"]}
展开以下代码段,以在您自己的浏览器中验证结果-
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"]}
答案 4 :(得分:0)
类似于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;
}
测试用例
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 }"));
答案 5 :(得分:0)
我尝试了上面的解决方案JSON.stringify解决方案,但是它不适用于''this is \'my \'test''这样的字符串。您可以使用stringify的replacer函数来解决它,而只需修剪输入的值即可。
JSON.parse(JSON.stringify(obj,(key,value)=>(typeof value ==='string'?value.trim():value)))
答案 6 :(得分:0)
@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)? []:{});
}
答案 7 :(得分:0)
我使用的最佳解决方案是这个。查看replacer function上的文档。
var obj = {"data": {"address": {"city": "\n \r New York", "country": " USA \n\n\r"}}};
var trimmed = JSON.stringify(obj, (key, value) => {
if (typeof value === 'string') {
return value.trim();
}
return value;
});
console.log(JSON.parse(trimmed));