【问题标题】:Is there a way to override python's json handler?有没有办法覆盖 python 的 json 处理程序?
【发布时间】:2013-07-04 10:45:11
【问题描述】:

我在 json 中编码无穷大时遇到问题。

json.dumps 会将其转换为 "Infinity",但我希望它确实将其转换为 null 或我选择的其他值。

不幸的是,设置 default 参数似乎仅在转储尚未理解对象时才有效,否则默认处理程序似乎被绕过。

有没有一种方法可以对对象进行预编码、更改类型/类的默认编码方式,或者在正常编码之前将某个类型/类转换为不同的对象?

【问题讨论】:

    标签: python json


    【解决方案1】:

    看这里的来源:http://hg.python.org/cpython/file/7ec9255d4189/Lib/json/encoder.py

    如果您将 JSONEncoder 子类化,则可以仅覆盖 iterencode(self, o, _one_shot=False) 方法,该方法具有 Infinity 的显式特殊大小写(在内部函数中)。

    要使其可重用,您还需要更改 __init__ 以采用一些新选项,并将它们存储在类中。

    或者,您可以从 pypi 中选择一个 json 库,该库具有您正在寻找的适当可扩展性:https://pypi.python.org/pypi?%3Aaction=search&term=json&submit=search

    这是一个例子:

    import json
    
    class FloatEncoder(json.JSONEncoder):
    
        def __init__(self, nan_str = "null", **kwargs):
            super(FloatEncoder,self).__init__(**kwargs)
        self.nan_str = nan_str
    
        # uses code from official python json.encoder module.
        # Same licence applies.
        def iterencode(self, o, _one_shot=False):
            """Encode the given object and yield each string
            representation as available.
    
            For example::
    
                for chunk in JSONEncoder().iterencode(bigobject):
                    mysocket.write(chunk)
            """
            if self.check_circular:
                markers = {}
            else:
                markers = None
            if self.ensure_ascii:
                _encoder = json.encoder.encode_basestring_ascii
            else:
                _encoder = json.encoder.encode_basestring
            if self.encoding != 'utf-8':
                def _encoder(o, _orig_encoder=_encoder,
                             _encoding=self.encoding):
                    if isinstance(o, str):
                        o = o.decode(_encoding)
                    return _orig_encoder(o)
    
            def floatstr(o, allow_nan=self.allow_nan,
                         _repr=json.encoder.FLOAT_REPR,
                         _inf=json.encoder.INFINITY,
                         _neginf=-json.encoder.INFINITY,
                         nan_str = self.nan_str):
                # Check for specials. Note that this type of test is 
                # processor and/or platform-specific, so do tests which
                # don't depend on the internals.
    
                if o != o:
                    text = nan_str
                elif o == _inf:
                    text = 'Infinity'
                elif o == _neginf:
                    text = '-Infinity'
                else:
                    return _repr(o)
    
                if not allow_nan:
                    raise ValueError(
                        "Out of range float values are not JSON compliant: " +
                        repr(o))
    
                return text
    
            _iterencode = json.encoder._make_iterencode(
                    markers, self.default, _encoder, self.indent, floatstr,
                    self.key_separator, self.item_separator, self.sort_keys,
                    self.skipkeys, _one_shot)
            return _iterencode(o, 0)
    
    
    example_obj = {
        'name': 'example',
        'body': [
            1.1,
            {"3.3": 5, "1.1": float('Nan')},
            [float('inf'), 2.2]
        ]}
    
    print json.dumps(example_obj, cls=FloatEncoder)
    

    ideone:http://ideone.com/dFWaNj

    【讨论】:

    • 给定的 sn-p 在 Python3.6 中不起作用。给AttributeError: 'FloatEncoder' object has no attribute 'encoding'
    • @AbdealiJK Python 3.6 在编写此代码 3.5 年后发布。 python.org/downloads/release/python-360
    • 啊,没有检查日期 ^_^ 看来我得想点别的办法了,因为我确实需要维护对我的应用程序的 3.3+ 支持
    【解决方案2】:

    不,没有简单的方法可以实现这一点。事实上,根据标准,NaNInfinity 浮点值根本不应该用 json 序列化。 Python 使用标准的扩展。您可以将allow_nan=False 参数传递给dumps,使python 编码符合标准,但这将引发ValueError for infinity/nans 即使您提供default 函数

    你有两种方法可以做你想做的事:

    1. 子类 JSONEncoder 并更改这些值的编码方式。请注意,您必须考虑序列可能包含无穷大值等的情况。AFAIK 没有 API 可以重新定义特定类的对象的编码方式。

    2. 制作对象的副本以进行编码,并将任何出现的 infinity/nan 替换为 None 或其他根据需要编码的对象。

    一个不太健壮但更简单的解决方案是修改编码数据,例如用null替换所有Infinity子字符串:

    >>> import re
    >>> infty_regex = re.compile(r'\bInfinity\b')
    >>> def replace_infinities(encoded):
    ...     regex = re.compile(r'\bInfinity\b')
    ...     return regex.sub('null', encoded)
    ... 
    >>> import json
    >>> replace_infinities(json.dumps([1, 2, 3, float('inf'), 4]))
    '[1, 2, 3, null, 4]'
    

    显然,您应该考虑字符串等内部的文本 Infinity,因此即使在这里,一个强大的解决方案也不是立竿见影的,也不是优雅的。

    【讨论】:

    • 必要的改动其实很简单。
    • 通过替换 Infinity before JSON 编码可能更容易制定稳健的解决方案。这样你就可以用float("inf")检查是否相等。
    【解决方案3】:

    您可以按照以下方式做一些事情:

    import json
    import math
    
    target=[1.1,1,2.2,float('inf'),float('nan'),'a string',int(2)]
    
    def ffloat(f):
        if not isinstance(f,float):
            return f
        if math.isnan(f):
            return 'custom NaN'
        if math.isinf(f):
            return 'custom inf'
        return f
    
    print 'regular json:',json.dumps(target)      
    print 'customized:',json.dumps(map(ffloat,target))     
    

    打印:

    regular json: [1.1, 1, 2.2, Infinity, NaN, "a string", 2]
    customized: [1.1, 1, 2.2, "custom inf", "custom NaN", "a string", 2]
    

    如果你想处理嵌套的数据结构,这也不是那么难:

    import json
    import math
    from collections import Mapping, Sequence
    
    def nested_json(o):
        if isinstance(o, float):
            if math.isnan(o):
                return 'custom NaN'
            if math.isinf(o):
                return 'custom inf'
            return o
        elif isinstance(o, basestring):
            return o
        elif isinstance(o, Sequence):
            return [nested_json(item) for item in o]
        elif isinstance(o, Mapping):
            return dict((key, nested_json(value)) for key, value in o.iteritems())
        else:
            return o    
    
    nested_tgt=[1.1,{1.1:float('inf'),3.3:5},(float('inf'),2.2),]
    
    print 'regular json:',json.dumps(nested_tgt)      
    print 'nested json',json.dumps(nested_json(nested_tgt))
    

    打印:

    regular json: [1.1, {"3.3": 5, "1.1": Infinity}, [Infinity, 2.2]]
    nested json [1.1, {"3.3": 5, "1.1": "custom inf"}, ["custom inf", 2.2]]
    

    【讨论】:

    • 这不会真正有用,除非处理扁平结构。
    • 现在您正在编写自己的 json 编码器。此时,将 JSONEncoder 子类化或使用其他库确实更有意义。
    • @Marcin:这几乎不是一个完整的 json 编码器。并且代码有效。您将拥有 JSONEncoder 的子类拦截 iterencode -- 很好 -- Python 2.7 不使用 iterencode 并且它不起作用。显示一些工作代码!
    • 仍在等待查看工作代码。打印单个平面和嵌套数据结构。 It is harder than you think
    • 我看不出这比遍历数据结构以查找有问题的浮点数更容易。您指向的代码是密集的 60 行代码,当发布新版本的 JSON 编码器时,您可能会遇到版本问题。为什么更容易?
    【解决方案4】:

    上下文

    我遇到了这个问题,不想在项目中引入额外的依赖来处理这种情况。此外,我的项目支持 Python 2.6、2.7、3.3 和 3.4 以及simplejson 的用户。不幸的是,在这些版本之间有 三种 iterencode 的不同实现,因此不希望对特定版本进行硬编码。

    希望这会帮助其他有类似要求的人!

    预选赛

    如果与项目的其他组件相比,json.dumps 调用的编码时间/处理能力较小,则可以利用 parse_constant kwarg 对 JSON 进行取消编码/重新编码以获得所需的结果。

    好处

    • 最终用户是否拥有 Python 2.x 的 json、Python 3.x 的 json 或使用 simplejson(例如,import simplejson as json)并不重要
    • 它只使用不太可能更改的公共json 接口。

    注意事项

    • 这将花费大约 3 倍的时间来编码内容
    • 此实现不处理 object_pairs_hook,因为那样它就不适用于 python 2.6
    • 无效的分隔符将失效

    代码

    class StrictJSONEncoder(json.JSONEncoder):
    
        def default(self, o):
            """Make sure we don't instantly fail"""
            return o
    
        def coerce_to_strict(self, const):
            """
            This is used to ultimately *encode* into strict JSON, see `encode`
    
            """
            # before python 2.7, 'true', 'false', 'null', were include here.
            if const in ('Infinity', '-Infinity', 'NaN'):
                return None
            else:
                return const
    
        def encode(self, o):
            """
            Load and then dump the result using parse_constant kwarg
    
            Note that setting invalid separators will cause a failure at this step.
    
            """
    
            # this will raise errors in a normal-expected way
            encoded_o = super(StrictJSONEncoder, self).encode(o)
    
            # now:
            #    1. `loads` to switch Infinity, -Infinity, NaN to None
            #    2. `dumps` again so you get 'null' instead of extended JSON
            try:
                new_o = json.loads(encoded_o, parse_constant=self.coerce_to_strict)
            except ValueError:
    
                # invalid separators will fail here. raise a helpful exception
                raise ValueError(
                    "Encoding into strict JSON failed. Did you set the separators "
                    "valid JSON separators?"
                )
            else:
                return json.dumps(new_o, sort_keys=self.sort_keys,
                                  indent=self.indent,
                                  separators=(self.item_separator,
                                              self.key_separator))
    

    【讨论】:

    • 这似乎不适用于dump()... 这给出了nulljson.dumps(float('nan'), cls=CustomJSONEncoder),但这给出了NaNjson.dump(float('nan'), open('/tmp/a', 'w'), cls=CustomJSONEncoder)。因为 dump() 使用 iterencode 而 dumps 使用 encode()
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2018-10-05
    • 1970-01-01
    • 1970-01-01
    • 2011-12-25
    • 2020-09-15
    相关资源
    最近更新 更多