首页 > 解决方案 > 在 Python 中将 JSON 键与正则表达式匹配

问题描述

我正在尝试找到一个正则表达式,它匹配嵌套 JSON 字符串表示的不同级别上的重复键。到目前为止,我所有的“解决方案”都遭受了灾难性的回溯。

该 JSON 字符串的示例如下所示:

d = {               
        "a": {      
            "b": {
                "c": {
                    "d": "v1", 
                    "key": "v2"
                }
            },
            "c": {  
                "g": "v3",     
                "key": "v4"
            },
            "key": "v5"        
        }
    }

的值key是目标。我的应用程序确实具有指向该键的所有对象名称。使用这些名称,我可以使用 for 循环来构造我的最终正则表达式。所以基本上我需要把零件放在中间。

示例:如果我得到"a"并且"key"我可以构造以下内容:"a"[^}]*"key". 这与我的字符串中的第一个“键”匹配d,即值为 v2 的那个。

但是应该发生的是"a"+"key"匹配具有值 v5 的键。"a"当完整路径++进来时,值 v2 的键应该匹配。本例中的最后一种情况是在给出 + "b"+"c"时匹配值v4的键。"key""a""c""key"

所以最后一个完整的正则表达式看起来类似于:

"a"MATCH_EVERYTHING_IN_BETWEEN_REGEX"c"MATCH_EVERYTHING_IN_BETWEEN_REGEX"key":\s*(\[[^}]*?\]|".*?"|\d+\.*\d*) 

需要明确的是,我正在寻找可以作为连接器插入的 MATCH_EVERYTHING_IN_BETWEEN_REGEX 表达式。这是为了确保它只匹配收到路径的密钥。JSON 字符串可以无限嵌套。

这是一个带有示例的在线正则表达式测试器: https ://regex101.com/r/yNZ3wo/2

注意: 我知道这不是特定于 python 的,但我也很感激在这种情况下的 python 提示。我考虑过构建自己的解析器,使用堆栈和计数{}但在我想确保没有简单的正则表达式解决方案之前。

编辑我知道 json 库,但这并不能解决我的问题,因为我在编辑器窗口内的字符串表示中跟踪目标的坐标。我不是在寻找值本身,我可以从关联的字典中访问它们。

标签: pythonjsonregex

解决方案


这很难。一个可能的解决方案是使用

  1. 一个递归正则表达式 * 以匹配嵌套大括号
    (?<="a": )({(?>[^{}]|(?1))*})
  2. 然后,使用垃圾箱方法继续搜索内部级别的键,即忽略整体匹配并仅查看特定捕获组是否包含值
    (此处为 $2,根据需要添加组):
    ({(?>[^{}]|(?1))*})|"key":\s*"([^"]*?)"

代码示例:

import regex as re

test_str = ("{                   \n"
    "  \"a\": {            \n"
    "    \"b\": {          \n"
    "      \"c\": {        \n"
    "        \"d\": \"v1\",  \n"
    "        \"key\": \"v2\" \n"
    "      }             \n"
    "    },              \n"
    "    \"c\": {          \n"
    "      \"g\": \"v3\",    \n"
    "      \"key\": \"v4\"   \n"
    "    },              \n"
    "    \"key\": \"v5\"  \n"
    "    }     \n"
    "  }                 \n"
    "}                   \n")

regex = r"(?<=\"a\": )({(?>[^{}]|(?1))*})"
innerRegex = r"({(?>[^{}]|(?1))*})|\"key\":\s*\"([^\"]*?)\""

matches = re.finditer(regex, test_str, re.DOTALL)

for n, match in enumerate(matches):
    n = n + 1    
    #print ("Match {n} was found at {start}-{end}: {match}".format(n = n, start = match.start(), end = match.end(), match = match.group()))
    inner = match.group()[1:-1]

    innerMatches = re.finditer(innerRegex, inner, re.DOTALL)
    for m, innerMatch in enumerate(innerMatches):
        #m = m + 1
        if (innerMatch.groups()[1] is not None):          
            print ("Found at {start}-{end}: {group}".format(start = innerMatch.start(2), end = innerMatch.end(2), group = innerMatch.group(2)))

或继续搜索下一级(上面未显示)代码。
基本上,您会inner以相同的方式从第 1 步再次继续比赛(参见演示),例如:

(?<="c": )({(?>[^{}]|(?1))*})

这应该让您领先一步。

*由于我们使用正则表达式递归,我们需要替代 Python 正则表达式包。


推荐阅读