JavaScript字符串解析问题:含空格的key=value对拆分失败
问题描述
需要解析格式类似的LDAP日志行,示例如下:
[16/Nov/2022:12:00:36.523 -0800] BIND RESULT threadID=8 conn=641870 op=11958 msgID=11959 version="3" dn="cn=Directory Manager" authType="SIMPLE" resultCode=0 resultCodeName="Success" qtime=0 etime=0.185 authDN="cn=LDAP Root,cn=Root DNs,cn=config" clientConnectionPolicy="default"
原代码通过空格拆分字符串,遇到authDN这类值含空格且用引号包裹的字段时,会被错误拆分成多个无效条目。原代码如下:
data=str.split(" "); for (x in data){ switch (x){ case 0: TimeStamp=data[x] + " " + data[x+1]; break; case 1: break; case 2: entryType=data[x] + " " + data[x+1]; break; case 3: break; default: entry.setAttribute(data[x].toString().split('=')[0],data[x].toString().split('=')[1]); break; } }
解决方案1:正则表达式匹配(推荐)
用正则直接匹配所有合法的key=value对,不管值是否带引号和空格。正则能同时处理两种值格式:不带引号的纯字符值,以及带双引号包裹的含空格值。
实现代码
const logLine = '[16/Nov/2022:12:00:36.523 -0800] BIND RESULT threadID=8 conn=641870 op=11958 msgID=11959 version="3" dn="cn=Directory Manager" authType="SIMPLE" resultCode=0 resultCodeName="Success" qtime=0 etime=0.185 authDN="cn=LDAP Root,cn=Root DNs,cn=config" clientConnectionPolicy="default"'; // 提取时间戳和操作类型 const headerMatch = logLine.match(/^\[(.*?)\]\s+(.*?)\s+/); const timeStamp = headerMatch[1]; const entryType = headerMatch[2]; // 匹配所有key=value对 const kvRegex = /(\w+)=("([^"]+)"|([^\s"]+))/g; const kvPairs = logLine.match(kvRegex) || []; const entry = {}; kvPairs.forEach(pair => { // 拆分key和value部分,只拆一次等号 const [key, valuePart] = pair.split('=', 2); // 去掉值的引号(如果有) const value = valuePart.startsWith('"') ? valuePart.slice(1, -1) : valuePart; entry[key] = value; // 如果你需要用setAttribute方法,替换成 entry.setAttribute(key, value) }); console.log('时间戳:', timeStamp); console.log('操作类型:', entryType); console.log('解析结果:', entry);
正则说明
(\w+): 匹配key部分(字母、数字、下划线组成)=: 匹配分隔用的等号("([^"]+)"|([^\s"]+)): 匹配两种值格式:"([^"]+)": 匹配带双引号的值,捕获引号内所有非引号字符([^\s"]+): 匹配不带引号的值,捕获非空格、非引号的连续字符
/g: 全局匹配所有符合条件的key=value对
解决方案2:状态机解析(适合复杂场景)
如果日志格式存在更复杂的情况(比如值内有转义引号),状态机逐字符解析的方式会更灵活,能应对各种边界情况。
实现代码
function parseLogLine(logLine) { const result = { timeStamp: '', entryType: '', attributes: {} }; let index = 0; const len = logLine.length; // 解析时间戳:从[开始到]结束 if (logLine[index] === '[') { index++; while (index < len && logLine[index] !== ']') { result.timeStamp += logLine[index]; index++; } index++; // 跳过闭合的] } // 解析操作类型:跳过空格,直到遇到第一个key=value开头 while (index < len && logLine[index] === ' ') index++; let entryType = ''; while (index < len && !/\w+=/.test(logLine.slice(index, index + 5))) { entryType += logLine[index]; index++; } result.entryType = entryType.trim(); // 解析key=value对 let currentKey = ''; let currentValue = ''; let inQuotes = false; let state = 'key'; // key: 收集key阶段; value: 收集value阶段 while (index < len) { const char = logLine[index]; if (state === 'key') { if (char === '=') { state = 'value'; } else if (char !== ' ') { currentKey += char; } } else if (state === 'value') { if (char === '"') { inQuotes = !inQuotes; } else if (char === ' ' && !inQuotes) { // 结束当前value,存入结果 result.attributes[currentKey] = currentValue.trim(); currentKey = ''; currentValue = ''; state = 'key'; } else { currentValue += char; } } index++; } // 保存最后一组key=value if (currentKey) { result.attributes[currentKey] = currentValue.trim(); } return result; } // 使用示例 const logLine = '[16/Nov/2022:12:00:36.523 -0800] BIND RESULT threadID=8 conn=641870 op=11958 msgID=11959 version="3" dn="cn=Directory Manager" authType="SIMPLE" resultCode=0 resultCodeName="Success" qtime=0 etime=0.185 authDN="cn=LDAP Root,cn=Root DNs,cn=config" clientConnectionPolicy="default"'; const parsedResult = parseLogLine(logLine); console.log(parsedResult);
状态机优势
- 可轻松扩展处理转义引号(如
\") - 对日志格式的微小变化兼容性更强
- 逻辑直观,不需要依赖正则的复杂写法
内容的提问来源于stack exchange,提问作者Billv
相关产品推荐
相关产品推荐

