Pyparsing:将半JSON嵌套明文数据解析为列表

时间:2013-12-19 20:32:40

标签: python json parsing pyparsing plaintext

我有一堆嵌套数据,其格式与JSON非常相似:

company="My Company"
phone="555-5555"
people=
{
    person=
    {
        name="Bob"
        location="Seattle"
        settings=
        {
            size=1
            color="red"
        }
    }
    person=
    {
        name="Joe"
        location="Seattle"
        settings=
        {
            size=2
            color="blue"
        }
    }
}
places=
{
    ...
}

有许多不同的参数,深度不同 - 这只是一个非常小的子集。

值得注意的是,当创建一个新的子数组时,总会有一个等号后跟一个换行符后跟一个开括号(如上图所示)。

是否有任何简单的循环或递归技术将此数据转换为系统友好的数据格式,如数组或JSON?我想避免硬编码属性的名称。我正在寻找可以在Python,Java或PHP中运行的东西。伪代码也很好。

我感谢任何帮助。

编辑:我发现了Python的Pyparsing库,看起来它可能是一个很大的帮助。我找不到任何关于如何使用Pyparsing来解析未知深度的嵌套结构的示例。任何人都可以根据我上面描述的数据阐明Pyparsing吗?

编辑2:好的,这是Pyparsing中的一个有效解决方案:

def parse_file(fileName):

#get the input text file
file = open(fileName, "r")
inputText = file.read()

#define the elements of our data pattern
name = Word(alphas, alphanums+"_")
EQ,LBRACE,RBRACE = map(Suppress, "={}")
value = Forward() #this tells pyparsing that values can be recursive
entry = Group(name + EQ + value) #this is the basic name-value pair


#define data types that might be in the values
real = Regex(r"[+-]?\d+\.\d*").setParseAction(lambda x: float(x[0]))
integer = Regex(r"[+-]?\d+").setParseAction(lambda x: int(x[0]))
quotedString.setParseAction(removeQuotes)

#declare the overall structure of a nested data element
struct = Dict(LBRACE + ZeroOrMore(entry) + RBRACE) #we will turn the output into a Dictionary

#declare the types that might be contained in our data value - string, real, int, or the struct we declared
value << (quotedString | struct | real | integer)

#parse our input text and return it as a Dictionary
result = Dict(OneOrMore(entry)).parseString(inputText)
return result.dump()

这样可行,但是当我尝试使用json.dump(result)将结果写入文件时,文件的内容用双引号括起来。此外,许多数据对之间有\n个咒语。我尝试使用LineEnd().suppress()在上面的代码中抑制它们,但我不能正确使用它。



好的,我提出了一个最终的解决方案,它实际上将这些数据转换为我最初想要的JSON友好的Dict。它首先使用Pyparsing将数据转换为一系列嵌套列表,然后遍历列表并将其转换为JSON。这使我能够克服Pyparsing的toDict()方法无法处理同一对象具有两个同名属性的问题。要确定列表是普通列表还是属性/值对,{P <{1}}方法会在Pyparsing检测到它们时在属性名称前面添加字符串prependPropertyToken

__property__

5 个答案:

答案 0 :(得分:5)

通过使用Forward类定义占位符来保存嵌套部分,可以通过pyparsing解析任意嵌套结构。在这种情况下,您只是解析简单的名称 - 值对,其中value本身可以是包含名称 - 值对的嵌套结构。

name :: word of alphanumeric characters
entry :: name '=' value
struct :: '{' entry* '}'
value :: real | integer | quotedstring | struct

这几乎逐字逐句地说是pypars。要定义可以递归地包含值的值,我们首先创建一个Forward()占位符,它可以用作条目定义的一部分。然后,一旦我们定义了所有可能的值类型,我们就使用'&lt;&lt;'运算符将此定义插入值表达式:

EQ,LBRACE,RBRACE = map(Suppress,"={}")

name = Word(alphas, alphanums+"_")
value = Forward()
entry = Group(name + EQ + value)

real = Regex(r"[+-]?\d+\.\d*").setParseAction(lambda x: float(x[0]))
integer = Regex(r"[+-]?\d+").setParseAction(lambda x: int(x[0]))
quotedString.setParseAction(removeQuotes)

struct = Group(LBRACE + ZeroOrMore(entry) + RBRACE)
value << (quotedString | struct | real | integer)

对real和integer的解析操作会在解析时将这些元素从字符串转换为float或ints,这样这些值可以在解析后立即用作实际类型(无需后处理来执行string-to - 其他类型的转换)。

