有没有办法覆盖python的json处理程序?

时间:2013-07-06 14:42:01

标签: python json

我在json中编码无穷大时遇到了麻烦。

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

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

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

4 个答案:

答案 0 :(得分:6)

在这里查看来源: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

答案 1 :(得分:5)

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

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

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

  2. 制作要编码的对象的副本,并用JSONEncoder或其他根据需要编码的其他对象替换任何出现的infinity / nan。

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

    null

    显然你应该考虑字符串中的文本>>> 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]' 等,所以即使在这里,强大的解决方案也不是直接的,也不是优雅的。

答案 2 :(得分:0)

你可以沿着这些方向做点什么:

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

答案 3 :(得分:0)

上下文

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

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

限定符

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

优势

  • 如果最终用户拥有Python 2.x parse_constant,Python 3.x&#39 {s} json或使用json,则无关紧要(例如,simplejson
  • 它只使用不太可能改变的公共import simplejson as json接口。

注意事项

  • 这需要约3倍的时间来编码
  • 此实现不处理object_pairs_hook,因为它不适用于python 2.6
  • 无效的分隔符将失败

代码

json