regex 如何通过正则表达式从有效的JavaScript源代码(不是JSON)中提取键值对?

wmomyfyw  于 2023-08-08  发布在  Java
关注(0)|答案(4)|浏览(90)

该字符串虽然是完全有效的JS源代码,但不符合JSON格式。因此,我不认为有一个简单的解决方案,允许使用JSON.parse。也许我错了
问题

我有一个键值对字符串,并希望使用正则表达式提取它们。

  • 钥匙都是已知的
  • 分隔符是冒号
  • 键可以用单引号或双引号括起来,也可以不用。即key:value'key':value"key":value
  • 键和分隔符之间可能有空间,也可能没有空间。即key:valuekey :value
  • 分隔符和值之间可能有空格,也可能没有。即key:valuekey: value
  • 该值可以用单引号或双引号括起来,也可以不用。即key:valuekey:"value"key:'value'
  • 该值可以由多行文本组成。即
key: {
       val1: 1,
       val2: 2,
       val3: 3,
     }
key: [
       val1,
       val2,
       val3,
     ]
key: (arg1, arg2) => {
       return {
         arg1,
         arg2
       }
     }

示例

字符串:

value1         :        true,
value2 : "something, something-else",
value3: [
  {
    a: 'a',
    b: true,
    c: 3
  }, {
    a: Thing,
    func: () => {
      return new Thing()
    }
  }
],
"value4": [1, 2, 3, 4],
'value5': "['a', 'b', 'c', 'd']",
value6: false


最后,我想以一个包含键值对的二维数组结束,但是一旦使用正则表达式提取了键和值,就可以处理这个问题。
预期结果:

