2

我想解析类似 JSON 的字符串。它们与普通 JSON 的唯一区别是数组中存在连续的逗号。当有两个这样的逗号时,它隐含地表示null应该在中间插入。例子:

       JSON-like:  ["foo",,,"bar",[1,,3,4]]
      Javascript:  ["foo",null,null,"bar",[1,null,3,4]]
Decoded (Python):  ["foo", None, None, "bar", [1, None, 3, 4]]

本机json.JSONDecoder类不允许我更改数组解析的行为。我只能修改对象(字典)、整数、浮点数、字符串的解析器(通过将 kwargs 函数赋予给JSONDecoder(),请参阅文档)。

那么,这是否意味着我必须从头开始编写 JSON 解析器?的 Python 代码json是可用的,但它相当混乱。我宁愿使用它的内部结构而不是复制它的代码!

4

6 回答 6

5

由于您要解析的不是 JSON本身,而是一种与 JSON 非常相似的不同语言,因此您可能需要自己的解析器。

幸运的是,这并不像听起来那么难。您可以使用 Python 解析器生成器,例如pyparsing。JSON 可以使用相当简单的上下文无关语法(我在此处找到)来完全指定,因此您应该能够对其进行修改以满足您的需求。

于 2012-04-07T19:11:38.973 回答
3

尝试小而简单的解决方法:

  1. 将类似 JSON 的数据转换为字符串。
  2. 将“,”替换为“,null”。
  3. 将其转换为您的代表。
  4. 让 JSONDecoder() 完成繁重的工作。

    1. & 3. 如果你已经处理过字符串,可以省略。

(如果转换为字符串不切实际,请使用此信息更新您的问题!)

于 2012-04-07T19:06:22.160 回答
2

您可以通过使用后向表达式一次性替换Lattyware 的/ przemo_li 的答案,即“替换所有以逗号开头的逗号”:

>>> s = '["foo",,,"bar",[1,,3,4]]'

>>> re.sub(r'(?<=,)\s*,', ' null,', s)
'["foo", null, null,"bar",[1, null,3,4]]'

请注意,这适用于您可以假设字符串文字中没有连续逗号的小事情,例如。一般来说,正则表达式不足以处理这个问题,而Taymon使用真正解析器的方法是唯一完全正确的解决方案。

于 2012-04-07T19:22:38.387 回答
1

我查看了 Taymon 的推荐,pyparsing,并成功破解了此处提供的示例以满足我的需要。它在模拟 Javascript 方面效果很好,eval()在一种情况下失败了:尾随逗号。应该有一个可选的尾随逗号——参见下面的测试——但我找不到任何合适的方法来实现它。

from pyparsing import *

TRUE = Keyword("true").setParseAction(replaceWith(True))
FALSE = Keyword("false").setParseAction(replaceWith(False))
NULL = Keyword("null").setParseAction(replaceWith(None))

jsonString = dblQuotedString.setParseAction(removeQuotes)
jsonNumber = Combine(Optional('-') + ('0' | Word('123456789', nums)) +
                    Optional('.' + Word(nums)) +
                    Optional(Word('eE', exact=1) + Word(nums + '+-', nums)))

jsonObject = Forward()
jsonValue = Forward()
# black magic begins
commaToNull = Word(',,', exact=1).setParseAction(replaceWith(None))
jsonElements = ZeroOrMore(commaToNull) + Optional(jsonValue) + ZeroOrMore((Suppress(',') + jsonValue) | commaToNull)
# black magic ends
jsonArray = Group(Suppress('[') + Optional(jsonElements) + Suppress(']'))
jsonValue << (jsonString | jsonNumber | Group(jsonObject) | jsonArray | TRUE | FALSE | NULL)
memberDef = Group(jsonString + Suppress(':') + jsonValue)
jsonMembers = delimitedList(memberDef)
jsonObject << Dict(Suppress('{') + Optional(jsonMembers) + Suppress('}'))

jsonComment = cppStyleComment
jsonObject.ignore(jsonComment)

def convertNumbers(s, l, toks):
    n = toks[0]
    try:
        return int(n)
    except ValueError:
        return float(n)

jsonNumber.setParseAction(convertNumbers)

def test():
    tests = (
        '[1,2]',       # ok
        '[,]',         # ok
        '[,,]',        # ok
        '[  , ,  , ]', # ok
        '[,1]',        # ok
        '[,,1]',       # ok
        '[1,,2]',      # ok
        '[1,]',        # failure, I got [1, None], I should have [1]
        '[1,,]',       # failure, I got [1, None, None], I should have [1, None]
    )
    for test in tests:
        results = jsonArray.parseString(test)
        print(results.asList())
于 2012-04-07T20:41:23.753 回答
1

这是一种骇人听闻的做法,但一种解决方案是简单地对 JSON-ish 数据进行一些字符串修改,以便在解析之前使其符合要求。

import re
import json

not_quite_json = '["foo",,,"bar",[1,,3,4]]'
not_json = True
while not_json:
    not_quite_json, not_json = re.subn(r',\s*,', ', null, ', not_quite_json)

这给我们留下了:

'["foo", null, null, "bar",[1, null, 3,4]]'

然后我们可以这样做:

json.loads(not_quite_json)

给我们:

['foo', None, None, 'bar', [1, None, 3, 4]]

请注意,它不像替换那么简单,因为替换还会插入可能需要替换的逗号。鉴于此,您必须循环访问,直到无法进行更多替换。在这里,我使用了一个简单的正则表达式来完成这项工作。

于 2012-04-07T19:14:54.630 回答
0

对于那些寻找快速而肮脏的东西来转换一般 JS 对象(到 dicts)的人。一个真实站点的页面的某些部分给了我一些我想处理的对象。日期有“新”结构,它在一行中,中间没有空格,所以两行就足够了:

data=sub(r'new Date\(([^)])*\)', r'\1', data)
data=sub(r'([,{])(\w*):', r'\1"\2":', data)

然后 json.loads() 工作正常。你的旅费可能会改变:)

于 2018-09-08T11:37:23.427 回答