使用Python中的正则表达式匹配JSON键

时间:2018-05-26 04:43:18

标签: python json regex

我正在尝试找到一个正则表达式,它匹配嵌套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" + "b" + "c" + "key"进入时,值v2的键应该匹配。此示例中的最后一种情况是匹配键当"a" + "c" + "key"被给出时,值为v4。

所以最后一个的完整正则表达式看起来与此相似:

"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库,但这并不能解决我的情况,因为我在编辑器窗口内跟踪字符串表示中的目标坐标。我不是自己寻找值,我可以从相关的字典中访问它们。

2 个答案:

答案 0 :(得分:1)

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

  1. 递归正则表达式*以匹配嵌套大括号
    (?<="a": )({(?>[^{}]|(?1))*})
  2. 然后,继续使用垃圾桶方法搜索内层的密钥,即忽略整体匹配,只要查看特定的捕获组,如果它包含值
    (这里$ 2,根据需要添加组):
    ({(?>[^{}]|(?1))*})|"key":\s*"([^"]*?)"
  3. 代码示例:

    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)))
    

    或继续搜索下一级别(上面未显示)代码 基本上,您将以同样的方式从第1步再次继续inner匹配(请参阅demo),例如:

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

    这应该让你先行。

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

答案 1 :(得分:0)

感谢wp78de提供的答案,我意识到在这种情况下正则表达式不是正确的工具,至少不是我想要的工具。也许这对其他人有用,这就是为什么我在这里添加它。

所以,我编写了一个递归地解决问题的函数。

我利用了这样一个事实,即我知道哪个密钥必须在哪个级别匹配,因此在这种情况下它只会增加密钥索引(ind)。其他未按名称和级别匹配的键会触发异常。最后的if子句负责嵌套级别。

作为第一步,我将字符串转换为行列表(前面的空白被剥离):

d = \
{
    "a": {
        "b": {
            "c": {
                "d": "v1",
                "key": "v2" # line 6
                }
            },
        "x": {
            "c": {
                "d": "v11",
                "key": "v20" # line 12
                }                      
            },                         
        "c": {                         
            "g": "v3",                 
            "key": "v4" # line 17      
            },                         
        "key": "v5" # line 19          
    }                                  
}       

ds = json.dumps(d, indent=4)     

l = ds.split('\n')               
ll = [x.lstrip() for x in l]     


def findkey(l, t, lev=0, ind=0):                                 
    if ind == len(t):                                            
        return 1                                                 
    else:                                                                                                         
        el = l[0]                                                
        try:                                                     
            if el.startswith(t[ind]) and t.index(t[ind]) == lev: 
                ind += 1                                         
        except IndexError as e:                                  
            pass                                                 

        if "{" in el:                                            
            lev += 1                                             
        if "}" in el:                                            
            lev -= 1                                             
        return 1 + findkey(l[1:], t, lev, ind) 

以上只返回行号,但现在我可以用一个非常简单的正则表达式匹配我的目标:

idx = findkey(ll[1:], tup) - 1            
s = re.compile(tup[-1] + ': (\s*(\[[^}]*?\]|".*?"|\d+\.*\d*))', re.DOTALL)          
match = s.search(l[idx])  
print("Value found at start index: {}, stop index: {}".format(match.start(1), match.end(2)))

输出:

Value found at start index: 19, stop index: 23

这是pyfiddle