[
   ['value1', true],
   ['value2', 'something, something-else'],
   ['value3', "{
                 a: 'a',
                 b: true,
                 c: 3
               }, {
                 a: Thing,
                 func: () => {
                   return new Thing()
                 }
               }"],
   ['value4', "[1, 2, 3, 4]"],
   ['value5', "['a', 'b', 'c', 'd']"],
   ['value6', false]
 [

尝试的解决方案

这是我目前为止想到的:

(?<key>value1|value2|value3|value4|value5|value6)["'\s]*?:\s*(?<value>(?!value1|value2|value3|value4|value5).*)


1.使用命名的捕获组显式匹配冒号左侧的键-考虑可选的单引号或双引号以及两侧的空格

(?<key>value1|value2|value3|value4|value5|value6)["'\s]*?:


1.使用负向前查找将值匹配到下一个键

\s*(?<value>(?!value1|value2|value3|value4|value5).*)


但这似乎并没有做我想的那样,就好像你把所有的单词都去掉,用一些任意的东西来代替,结果还是一样的

\s*(?<value>(?!a).*)


我意识到这实际上并不是检查换行符,但我不确定如何合并它?
已尝试在regex101上解决问题

有你真好

对于值,只提取可选的单引号或双引号内的内容,而不是引号或逗号。即,该something, something-else而不是'something, something-else',

备注

regex 101示例被设置为PCRE,以便我可以使用Regex调试器,但我正在寻找使用有效JavaScript正则表达式的解决方案。

gcmastyq

gcmastyq1#

基于一个正则表达式,比如…

/^(?:(?<quote>['"])(?<dequotedKey>[^'"]+)\k<quote>|(?<unquotedKey>\w+))\s*\:\s*(?<value>.*?),*$/

字符串
...如果它的全局多行变体产生matches like shown at this regex playground,则可以选择基于reduce的方法,该方法确实处理基于行的令牌,这些令牌是split在每个换行符处对源字符串进行ting的结果。
然后,实现是非常简单的。对于每个行标记,只需要执行regex并尝试访问regex-result的命名捕获组。只要至少value捕获确实存在,就有一个有效的匹配,因此,可以将有效的键值对推入OP的结果数组,其中键要么是未加引号的,要么是作为其取消引号的变体捕获的。如果value为null,则没有任何内容匹配,这表明最后收集的有效键值对的多行值。因此,必须逐行连接后者的值。

const regXKeyValueCaptures =
  /^(?:(?<quote>['"])(?<dequotedKey>[^'"]+)\k<quote>|(?<unquotedKey>\w+))\s*\:\s*(?<value>.*?),*$/;

console.log(

  sampleData
    .split(/\n/)
    .reduce((result, lineToken) => {
      const {
        dequotedKey = null,
        unquotedKey = null,
        value = null,
      } = regXKeyValueCaptures.exec(lineToken)?.groups ?? {};

      if (value === null) {
        result.at(-1)[1] = `${ result.at(-1)[1] }\n${ lineToken }`;
      } else {
        result.push([dequotedKey || unquotedKey, value]);
      }
      return result;
    }, [])

);

x

.as-console-wrapper { min-height: 100%!important; top: 0; }
<script>
const sampleData =
`value1         :        true,
value2 : "something, something-else",
value3: [
  {
    a: 'a',
    b: true,
    c: 3
  }, {
    a: Thing,
    func: () => {
      return new Thing()
    }
  }
],
"value4": [1, 2, 3, 4],
'value5': "['a', 'b', 'c', 'd']",
value6: false`;
</script>

的字符串
如果OP需要不等于上述实现的解析原始结果的问题的请求结果,则上述代码需要伴随有附加的清理任务,该清理任务从每个键值对的值中剥离尾随和前导引号。

const regXKeyValueCaptures =
  /^(?:(?<quote>['"])(?<dequotedKey>[^'"]+)\k<quote>|(?<unquotedKey>\w+))\s*\:\s*(?<value>.*?),*$/;

console.log(

  sampleData
    .split(/\n/)
    .reduce((result, lineToken) => {
      const {
        dequotedKey = null,
        unquotedKey = null,
        value = null,
      } = regXKeyValueCaptures.exec(lineToken)?.groups ?? {};

      if (value === null) {
        result.at(-1)[1] = `${ result.at(-1)[1] }\n${ lineToken }`;
      } else {
        result.push([dequotedKey || unquotedKey, value]);
      }
      return result;

    }, []).map(([key, value]) =>

      [key, value.replace(/^["']|["']$/g, '')]
    )
);
.as-console-wrapper { min-height: 100%!important; top: 0; }
<script>
const sampleData =
`value1         :        true,
value2 : "something, something-else",
value3: [
  {
    a: 'a',
    b: true,
    c: 3
  }, {
    a: Thing,
    func: () => {
      return new Thing()
    }
  }
],
"value4": [1, 2, 3, 4],
'value5': "['a', 'b', 'c', 'd']",
value6: false`;
</script>

编辑

  • 奖金...完全基于“正则表达式和仅捕获组”的方法 *

在处理了第一个正则表达式模式之后,我认为找到一个只支持正则表达式的方法并不难。所以在尝试了一点并给了之前...

/^(?:(?<quote>['"])(?<dequotedKey>[^'"]+)\k<quote>|(?<unquotedKey>\w+))\s*\:\s*(?<value>.*?),*$/


...一个转折,正则表达式可以处理解析本身是...

/(?:^|\n)(?:(?<quote>['"])(?<dequotedKey>[^'"]+)\k<quote>|(?<unquotedKey>\w+))\s*\:\s*(?<value>.*?)(?=,\n['"\w]|$)/gs


explanation and how it actually works can be found a this regex' playground
主要的变化是…

  • 将多行字符串处理为s单行,因此是单行标志,这允许value组通过非贪婪点全(.*?)量词捕获多行匹配,
  • 以及在模式开始处的non capturing group艾德alternation(?:^|\n)和在模式结束处的正向前查找(具有(?=,\n['"\w]|$)的交替)的组合,这两者都标识键值对的开始和结束,即使它分布在几个换行符上。

利用新的模式和适应之前介绍的代码相应地导致以下实现…

const regXKeyValueCaptures =
  /(?:^|\n)(?:(?<quote>['"])(?<dequotedKey>[^'"]+)\k<quote>|(?<unquotedKey>\w+))\s*\:\s*(?<value>.*?)(?=,\n['"\w]|$)/gs;

console.log(

  [...sampleData.matchAll(regXKeyValueCaptures)]
    .map(({ groups: { dequotedKey, unquotedKey, value } }) => [

      (dequotedKey || unquotedKey),
      value.replace(/^["']|["']$/g, ''),
    ])
);
.as-console-wrapper { min-height: 100%!important; top: 0; }
wqsoz72f

wqsoz72f2#

是否知道密钥的顺序?如果是这样,您可以尝试将源字符串从一个键分割到下一个键,然后从每个值的开始和结束处删除不需要的位(空格、换行符、逗号、引号):

const str = `
  value1         :        true,
  value2 : "something, something-else",
  value3: [
    {
      a: 'a',
      b: true,
      c: 3
    }, {
      a: Thing,
      func: () => {
        return new Thing()
      }
    }
  ],
  "value4": [1, 2, 3, 4],
  'value5': "['a', 'b', 'c', 'd']",
  value6: false
`

function clean(dirtyValue) {
  return dirtyValue
    .replace(/^['"]?\s*\:\s*/, '')
    .replace(/,?\s*['"]?$/, '')
}

const keys = ['value1', 'value2', 'value3', 'value4', 'value5', 'value6']

const parsed = keys.reduce((acc, key, i) => {
  const indexOfKey = str.indexOf(key);
  const indexOfNextKey = i < keys.length - 1 ? str.indexOf(keys[i + 1]) : str.length
  
  acc[key] = clean(str.slice(indexOfKey + key.length, indexOfNextKey))

  return acc;
}, {})

Object.entries(parsed).forEach(([key, value]) => console.log(key, '=', value))

字符串
您也可以调整上述范例,以使用未排序的索引键:

const str = `
  value1         :        true,
  value2 : "something, something-else",
  value3: [
    {
      a: 'a',
      b: true,
      c: 3
    }, {
      a: Thing,
      func: () => {
        return new Thing()
      }
    }
  ],
  "value4": [1, 2, 3, 4],
  'value5': "['a', 'b', 'c', 'd']",
  value6: false
`

function clean(dirtyValue) {
  return dirtyValue
    .replace(/^['"]?\s*\:\s*/, '')
    .replace(/,?\s*['"]?$/, '')
}

function shuffleArray(arr) {
  const shuffledArray = arr.slice(0)

  for (let i = shuffledArray.length - 1; i > 0; i--) {
      const j = Math.floor(Math.random() * (i + 1));    

      [shuffledArray[i], shuffledArray[j]] = [shuffledArray[j], shuffledArray[i]]
  }

  return shuffledArray;
}

const keys = shuffleArray(['value1', 'value2', 'value3', 'value4', 'value5', 'value6'])

const parsed = keys.reduce((acc, key) => {
  const indexOfKey = str.indexOf(key);
      
  const closestIndexOfNextKey = keys.map((possibleNextKey) => {
    const possibleNextKeyIndex = str.indexOf(possibleNextKey, indexOfKey + 1)
    
    return possibleNextKeyIndex <= 0 ? Infinity : possibleNextKeyIndex
  }).sort((a, b) => a - b)[0]
  
  acc[key] = clean(str.slice(indexOfKey + key.length, closestIndexOfNextKey))

  return acc;
}, {})

Object.entries(parsed).forEach(([key, value]) => console.log(key, '=', value))


注意,如果您有许多键,则可能需要通过从键数组中删除已找到的键来优化此代码。

c90pui9n

c90pui9n3#

我已经成功地用下面的正则表达式解决了这个问题(它可能可以优化,但它完成了我需要它做的工作):

/(?<key>value1|value2|value3|value4|value5|value6)["' ]*?:[ '"]*(?<value>[\s\S]*?)(?:[ '",]*?\n*?[ '",]*?)(?=value1|value2|value3|value4|value5|value6|$)/g

字符串
它捕获命名组“key”中冒号左侧的键,并允许在键后添加可选的空格或单/双引号:

(?<key>value1|value2|value3|value4|value5|value6)["' ]*?:


然后,它使用负向前查找来匹配冒号右侧的值,以查找下一个键或字符串的结尾。在它查找的负前瞻之前:

  • 空格、单引号/双引号和逗号的可选组合
  • 值(由命名组“value”捕获)
  • 可选的换行符
  • 空格、单引号/双引号和逗号的可选组合
[ '"]*(?<value>[\s\S]*?)(?:[ '",]*?\n*?[ '",]*?)(?=value1|value2|value3|value4|value5|value6|$)


工作演示here

const str = `value1         :        true,
  value2 : 'something, something-else',
  value3: [
    {
      a: 'a',
      b: true,
      c: 3
    }, {
      a: Thing,
      func: () => {
        return new Thing()
      }
    }
  ],
  "value4": [1, 2, 3, 4],
  'value5': "['a', 'b', 'c', 'd']",
  value6: false`;

  const keyAndValue = new RegExp(/(?<key>value1|value2|value3|value4|value5|value6)["' ]*?:[ '"]*(?<value>[\s\S]*?)(?:[ '",]*?\n*?[ '",]*?)(?=value1|value2|value3|value4|value5|value6|$)/, 'g');

  console.log([...str.matchAll(keyAndValue)])

vptzau2j

vptzau2j4#

你得到的数据不是JSON,但看起来像是一个JavaScript对象,去掉了开始的{和结束的}
因此,您可以使用eval解析它,但要注意eval的问题,IOW:确保你相信消息来源
你的源代码有一些函数,比如Thing(),所以需要stubbing,下面我用了一个小技巧来捕获异常并自动添加Thing,它在Chrome和Firefox上对我有效,但是解析错误字符串感觉有点笨拙,所以需要注意的是。
我注意到你的布尔值的结果没有 Package 在字符串中,所以我做了一个测试,因为我刚刚使用JSON来做值部分,它与你的输出不完全相同,但可以用一个更接近你的目标的自定义部分来代替。
如果需要更详细的信息,我建议使用AST来解析它,regex可能也可以,但我觉得它们可能是一些边缘情况,会让您感到困惑。如果您使用AST,不要忘记添加{}来使它可以解析。

const src = `value1         :        true,
value2 : "something, something-else",
value3: [
  {
    a: 'a',
    b: true,
    c: 3
  }, {
    a: Thing,
    func: () => {
      return new Thing()
    }
  }
],
"value4": [1, 2, 3, 4],
'value5': "['a', 'b', 'c', 'd']",
value6: false`;

function keyValue(src) {
  const stubs = [];
  for (let l = 0; l < 100; l ++) {
    try {
      const stubsTxt = stubs.map(m=>`function ${m}(){}`).join(';');
      const p = eval(`${stubsTxt};({${src}})`);
      return Object.entries(p).map(([k, v]) => {
        return [k, typeof v === 'boolean' ? v : JSON.stringify(v)]
      });
    } catch (e) {
      const t = e.toString().split(' ');
      if (t[0] === 'ReferenceError:') {
        stubs.push(t[1]);
      } else break;
    }
  }
}

document.querySelector('pre').innerText = JSON.stringify(keyValue(src), null, '  ');

个字符

相关问题