Python json.load缺少数组钩子和解析回调?

时间:2017-09-02 08:43:34

标签: python arrays json

为什么json.loads只允许object_hookparse_floatparse_intparse_constant

json.loads(fp[, encoding[, cls[, object_hook[, parse_float[, parse_int[, parse_constant[, object_pairs_hook[, **kw]]]]]]]])

它们本质上都是回调,在解析过程中使用其返回值。

我的目的是通过修改回调参数来使用这些回调来构建我自己的JSON字符串表示。但是没有数组,字符串或布尔值的回调 - 它似乎相当有限。

举个简单的例子:

import json

def _object_hook(d):
    print(d)
    return d

json.loads('{"a": [1, [2, [3]]]}', object_hook=_object_hook)

...导致只有一次调用_object_hook(因为只有一个对象)。

>>> {u'a': [1, [2, [3]]]}

给出一个任意深度的嵌套数组,传递数组的工作(递归地,或者可能是广度优先/深度优先遍历)仍然存在。

然后,字符串也是出于某种原因和例外:

import json

def _object_hook(o):
    print('_object_hook', o)
    return o

def _parse_float(f):
    print('_parse_float', f)
    return f

def _parse_int(i):
    print('_parse_int', i)
    return i

def _parse_constant(c):
    print('_parse_constant', c)
    return c

json.loads('{"a": [1, [2, [3.1], ["4"]]]}',
    object_hook=_object_hook,
    parse_int=_parse_int,
    parse_float=_parse_float,
    parse_constant=_parse_constant)

...没有办法处理字符串(下面的结果中省略了“4”):

('_parse_int', '1')
('_parse_int', '2')
('_parse_float', '3.1')
('_object_hook', {u'a': ['1', ['2', ['3.1'], [u'4']]]})

也许我的期望是错误的。但是将JSON字符串解析为Python字典或列表似乎很浪费,只是将其再次解析为自定义格式。

没有字符串的数组挂钩或解析回调,boolean,null等。我使用json.loads然后将生成的Python表示解析为我自己的Python类吗?

3 个答案:

答案 0 :(得分:1)

实际上,json.loads调用其scanner来解析输入字符串,并且可以通过在自定义类中重建scanner来挂钩所有行为。

import json
from json.scanner import py_make_scanner
from json.decoder import JSONArray

class CustomizedDecoder(json.JSONDecoder):
    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)

        def parse_array(*_args, **_kwargs):
            values, end = JSONArray(*_args, **_kwargs)
            for item in values:
                print(item)  # Is it what you want?
            return values, end

        self.parse_array = parse_array
        self.scan_once = py_make_scanner(self)

json.loads('{"a": [1, [2, [3.1], ["4"]]]}', cls=CustomizedDecoder)

输出

3.1
4
2
[3.1]
['4']
1
[2, [3.1], ['4']]

此外,您可以通过执行完全相同的操作来挂接其他几个功能。

self.object_hook = object_hook
self.parse_float = parse_float or float
self.parse_int = parse_int or int
self.parse_constant = parse_constant or _CONSTANTS.__getitem__
self.object_pairs_hook = object_pairs_hook
self.parse_object = JSONObject
self.parse_array = JSONArray
self.parse_string = scanstring

答案 1 :(得分:0)

如果您愿意考虑稍慢的解析,可以使用ruamel.yaml解析器(免责声明:我是该程序包的作者)。由于YAML 1.2是JSON的超集,用于所有实际目的,您可以继承Constructor

import sys
from ruamel.yaml import YAML, SafeConstructor

json_str = '{"a": [1, [2.0, True, [3, null]]]}'


class MyConstructor(SafeConstructor):
    def construct_yaml_null(self, node):
        print('null')
        data = SafeConstructor.construct_yaml_null(self, node)
        return data

    def construct_yaml_bool(self, node):
        print('bool')
        data = SafeConstructor.construct_yaml_bool(self, node)
        return data

    def construct_yaml_int(self, node):
        print('int')
        data = SafeConstructor.construct_yaml_int(self, node)
        return data

    def construct_yaml_float(self, node):
        print('float')
        data = SafeConstructor.construct_yaml_float(self, node)
        return data

    def construct_yaml_str(self, node):
        print('str')
        data = SafeConstructor.construct_yaml_str(self, node)
        return data

    def construct_yaml_seq(self, node):
        print('seq')
        for data in SafeConstructor.construct_yaml_seq(self, node):
            pass
        return data

    def construct_yaml_map(self, node):
        print('map')
        for data in SafeConstructor.construct_yaml_map(self, node):
            pass
        return data


MyConstructor.add_constructor(
    u'tag:yaml.org,2002:null',
    MyConstructor.construct_yaml_null)

MyConstructor.add_constructor(
    u'tag:yaml.org,2002:bool',
    MyConstructor.construct_yaml_bool)

MyConstructor.add_constructor(
    u'tag:yaml.org,2002:int',
    MyConstructor.construct_yaml_int)

MyConstructor.add_constructor(
    u'tag:yaml.org,2002:float',
    MyConstructor.construct_yaml_float)

MyConstructor.add_constructor(
    u'tag:yaml.org,2002:str',
    MyConstructor.construct_yaml_str)

MyConstructor.add_constructor(
    u'tag:yaml.org,2002:seq',
    MyConstructor.construct_yaml_seq)

MyConstructor.add_constructor(
    u'tag:yaml.org,2002:map',
    MyConstructor.construct_yaml_map)


yaml = YAML(typ='safe')
yaml.Constructor = MyConstructor

data = yaml.load(json_str)
print(data)

只需将每个construct_yaml_XYZ方法中的代码替换为创建所需对象的代码并将其返回。

有趣的生意"在创建映射/字典时使用for循环。 sequence / list,用于解包创建这些对象的两步过程("真实" YAML输入以使用锚点/别名来处理递归数据结构)。

以上输出:

map
str
seq
int
seq
float
bool
seq
int
null
{'a': [1, [2.0, True, [3, None]]]}

您也可以在较低级别挂钩YAML解析器,但这并不会使实现更容易,而且可能只是稍微快一点。

答案 2 :(得分:0)

@ martijn-pieters有正确的方法。我正在试图使用钩子来解析JSON。它们的存在是为了增强解析,但不能取代它。