您的示例是一个或多个条目的集合,因此我们使用它来解析总输入:

result = OneOrMore(entry).parseString(sample)

我们可以将解析后的数据作为嵌套列表访问,但显示效果并不是很好。此代码使用pprint来打印格式化的嵌套列表:

from pprint import pprint
pprint(result.asList())

,并提供:

[['company', 'My Company'],
 ['phone', '555-5555'],
 ['people',
  [['person',
    [['name', 'Bob'],
     ['location', 'Seattle'],
     ['settings', [['size', 1], ['color', 'red']]]]],
   ['person',
    [['name', 'Joe'],
     ['location', 'Seattle'],
     ['settings', [['size', 2], ['color', 'blue']]]]]]]]

请注意,所有字符串都只是没有括号引号的字符串,而int是实际的整数。

通过认识到条目格式实际上定义了一个适合像Python字典一样访问的名称 - 值对,我们可以做得更好一些。我们的解析器可以通过一些小的改动来做到这一点:

将结构定义更改为:

struct = Dict(LBRACE + ZeroOrMore(entry) + RBRACE)

和整个解析器:

result = Dict(OneOrMore(entry)).parseString(sample)

Dict类将解析后的内容视为名称,后跟值,可以递归完成。通过这些更改,我们现在可以访问结果中的数据,如dict中的元素:

print result['phone']

或类似对象中的属性:

print result.company

使用dump()方法查看结构或子结构的内容:

for person in result.people:
    print person.dump()
    print

打印:

['person', ['name', 'Bob'], ['location', 'Seattle'], ['settings', ['size', 1], ['color', 'red']]]
- location: Seattle
- name: Bob
- settings: [['size', 1], ['color', 'red']]
  - color: red
  - size: 1

['person', ['name', 'Joe'], ['location', 'Seattle'], ['settings', ['size', 2], ['color', 'blue']]]
- location: Seattle
- name: Joe
- settings: [['size', 2], ['color', 'blue']]
  - color: blue
  - size: 2

答案 1 :(得分:1)

没有“简单”的方式,但有更难和不那么难的方式。如果您不想对事物进行硬编码,那么在某些时候您将不得不将其解析为结构化格式。这将涉及逐个解析每一行,适当地标记它(例如,正确地将键与值分开),然后确定你想如何处理该行。

您可能需要以中间格式存储数据,例如(解析)树,以便考虑任意嵌套关系(由缩进和大括号表示),然后在完成解析数据后,取出结果树,然后再次通过它来获取您的数组或JSON。

有一些库可用,例如ANTLR,它们可以处理一些解决如何编写解析器的手动工作。

答案 2 :(得分:1)

看看这段代码:

still_not_valid_json = re.sub (r'(\w+)=', r'"\1":', pseudo_json ) #1
this_one_is_tricky = re.compile ('("|\d)\n(?!\s+})', re.M)
that_one_is_tricky_too = re.compile ('(})\n(?=\s+\")', re.M)
nearly_valid_json = this_one_is_tricky.sub (r'\1,\n', still_not_valid_json) #2
nearly_valid_json = that_one_is_tricky_too.sub (r'\1,\n', nearly_valid_json) #3
valid_json = '{' +  nearly_valid_json + '}' #4

你可以通过一些替换来转换parseable json中的伪_json。

  1. 将'='替换为':'
  2. 在简单值(如“2”或“Joe”)和下一个字段
  3. 之间添加缺少的逗号
  4. 在复数值的右括号和下一个字段之间添加缺少的逗号
  5. 用大括号包围它
  6. 仍有问题。在您的示例中,'people'字典包含两个相似的键'person'。解析后,字典中只剩下一个键。这是解析后我得到的:{u'phone': u'555-5555', u'company': u'My Company', u'people': {u'person': {u'settings': {u'color': u'blue', u'size': 2}, u'name': u'Joe', u'location': u'Seattle'}}}

    如果只有你可以将'person ='的第二次出现替换为'person1 ='等等......

答案 3 :(得分:0)

将'='替换为':',然后将其读作json,添加尾随逗号

答案 4 :(得分:0)

好的,我提出了一个最终的解决方案,它实际上将这些数据转换为我最初想要的JSON友好的Dict。它首先使用Pyparsing将数据转换为一系列嵌套列表,然后遍历列表并将其转换为JSON。这使我能够克服Pyparsing的toDict()方法无法处理同一对象具有两个同名属性的问题。要确定列表是普通列表还是属性/值对,{P <{1}}方法会在Pyparsing检测到它们时在属性名称前面添加字符串prependPropertyToken

__property__