【发布时间】:2013-07-04 10:45:11
【问题描述】:
我在 json 中编码无穷大时遇到问题。
json.dumps 会将其转换为 "Infinity",但我希望它确实将其转换为 null 或我选择的其他值。
不幸的是,设置 default 参数似乎仅在转储尚未理解对象时才有效,否则默认处理程序似乎被绕过。
有没有一种方法可以对对象进行预编码、更改类型/类的默认编码方式,或者在正常编码之前将某个类型/类转换为不同的对象?
【问题讨论】:
我在 json 中编码无穷大时遇到问题。
json.dumps 会将其转换为 "Infinity",但我希望它确实将其转换为 null 或我选择的其他值。
不幸的是,设置 default 参数似乎仅在转储尚未理解对象时才有效,否则默认处理程序似乎被绕过。
有没有一种方法可以对对象进行预编码、更改类型/类的默认编码方式,或者在正常编码之前将某个类型/类转换为不同的对象?
【问题讨论】:
看这里的来源: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
【讨论】:
AttributeError: 'FloatEncoder' object has no attribute 'encoding'
不,没有简单的方法可以实现这一点。事实上,根据标准,NaN 和 Infinity 浮点值根本不应该用 json 序列化。
Python 使用标准的扩展。您可以将allow_nan=False 参数传递给dumps,使python 编码符合标准,但这将引发ValueError for infinity/nans 即使您提供default 函数。
你有两种方法可以做你想做的事:
子类 JSONEncoder 并更改这些值的编码方式。请注意,您必须考虑序列可能包含无穷大值等的情况。AFAIK 没有 API 可以重新定义特定类的对象的编码方式。
制作对象的副本以进行编码,并将任何出现的 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,因此即使在这里,一个强大的解决方案也不是立竿见影的,也不是优雅的。
【讨论】:
float("inf")检查是否相等。
您可以按照以下方式做一些事情:
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]]
【讨论】:
iterencode -- 很好 -- Python 2.7 不使用 iterencode 并且它不起作用。显示一些工作代码!
我遇到了这个问题,不想在项目中引入额外的依赖来处理这种情况。此外,我的项目支持 Python 2.6、2.7、3.3 和 3.4 以及simplejson 的用户。不幸的是,在这些版本之间有 三种 iterencode 的不同实现,因此不希望对特定版本进行硬编码。
希望这会帮助其他有类似要求的人!
如果与项目的其他组件相比,json.dumps 调用的编码时间/处理能力较小,则可以利用 parse_constant kwarg 对 JSON 进行取消编码/重新编码以获得所需的结果。
json、Python 3.x 的 json 或使用 simplejson(例如,import simplejson as json)并不重要json 接口。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()... 这给出了null:json.dumps(float('nan'), cls=CustomJSONEncoder),但这给出了NaN:json.dump(float('nan'), open('/tmp/a', 'w'), cls=CustomJSONEncoder)。因为 dump() 使用 iterencode 而 dumps 使用 encode()