我使用simplejson转换python dicts,但我想为某些已定义的键自定义输出。
例如,我希望密钥callback
和scope
始终使用无周围引号进行渲染,以便javascript可以解释数据而不是将其读取为一个字符串。
示例所需输出:
"data":{
"name":"Julien"
,"callback":function() {alert('hello, world');}
,"params":{
"target":"div2"
,"scope":this
}
}
请注意,callback
和scope
键的值中没有周围的引号。
我尝试过创建自定义类和子类JSONencoder,但没有运气。
class JsonSpecialKey(object):
def __init__(self, data):
self.data = data
class JsonSpecialEncoder(simplejson.JSONEncoder):
def default(self, obj):
if isinstance (obj, JsonSpecialKey):
# how to remove quotes ??
return obj.data
return simplejson.JSONEncoder.default(self, obj)
d = {'testKey':JsonSpecialKey('function() {alert(123);}')}
print simplejson.dumps(d, cls=JsonSpecialEncoder, ensure_ascii=False, indent=4)
我知道JSON建议中生成的JSON可能无效,但它对某些JS应用程序很重要。
我已经尝试了一些正则表达式的解决方法,但是内部数据的多行和内联函数变得越来越复杂。
谢谢!
答案 0 :(得分:1)
我通过修改json代码
成功了import json
from json.encoder import encode_basestring_ascii ,encode_basestring,FLOAT_REPR,INFINITY,c_make_encoder
class JsonSpecialKey(object):
def __init__(self, data):
self.data = data
def _make_iterencode(markers, _default, _encoder, _indent, _floatstr,
_key_separator, _item_separator, _sort_keys, _skipkeys, _one_shot,
## HACK: hand-optimized bytecode; turn globals into locals
ValueError=ValueError,
dict=dict,
float=float,
id=id,
int=int,
isinstance=isinstance,
list=list,
str=str,
tuple=tuple,
):
if _indent is not None and not isinstance(_indent, str):
_indent = ' ' * _indent
def _iterencode_list(lst, _current_indent_level):
if not lst:
yield '[]'
return
if markers is not None:
markerid = id(lst)
if markerid in markers:
raise ValueError("Circular reference detected")
markers[markerid] = lst
buf = '['
if _indent is not None:
_current_indent_level += 1
newline_indent = '\n' + _indent * _current_indent_level
separator = _item_separator + newline_indent
buf += newline_indent
else:
newline_indent = None
separator = _item_separator
first = True
for value in lst:
if first:
first = False
else:
buf = separator
if isinstance(value, str):
yield buf + _encoder(value)
elif value is None:
yield buf + 'null'
elif value is True:
yield buf + 'true'
elif value is False:
yield buf + 'false'
elif isinstance(value, int):
yield buf + str(value)
elif isinstance(value, float):
yield buf + _floatstr(value)
elif isinstance(value, JsonSpecialKey):
yield buf + value.data
else:
yield buf
if isinstance(value, (list, tuple)):
chunks = _iterencode_list(value, _current_indent_level)
elif isinstance(value, dict):
chunks = _iterencode_dict(value, _current_indent_level)
else:
chunks = _iterencode(value, _current_indent_level)
for chunk in chunks:
yield chunk
if newline_indent is not None:
_current_indent_level -= 1
yield '\n' + _indent * _current_indent_level
yield ']'
if markers is not None:
del markers[markerid]
def _iterencode_dict(dct, _current_indent_level):
if not dct:
yield '{}'
return
if markers is not None:
markerid = id(dct)
if markerid in markers:
raise ValueError("Circular reference detected")
markers[markerid] = dct
yield '{'
if _indent is not None:
_current_indent_level += 1
newline_indent = '\n' + _indent * _current_indent_level
item_separator = _item_separator + newline_indent
yield newline_indent
else:
newline_indent = None
item_separator = _item_separator
first = True
if _sort_keys:
items = sorted(dct.items(), key=lambda kv: kv[0])
else:
items = dct.items()
for key, value in items:
if isinstance(key, str):
pass
# JavaScript is weakly typed for these, so it makes sense to
# also allow them. Many encoders seem to do something like this.
elif isinstance(key, float):
key = _floatstr(key)
elif key is True:
key = 'true'
elif key is False:
key = 'false'
elif key is None:
key = 'null'
elif isinstance(key, int):
key = str(key)
elif _skipkeys:
continue
else:
raise TypeError("key " + repr(key) + " is not a string")
if first:
first = False
else:
yield item_separator
yield _encoder(key)
yield _key_separator
if isinstance(value, str):
yield _encoder(value)
elif value is None:
yield 'null'
elif value is True:
yield 'true'
elif value is False:
yield 'false'
elif isinstance(value, int):
yield str(value)
elif isinstance(value, float):
yield _floatstr(value)
elif isinstance(value, JsonSpecialKey):
yield value.data
else:
if isinstance(value, (list, tuple)):
chunks = _iterencode_list(value, _current_indent_level)
elif isinstance(value, dict):
chunks = _iterencode_dict(value, _current_indent_level)
else:
chunks = _iterencode(value, _current_indent_level)
for chunk in chunks:
yield chunk
if newline_indent is not None:
_current_indent_level -= 1
yield '\n' + _indent * _current_indent_level
yield '}'
if markers is not None:
del markers[markerid]
def _iterencode(o, _current_indent_level):
if isinstance(o, str):
yield _encoder(o)
elif o is None:
yield 'null'
elif o is True:
yield 'true'
elif o is False:
yield 'false'
elif isinstance(o, int):
yield str(o)
elif isinstance(o, float):
yield _floatstr(o)
elif isinstance(o, JsonSpecialKey):
yield o.data
elif isinstance(o, (list, tuple)):
for chunk in _iterencode_list(o, _current_indent_level):
yield chunk
elif isinstance(o, dict):
for chunk in _iterencode_dict(o, _current_indent_level):
yield chunk
else:
if markers is not None:
markerid = id(o)
if markerid in markers:
raise ValueError("Circular reference detected")
markers[markerid] = o
o = _default(o)
for chunk in _iterencode(o, _current_indent_level):
yield chunk
if markers is not None:
del markers[markerid]
return _iterencode
class JsonSpecialEncoder(json.JSONEncoder):
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 = encode_basestring_ascii
else:
_encoder = encode_basestring
def floatstr(o, allow_nan=self.allow_nan,
_repr=FLOAT_REPR, _inf=INFINITY, _neginf=-INFINITY):
# 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'
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 = _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)
d = {'testKey':JsonSpecialKey('function() {alert(123);}')}
print (json.dumps(d, cls=JsonSpecialEncoder, ensure_ascii=False, indent=4))
已编辑:我修改的代码的精度
来自json.encode
我接受了函数_make_iterencode
添加类似
的内容 elif isinstance(value, JsonSpecialKey):
yield buf + value.data
在三个地方
来自JsonEncoder
的我采用了方法iterencode
但我只是强迫_iterencode
成为我的自定义函数_make_iterencode
_iterencode = _make_iterencode(
markers, self.default, _encoder, self.indent, floatstr,
self.key_separator, self.item_separator, self.sort_keys,
self.skipkeys, _one_shot)
我希望它很清楚
答案 1 :(得分:1)
我稍微改变了一下这个问题并假设我的客户端会将一个函数编码为字典{"__js_func__": "function() { return 10; }"}
,因为我不知道我的函数究竟会被调用。
基本上我编写了一个自定义解码器,它将带有__js_func__
键的dict转换为JSFunction对象,然后对encode_basestring_ascii
函数进行猴子修补,以便不添加此类对象的引号。我的JSFunction对象扩展了str,这意味着encode_basestring_ascii
被调用普通字符串和JSFunction对象。
import json
import json.encoder
class JSFunction(str):
def __repr__(self):
return '<JSFunction: self>'
@staticmethod
def decode_js_func(dct):
"""Turns any dictionary that contains a __js_func__ key into a JSFunction object.
Used when loads()'ing the json sent to us by the webserver.
"""
if '__js_func__' in dct:
return JSFunction(dct['__js_func__'])
return dct
@staticmethod
def encode_basestring(s):
"""A function we use to monkeypatch json.encoder.encode_basestring_ascii that dumps JSFunction objects without quotes."""
if isinstance(s, JSFunction):
return str(s)
else:
return _original_encode_basestring_ascii(s)
_original_encode_basestring_ascii = json.encoder.encode_basestring_ascii
json.encoder.encode_basestring_ascii = JSFunction.encode_basestring
因此,当您使用示例运行代码时:
>>> data = '{"name": "Julien", "callback": {"__js_func__": "function() { return 10; }"}}'
>>> json.loads(data, object_hook=JSFunction.decode_js_func)
{u'callback': <JSFunction: self>, u'name': u'Julien'}
并将其转换为一个很好的序列化json字符串,只需json.dumps()它:
>>> json.dumps(json.loads(data, object_hook=JSFunction.decode_js_func))
'{"callback": function() { return 10; }, "name": "Julien"}'
现在这变得相当混乱,因为我们已经修改了基本json模块的行为(所以它会影响应用程序范围内的所有内容),但它完成了工作。
我希望看到一种更清洁的方式!