如何更改可序列化python对象的json编码行为?

时间:2013-05-06 19:39:32

标签: python json

很容易更改非JSON序列化对象的格式,例如datetime.datetime。

出于调试目的,我的要求是改变一些自定义对象从dictlist等基类扩展的方式,以json格式进行序列化。代码:

import datetime
import json

def json_debug_handler(obj):
    print("object received:")
    print type(obj)
    print("\n\n")
    if  isinstance(obj, datetime.datetime):
        return obj.isoformat()
    elif isinstance(obj,mDict):
        return {'orig':obj , 'attrs': vars(obj)}
    elif isinstance(obj,mList):
        return {'orig':obj, 'attrs': vars(obj)}
    else:
        return None


class mDict(dict):
    pass


class mList(list):
    pass


def test_debug_json():
    games = mList(['mario','contra','tetris'])
    games.src = 'console'
    scores = mDict({'dp':10,'pk':45})
    scores.processed = "unprocessed"
    test_json = { 'games' : games , 'scores' : scores , 'date': datetime.datetime.now() }
    print(json.dumps(test_json,default=json_debug_handler))

if __name__ == '__main__':
    test_debug_json()

DEMO:http://ideone.com/hQJnLy

输出:

{"date": "2013-05-07T01:03:13.098727", "games": ["mario", "contra", "tetris"], "scores": {"pk": 45, "dp": 10}}

期望的输出:

{"date": "2013-05-07T01:03:13.098727", "games": { "orig": ["mario", "contra", "tetris"] ,"attrs" : { "src":"console"}} , "scores": { "orig": {"pk": 45, "dp": 10},"attrs": "processed":"unprocessed }}

default处理程序不适用于可序列化对象吗? 如果没有,如何在不向扩展类添加JSON方法的情况下覆盖它?

此外,还有这个版本的JSON编码器不起作用:

class JsonDebugEncoder(json.JSONEncoder):
    def default(self,obj):
        if  isinstance(obj, datetime.datetime):
            return obj.isoformat()
        elif isinstance(obj,mDict):
            return {'orig':obj , 'attrs': vars(obj)}
        elif isinstance(obj,mList):
            return {'orig':obj, 'attrs': vars(obj)}
        else:
            return json.JSONEncoder.default(self, obj)

如果有pickle,__getstate__,__setstate__,的hack,然后使用json.dump而不是pickle.loads对象,我也对此持开放态度,我试过了,但是没有用。

13 个答案:

答案 0 :(得分:22)

似乎要实现您想要的行为,在给定限制的情况下,您将需要深入研究JSONEncoder类。下面我写了一个自定义JSONEncoder,它会覆盖iterencode方法,将自定义isinstance方法传递给_make_iterencode。它不是世界上最干净的东西,但似乎是最好的选择,它可以将定制保持在最低限度。

# customencoder.py
from json.encoder import (_make_iterencode, JSONEncoder,
                          encode_basestring_ascii, FLOAT_REPR, INFINITY,
                          c_make_encoder, encode_basestring)


class CustomObjectEncoder(JSONEncoder):

    def iterencode(self, o, _one_shot=False):
        """
        Most of the original method has been left untouched.

        _one_shot is forced to False to prevent c_make_encoder from
        being used. c_make_encoder is a funcion defined in C, so it's easier
        to avoid using it than overriding/redefining it.

        The keyword argument isinstance for _make_iterencode has been set
        to self.isinstance. This allows for a custom isinstance function
        to be defined, which can be used to defer the serialization of custom
        objects to the default method.
        """
        # Force the use of _make_iterencode instead of c_make_encoder
        _one_shot = False

        if self.check_circular:
            markers = {}
        else:
            markers = None
        if self.ensure_ascii:
            _encoder = encode_basestring_ascii
        else:
            _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=FLOAT_REPR, _inf=INFINITY, _neginf=-INFINITY):
            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

        # Instead of forcing _one_shot to False, you can also just
        # remove the first part of this conditional statement and only
        # call _make_iterencode
        if (_one_shot and c_make_encoder is not None
                and self.indent is None and not self.sort_keys):
            _iterencode = c_make_encoder(
                markers, self.default, _encoder, self.indent,
                self.key_separator, self.item_separator, self.sort_keys,
                self.skipkeys, self.allow_nan)
        else:
            _iterencode = _make_iterencode(
                markers, self.default, _encoder, self.indent, floatstr,
                self.key_separator, self.item_separator, self.sort_keys,
                self.skipkeys, _one_shot, isinstance=self.isinstance)
        return _iterencode(o, 0)

您现在可以继承CustomObjectEncoder,以便正确序列化自定义对象。 CustomObjectEncoder也可以做很酷的事情,比如处理嵌套对象。

# test.py
import json
import datetime
from customencoder import CustomObjectEncoder


class MyEncoder(CustomObjectEncoder):

    def isinstance(self, obj, cls):
        if isinstance(obj, (mList, mDict)):
            return False
        return isinstance(obj, cls)

    def default(self, obj):
        """
        Defines custom serialization.

        To avoid circular references, any object that will always fail
        self.isinstance must be converted to something that is
        deserializable here.
        """
        if isinstance(obj, datetime.datetime):
            return obj.isoformat()
        elif isinstance(obj, mDict):
            return {"orig": dict(obj), "attrs": vars(obj)}
        elif isinstance(obj, mList):
            return {"orig": list(obj), "attrs": vars(obj)}
        else:
            return None


class mList(list):
    pass


class mDict(dict):
    pass


def main():
    zelda = mList(['zelda'])
    zelda.src = "oldschool"
    games = mList(['mario', 'contra', 'tetris', zelda])
    games.src = 'console'
    scores = mDict({'dp': 10, 'pk': 45})
    scores.processed = "unprocessed"
    test_json = {'games': games, 'scores': scores,
                 'date': datetime.datetime.now()}
    print(json.dumps(test_json, cls=MyEncoder))

if __name__ == '__main__':
    main()

答案 1 :(得分:10)

FastTurtle的答案可能是一个更清洁的解决方案。

根据我的问题/答案中解释的技术,这里有你想要的东西:Overriding nested JSON encoding of inherited default supported objects like dict, list

import json
import datetime


class mDict(dict):
    pass


class mList(list):
    pass


class JsonDebugEncoder(json.JSONEncoder):
    def _iterencode(self, o, markers=None):
        if isinstance(o, mDict):
            yield '{"__mDict__": '
            # Encode dictionary
            yield '{"orig": '
            for chunk in super(JsonDebugEncoder, self)._iterencode(o, markers):
                yield chunk
            yield ', '
            # / End of Encode dictionary
            # Encode attributes
            yield '"attr": '
            for key, value in o.__dict__.iteritems():
                yield '{"' + key + '": '
                for chunk in super(JsonDebugEncoder, self)._iterencode(value, markers):
                    yield chunk
                yield '}'
            yield '}'
            # / End of Encode attributes
            yield '}'
        elif isinstance(o, mList):
            yield '{"__mList__": '
            # Encode list
            yield '{"orig": '
            for chunk in super(JsonDebugEncoder, self)._iterencode(o, markers):
                yield chunk
            yield ', '
            # / End of Encode list
            # Encode attributes
            yield '"attr": '
            for key, value in o.__dict__.iteritems():
                yield '{"' + key + '": '
                for chunk in super(JsonDebugEncoder, self)._iterencode(value, markers):
                    yield chunk
                yield '}'
            yield '}'
            # / End of Encode attributes
            yield '}'
        else:
            for chunk in super(JsonDebugEncoder, self)._iterencode(o, markers=markers):
                yield chunk

    def default(self, obj):
        if isinstance(obj, datetime.datetime):
            return obj.isoformat()


class JsonDebugDecoder(json.JSONDecoder):
    def decode(self, s):
        obj = super(JsonDebugDecoder, self).decode(s)
        obj = self.recursiveObjectDecode(obj)
        return obj

    def recursiveObjectDecode(self, obj):
        if isinstance(obj, dict):
            decoders = [("__mList__", self.mListDecode),
                        ("__mDict__", self.mDictDecode)]
            for placeholder, decoder in decoders:
                if placeholder in obj:                  # We assume it's supposed to be converted
                    return decoder(obj[placeholder])
                else:
                    for k in obj:
                        obj[k] = self.recursiveObjectDecode(obj[k])
        elif isinstance(obj, list):
            for x in range(len(obj)):
                obj[x] = self.recursiveObjectDecode(obj[x])
        return obj

    def mDictDecode(self, o):
        res = mDict()
        for key, value in o['orig'].iteritems():
            res[key] = self.recursiveObjectDecode(value)
        for key, value in o['attr'].iteritems():
            res.__dict__[key] = self.recursiveObjectDecode(value)
        return res

    def mListDecode(self, o):
        res = mList()
        for value in o['orig']:
            res.append(self.recursiveObjectDecode(value))
        for key, value in o['attr'].iteritems():
            res.__dict__[key] = self.recursiveObjectDecode(value)
        return res


def test_debug_json():
    games = mList(['mario','contra','tetris'])
    games.src = 'console'
    scores = mDict({'dp':10,'pk':45})
    scores.processed = "unprocessed"
    test_json = { 'games' : games, 'scores' : scores ,'date': datetime.datetime.now() }
    jsonDump = json.dumps(test_json, cls=JsonDebugEncoder)
    print jsonDump
    test_pyObject = json.loads(jsonDump, cls=JsonDebugDecoder)
    print test_pyObject

if __name__ == '__main__':
    test_debug_json()

这导致:

{"date": "2013-05-06T22:28:08.967000", "games": {"__mList__": {"orig": ["mario", "contra", "tetris"], "attr": {"src": "console"}}}, "scores": {"__mDict__": {"orig": {"pk": 45, "dp": 10}, "attr": {"processed": "unprocessed"}}}}

通过这种方式,您可以对其进行编码并将其解码回来自它的python对象。

修改

这是一个实际将其编码为您想要的输出的版本,也可以对其进行解码。每当字典包含'orig'和'attr'时,它将检查'orig'是否包含字典或列表,如果是,它将分别将对象转换回mDict或mList。

import json
import datetime


class mDict(dict):
    pass


class mList(list):
    pass


class JsonDebugEncoder(json.JSONEncoder):
    def _iterencode(self, o, markers=None):
        if isinstance(o, mDict):    # Encode mDict
            yield '{"orig": '
            for chunk in super(JsonDebugEncoder, self)._iterencode(o, markers):
                yield chunk
            yield ', '
            yield '"attr": '
            for key, value in o.__dict__.iteritems():
                yield '{"' + key + '": '
                for chunk in super(JsonDebugEncoder, self)._iterencode(value, markers):
                    yield chunk
                yield '}'
            yield '}'
            # / End of Encode attributes
        elif isinstance(o, mList):    # Encode mList
            yield '{"orig": '
            for chunk in super(JsonDebugEncoder, self)._iterencode(o, markers):
                yield chunk
            yield ', '
            yield '"attr": '
            for key, value in o.__dict__.iteritems():
                yield '{"' + key + '": '
                for chunk in super(JsonDebugEncoder, self)._iterencode(value, markers):
                    yield chunk
                yield '}'
            yield '}'
        else:
            for chunk in super(JsonDebugEncoder, self)._iterencode(o, markers=markers):
                yield chunk

    def default(self, obj):
        if isinstance(obj, datetime.datetime):    # Encode datetime
            return obj.isoformat()


class JsonDebugDecoder(json.JSONDecoder):
    def decode(self, s):
        obj = super(JsonDebugDecoder, self).decode(s)
        obj = self.recursiveObjectDecode(obj)
        return obj

    def recursiveObjectDecode(self, obj):
        if isinstance(obj, dict):
            if "orig" in obj and "attr" in obj and isinstance(obj["orig"], list):
                return self.mListDecode(obj)
            elif "orig" in obj and "attr" in obj and isinstance(obj['orig'], dict):
                return self.mDictDecode(obj)
            else:
                for k in obj:
                    obj[k] = self.recursiveObjectDecode(obj[k])
        elif isinstance(obj, list):
            for x in range(len(obj)):
                obj[x] = self.recursiveObjectDecode(obj[x])
        return obj

    def mDictDecode(self, o):
        res = mDict()
        for key, value in o['orig'].iteritems():
            res[key] = self.recursiveObjectDecode(value)
        for key, value in o['attr'].iteritems():
            res.__dict__[key] = self.recursiveObjectDecode(value)
        return res

    def mListDecode(self, o):
        res = mList()
        for value in o['orig']:
            res.append(self.recursiveObjectDecode(value))
        for key, value in o['attr'].iteritems():
            res.__dict__[key] = self.recursiveObjectDecode(value)
        return res


def test_debug_json():
    games = mList(['mario','contra','tetris'])
    games.src = 'console'
    scores = mDict({'dp':10,'pk':45})
    scores.processed = "unprocessed"
    test_json = { 'games' : games, 'scores' : scores ,'date': datetime.datetime.now() }
    jsonDump = json.dumps(test_json, cls=JsonDebugEncoder)
    print jsonDump
    test_pyObject = json.loads(jsonDump, cls=JsonDebugDecoder)
    print test_pyObject
    print test_pyObject['games'].src

if __name__ == '__main__':
    test_debug_json()

以下是有关输出的更多信息:

# Encoded
{"date": "2013-05-06T22:41:35.498000", "games": {"orig": ["mario", "contra", "tetris"], "attr": {"src": "console"}}, "scores": {"orig": {"pk": 45, "dp": 10}, "attr": {"processed": "unprocessed"}}}

# Decoded ('games' contains the mList with the src attribute and 'scores' contains the mDict processed attribute)
# Note that printing the python objects doesn't directly show the processed and src attributes, as seen below.
{u'date': u'2013-05-06T22:41:35.498000', u'games': [u'mario', u'contra', u'tetris'], u'scores': {u'pk': 45, u'dp': 10}}

对于任何错误的命名约定,很抱歉,这是一个快速设置。 ;)

注意:日期时间不会被解码回python表示。实现可以通过检查任何名为'date'的dict键并包含日期时间的有效字符串表示来完成。

答案 2 :(得分:6)

正如其他人已经指出的那样,默认处理程序仅针对不是已识别类型之一的值进行调用。我对此问题的建议解决方案是预处理要序列化的对象,递归列表,元组和字典,但将所有其他值包装在自定义类中。

这样的事情:

def debug(obj):
    class Debug:
        def __init__(self,obj):
            self.originalObject = obj
    if obj.__class__ == list:
        return [debug(item) for item in obj]
    elif obj.__class__ == tuple:
        return (debug(item) for item in obj)
    elif obj.__class__ == dict:
        return dict((key,debug(obj[key])) for key in obj)
    else:
        return Debug(obj)

在将对象传递给 json.dumps 之前,您可以调用此函数,如下所示:

test_json = debug(test_json)
print(json.dumps(test_json,default=json_debug_handler))

请注意,此代码检查其类与列表,元组或字典完全匹配的对象,因此从这些类型扩展的任何自定义对象都将被包装而不是解析。因此,常规列表,元组和字典将照常序列化,但所有其他值将传递给默认处理程序。

所有这一切的最终结果是,每个到达默认处理程序的值都保证包含在其中一个Debug类中。所以你要做的第一件事就是提取原始对象,如下所示:

obj = obj.originalObject

然后,您可以检查原始对象的类型并处理需要特殊处理的类型。对于其他所有内容,您应该只返回原始对象(因此处理程序的最后一次返回应该是return obj而不是return None)。

def json_debug_handler(obj):
    obj = obj.originalObject      # Add this line
    print("object received:")
    print type(obj)
    print("\n\n")
    if  isinstance(obj, datetime.datetime):
        return obj.isoformat()
    elif isinstance(obj,mDict):
        return {'orig':obj, 'attrs': vars(obj)}
    elif isinstance(obj,mList):
        return {'orig':obj, 'attrs': vars(obj)}
    else:
        return obj                # Change this line

请注意,此代码不会检查不可序列化的值。这些将落在最后的return obj,然后将被序列化程序拒绝并再次传递回默认处理程序 - 这次没有Debug包装器。

如果您需要处理这种情况,可以在处理程序的顶部添加一个检查,如下所示:

if not hasattr(obj, 'originalObject'):
    return None

Ideone演示:http://ideone.com/tOloNq

答案 3 :(得分:5)

只有当被转储的节点不是本机可序列化的,并且您的mDict类按原样序列化时,才会调用默认函数。这是一个小的演示,显示调用默认值时,不显示:

import json

def serializer(obj):
    print 'serializer called'
    return str(obj)

class mDict(dict):
    pass

class mSet(set):
    pass

d = mDict(dict(a=1))
print json.dumps(d, default=serializer)

s = mSet({1, 2, 3,})
print json.dumps(s, default=serializer)

输出:

{"a": 1}
serializer called
"mSet([1, 2, 3])"

请注意,集合不是本机可序列化的,但是dicts是。

由于您的m___类是可序列化的,因此永远不会调用您的处理程序。

更新#1 -----

您可以更改JSON编码器代码。有关如何执行此操作的详细信息取决于您正在使用的JSON实现。例如,在simplejson中,相关代码就是这个,在encode.py中:

def _iterencode(o, _current_indent_level):
    ...
        for_json = _for_json and getattr(o, 'for_json', None)
        if for_json and callable(for_json):
            ...
        elif isinstance(o, list):
            ...
        else:
            _asdict = _namedtuple_as_object and getattr(o, '_asdict', None)
            if _asdict and callable(_asdict):
                for chunk in _iterencode_dict(_asdict(),
                        _current_indent_level):
                    yield chunk
            elif (_tuple_as_array and isinstance(o, tuple)):
                ...
            elif isinstance(o, dict):
                ...
            elif _use_decimal and isinstance(o, Decimal):
                ...
            else:
                ...
                o = _default(o)
                for chunk in _iterencode(o, _current_indent_level):
                    yield chunk
                ...

换句话说,只有当被编码的节点不是已识别的基类型之一时,才会调用默认的硬连线行为。您可以通过以下几种方式之一覆盖它:

1 - 如上所述继承JSONEncoder子类,但是在其初始值设定项中添加一个参数,指定要用于代替标准_make_iterencode的函数,在该函数中添加一个测试,该测试将为满足的类调用默认值你的标准。这是一个干净的方法,因为您没有更改JSON模块,但您将重复原始_make_iterencode中的大量代码。 (此方法的其他变体包括monkeypatching _make_iterencode或其子函数_iterencode_dict)。

2 - 更改JSON模块源,并使用__debug__常量来更改行为:

def _iterencode(o, _current_indent_level):
    ...
        for_json = _for_json and getattr(o, 'for_json', None)
        if for_json and callable(for_json):
            ...
        elif isinstance(o, list):
            ...
        ## added code below
        elif __debug__:
            o = _default(o)
            for chunk in _iterencode(o, _current_indent_level):
                yield chunk
        ## added code above
        else:
            ...

理想情况下,JSONEncoder类将提供一个参数来指定“使用所有类型的默认值”,但事实并非如此。以上是一个简单的一次性更改,可以满足您的需求。

答案 4 :(得分:3)

尝试以下方法。它产生你想要的输出,看起来相对简单。与编码器类唯一的真正区别在于我们应该覆盖解码和编码方法(因为后者仍然需要编码器知道如何处理的类型)。

import json
import datetime

class JSONDebugEncoder(json.JSONEncoder):
    # transform objects known to JSONEncoder here
    def encode(self, o, *args, **kw):
        for_json = o
        if isinstance(o, mDict):
            for_json = { 'orig' : o, 'attrs' : vars(o) }
        elif isinstance(o, mList):
            for_json = { 'orig' : o, 'attrs' : vars(o) }
        return super(JSONDebugEncoder, self).encode(for_json, *args, **kw)

    # handle objects not known to JSONEncoder here
    def default(self, o, *args, **kw):
        if isinstance(o, datetime.datetime):
            return o.isoformat()
        else:
            return super(JSONDebugEncoder, self).default(o, *args, **kw)


class mDict(dict):
    pass

class mList(list):
    pass

def test_debug_json():
    games = mList(['mario','contra','tetris'])
    games.src = 'console'
    scores = mDict({'dp':10,'pk':45})
    scores.processed = "unprocessed"
    test_json = { 'games' : games , 'scores' : scores , 'date': datetime.datetime.now() }
    print(json.dumps(test_json,cls=JSONDebugEncoder))

if __name__ == '__main__':
    test_debug_json()

答案 5 :(得分:2)

为什么不能创建一个新的对象类型来传递给编码器?尝试:

class MStuff(object):
    def __init__(self, content):
        self.content = content

class mDict(MStuff):
    pass

class mList(MStuff):
    pass

def json_debug_handler(obj):
    print("object received:")
    print(type(obj))
    print("\n\n")
    if  isinstance(obj, datetime.datetime):
        return obj.isoformat()
    elif isinstance(obj,MStuff):
        attrs = {}
        for key in obj.__dict__:
            if not ( key.startswith("_") or key == "content"):
                attrs[key] = obj.__dict__[key]

        return {'orig':obj.content , 'attrs': attrs}
    else:
        return None

如果需要,您可以在mDict和mList上添加验证。

答案 6 :(得分:2)

如果您定义这些to override __instancecheck__

def strict_check(builtin):
    '''creates a new class from the builtin whose instance check
    method can be overridden to renounce particular types'''
    class BuiltIn(type):
        def __instancecheck__(self, other):
            print 'instance', self, type(other), other
            if type(other) in strict_check.blacklist:
                return False
            return builtin.__instancecheck__(other)
    # construct a class, whose instance check method is known.
    return BuiltIn('strict_%s' % builtin.__name__, (builtin,), dict())

# for safety, define it here.
strict_check.blacklist = ()

然后修补json.encoder,如to override _make_iterencode.func_defaults

# modify json encoder to use some new list/dict attr.
import json.encoder
# save old stuff, never know when you need it.
old_defaults = json.encoder._make_iterencode.func_defaults
old_encoder = json.encoder.c_make_encoder
encoder_defaults = list(json.encoder._make_iterencode.func_defaults)
for index, default in enumerate(encoder_defaults):
    if default in (list, dict):
        encoder_defaults[index] = strict_check(default)

# change the defaults for _make_iterencode.
json.encoder._make_iterencode.func_defaults = tuple(encoder_defaults)
# disable C extension.
json.encoder.c_make_encoder = None

...你的例子几乎可以逐字逐句地工作:

import datetime
import json

def json_debug_handler(obj):
    print("object received:")
    print type(obj)
    print("\n\n")
    if  isinstance(obj, datetime.datetime):
        return obj.isoformat()
    elif isinstance(obj,mDict):
        # degrade obj to more primitive dict()
        # to avoid cycles in the encoding.
        return {'orig': dict(obj) , 'attrs': vars(obj)}
    elif isinstance(obj,mList):
        # degrade obj to more primitive list()
        # to avoid cycles in the encoding.
        return {'orig': list(obj), 'attrs': vars(obj)}
    else:
        return None


class mDict(dict):
    pass


class mList(list):
    pass

# set the stuff we want to process differently.
strict_check.blacklist = (mDict, mList)

def test_debug_json():
    global test_json
    games = mList(['mario','contra','tetris'])
    games.src = 'console'
    scores = mDict({'dp':10,'pk':45})
    scores.processed = "unprocessed"
    test_json = { 'games' : games , 'scores' : scores , 'date': datetime.datetime.now() }
    print(json.dumps(test_json,default=json_debug_handler))

if __name__ == '__main__':
    test_debug_json()

我需要改变的是确保没有周期:

    elif isinstance(obj,mDict):
        # degrade obj to more primitive dict()
        # to avoid cycles in the encoding.
        return {'orig': dict(obj) , 'attrs': vars(obj)}
    elif isinstance(obj,mList):
        # degrade obj to more primitive list()
        # to avoid cycles in the encoding.
        return {'orig': list(obj), 'attrs': vars(obj)}

并在test_debug_json之前的某处添加:

# set the stuff we want to process differently.
strict_check.blacklist = (mDict, mList)

这是我的控制台输出:

>>> test_debug_json()
instance <class '__main__.strict_list'> <type 'dict'> {'date': datetime.datetime(2013, 7, 17, 12, 4, 40, 950637), 'games': ['mario', 'contra', 'tetris'], 'scores': {'pk': 45, 'dp': 10}}
instance <class '__main__.strict_dict'> <type 'dict'> {'date': datetime.datetime(2013, 7, 17, 12, 4, 40, 950637), 'games': ['mario', 'contra', 'tetris'], 'scores': {'pk': 45, 'dp': 10}}
instance <class '__main__.strict_list'> <type 'datetime.datetime'> 2013-07-17 12:04:40.950637
instance <class '__main__.strict_dict'> <type 'datetime.datetime'> 2013-07-17 12:04:40.950637
instance <class '__main__.strict_list'> <type 'datetime.datetime'> 2013-07-17 12:04:40.950637
instance <class '__main__.strict_dict'> <type 'datetime.datetime'> 2013-07-17 12:04:40.950637
object received:
<type 'datetime.datetime'>



instance <class '__main__.strict_list'> <class '__main__.mList'> ['mario', 'contra', 'tetris']
instance <class '__main__.strict_dict'> <class '__main__.mList'> ['mario', 'contra', 'tetris']
instance <class '__main__.strict_list'> <class '__main__.mList'> ['mario', 'contra', 'tetris']
instance <class '__main__.strict_dict'> <class '__main__.mList'> ['mario', 'contra', 'tetris']
object received:
<class '__main__.mList'>



instance <class '__main__.strict_list'> <type 'dict'> {'attrs': {'src': 'console'}, 'orig': ['mario', 'contra', 'tetris']}
instance <class '__main__.strict_dict'> <type 'dict'> {'attrs': {'src': 'console'}, 'orig': ['mario', 'contra', 'tetris']}
instance <class '__main__.strict_list'> <type 'dict'> {'src': 'console'}
instance <class '__main__.strict_dict'> <type 'dict'> {'src': 'console'}
instance <class '__main__.strict_list'> <type 'list'> ['mario', 'contra', 'tetris']
instance <class '__main__.strict_list'> <class '__main__.mDict'> {'pk': 45, 'dp': 10}
instance <class '__main__.strict_dict'> <class '__main__.mDict'> {'pk': 45, 'dp': 10}
instance <class '__main__.strict_list'> <class '__main__.mDict'> {'pk': 45, 'dp': 10}
instance <class '__main__.strict_dict'> <class '__main__.mDict'> {'pk': 45, 'dp': 10}
object received:
<class '__main__.mDict'>



instance <class '__main__.strict_list'> <type 'dict'> {'attrs': {'processed': 'unprocessed'}, 'orig': {'pk': 45, 'dp': 10}}
instance <class '__main__.strict_dict'> <type 'dict'> {'attrs': {'processed': 'unprocessed'}, 'orig': {'pk': 45, 'dp': 10}}
instance <class '__main__.strict_list'> <type 'dict'> {'processed': 'unprocessed'}
instance <class '__main__.strict_dict'> <type 'dict'> {'processed': 'unprocessed'}
instance <class '__main__.strict_list'> <type 'dict'> {'pk': 45, 'dp': 10}
instance <class '__main__.strict_dict'> <type 'dict'> {'pk': 45, 'dp': 10}
{"date": "2013-07-17T12:04:40.950637", "games": {"attrs": {"src": "console"}, "orig": ["mario", "contra", "tetris"]}, "scores": {"attrs": {"processed": "unprocessed"}, "orig": {"pk": 45, "dp": 10}}}

答案 7 :(得分:1)

如果您能够更改json.dumps的调用方式。您可以在JSON编码器开始之前完成所需的所有处理。此版本不使用任何类型的复制,并将就地编辑结构。如果需要,您可以添加copy()

import datetime
import json
import collections


def json_debug_handler(obj):
    print("object received:")
    print type(obj)
    print("\n\n")
    if isinstance(obj, collections.Mapping):
        for key, value in obj.iteritems():
            if isinstance(value, (collections.Mapping, collections.MutableSequence)):
                value = json_debug_handler(value)

            obj[key] = convert(value)
    elif isinstance(obj, collections.MutableSequence):
        for index, value in enumerate(obj):
            if isinstance(value, (collections.Mapping, collections.MutableSequence)):
                value = json_debug_handler(value)

            obj[index] = convert(value)
    return obj

def convert(obj):
    if  isinstance(obj, datetime.datetime):
        return obj.isoformat()
    elif isinstance(obj,mDict):
        return {'orig':obj , 'attrs': vars(obj)}
    elif isinstance(obj,mList):
        return {'orig':obj, 'attrs': vars(obj)}
    else:
        return obj


class mDict(dict):
    pass


class mList(list):
    pass


def test_debug_json():
    games = mList(['mario','contra','tetris'])
    games.src = 'console'
    scores = mDict({'dp':10,'pk':45})
    scores.processed = "qunprocessed"
    test_json = { 'games' : games , 'scores' : scores , 'date': datetime.datetime.now() }
    print(json.dumps(json_debug_handler(test_json)))

if __name__ == '__main__':
    test_debug_json()

在将要序列化的对象上调用json_debug_handler,然后将其传递给json.dumps。使用此模式,您还可以轻松撤消更改和/或添加额外的转换规则。

修改

如果您无法更改json.dumps的调用方式,您可以随时将其命名为执行您想要的操作。比如这样做:

json.dumps = lambda obj, *args, **kwargs: json.dumps(json_debug_handler(obj), *args, **kwargs)

答案 8 :(得分:1)

我们可以预处理test_json,使其适合您的要求吗?操作python dict比编写无用的Encode更容易。

import datetime
import json
class mDict(dict):
    pass

class mList(list):
    pass

def prepare(obj):
    if  isinstance(obj, datetime.datetime):
        return obj.isoformat()
    elif isinstance(obj, mDict):
        return {'orig':obj , 'attrs': vars(obj)}
    elif isinstance(obj, mList):
        return {'orig':obj, 'attrs': vars(obj)}
    else:
        return obj
def preprocessor(toJson):
    ret ={}
    for key, value in toJson.items():
        ret[key] = prepare(value)
    return ret
if __name__ == '__main__':
    def test_debug_json():
        games = mList(['mario','contra','tetris'])
        games.src = 'console'
        scores = mDict({'dp':10,'pk':45})
        scores.processed = "unprocessed"
        test_json = { 'games' : games, 'scores' : scores , 'date': datetime.datetime.now() }
        print(json.dumps(preprocessor(test_json)))
    test_debug_json()

答案 9 :(得分:0)

您应该可以覆盖JSONEncoder.encode()

class MyEncoder(JSONEncoder):
  def encode(self, o):
    if isinstance(o, dict):
      # directly call JSONEncoder rather than infinite-looping through self.encode()
      return JSONEncoder.encode(self, {'orig': o, 'attrs': vars(o)})
    elif isinstance(o, list):
      return JSONEncoder.encode(self, {'orig': o, 'attrs': vars(o)})
    else:
      return JSONEncoder.encode(self, o)

然后如果您要将其修补到json.dumps,则会从http://docs.buildbot.net/latest/reference/json-pysrc.html查看,因为您需要将json._default_encoder替换为MyEncoder的实例。

答案 10 :(得分:0)

如果您只是在寻找序列化而不是反序列化,那么您可以在将对象发送到json.dumps之前对其进行处理。见下面的例子

import datetime
import json


def is_inherited_from(obj, objtype):
    return isinstance(obj, objtype) and not type(obj).__mro__[0] == objtype


def process_object(data):
    if isinstance(data, list):
        if is_inherited_from(data, list):
            return process_object({"orig": list(data), "attrs": vars(data)})
        new_data = []
        for d in data:
            new_data.append(process_object(d))
    elif isinstance(data, tuple):
        if is_inherited_from(data, tuple):
            return process_object({"orig": tuple(data), "attrs": vars(data)})
        new_data = []
        for d in data:
            new_data.append(process_object(d))
        return tuple(new_data)
    elif isinstance(data, dict):
        if is_inherited_from(data, dict):
            return process_object({"orig": list(data), "attrs": vars(data)})
        new_data = {}
        for k, v in data.items():
            new_data[k] = process_object(v)
    else:
        return data
    return new_data


def json_debug_handler(obj):
    print("object received:")
    print("\n\n")
    if isinstance(obj, datetime.datetime):
        return obj.isoformat()


class mDict(dict):
    pass


class mList(list):
    pass


def test_debug_json():
    games = mList(['mario', 'contra', 'tetris'])
    games.src = 'console'
    scores = mDict({'dp': 10, 'pk': 45})
    scores.processed = "unprocessed"
    test_json = {'games': games, 'scores': scores, 'date': datetime.datetime.now()}
    new_object = process_object(test_json)
    print(json.dumps(new_object, default=json_debug_handler))


if __name__ == '__main__':
    test_debug_json()

同样的输出是

  

{“games”:{“orig”:[“mario”,“contra”,“tetris”],“attrs”:{“src”:“console”}},“得分”:{“orig” :[“dp”,“pk”],“attrs”:{“processed”:“unprocessed”}},“date”:“2018-01-24T12:59:36.581689”}

也可以覆盖JSONEncoder,但由于它使用嵌套方法,因此它很复杂,需要下面讨论的技术

Can you patch *just* a nested function with closure, or must the whole outer function be repeated?

由于你想保持简单,我不建议走那条路

答案 11 :(得分:0)

根据FastTurtle的建议,但需要更少的代码和更深入的编辑,您可以全局覆盖isinstance本身。这可能不是一个好主意,可能会破坏一些东西。但它确实有效,因为它产生了你所需的输出,而且非常简单。

首先,在将json导入到任​​何地方之前的 ,对内置模块进行猴子补丁,将isinstance替换为一个只有一点点的_original_isinstance = isinstance def _isinstance(obj, class_or_tuple): if '_make_iterencode' in globals(): if not _original_isinstance(class_or_tuple, tuple): class_or_tuple = (class_or_tuple,) for custom in mList, mDict: if _original_isinstance(obj, custom): return custom in class_or_tuple return _original_isinstance(obj, class_or_tuple) try: import builtins # Python 3 except ImportError: import __builtin__ as builtins # Python 2 builtins.isinstance = _isinstance ,并且仅在特定的上下文中使用 p>

_make_iterencode

然后,创建自定义编码器,实现自定义序列化并强制使用class CustomEncoder(json.JSONEncoder): def iterencode(self, o, _one_shot = False): return super(CustomEncoder, self).iterencode(o, _one_shot=False) def default(self, obj): if isinstance(obj, datetime.datetime): return obj.isoformat() elif isinstance(obj,mDict): return {'orig':dict(obj) , 'attrs': vars(obj)} elif isinstance(obj,mList): return {'orig':list(obj), 'attrs': vars(obj)} else: return None (因为c版本不会受到monkeypatching的影响):

Python 3.6.3 (default, Oct 10 2017, 21:06:48)
...
>>> from test import test_debug_json
>>> test_debug_json()
{"games": {"orig": ["mario", "contra", "tetris"], "attrs": {"src": "console"}}, "scores": {"orig": {"dp": 10, "pk": 45}, "attrs": {"processed": "unprocessed"}}, "date": "2018-01-27T13:56:15.666655"}

Python 2.7.13 (default, May  9 2017, 12:06:13)
...
>>> from test import test_debug_json
>>> test_debug_json()
{"date": "2018-01-27T13:57:04.681664", "games": {"attrs": {"src": "console"}, "orig": ["mario", "contra", "tetris"]}, "scores": {"attrs": {"processed": "unprocessed"}, "orig": {"pk": 45, "dp": 10}}}

这真的就是它的全部!下面的Python 3和Python 2的输出。

.logo {
  max-height: 80px;
  border-radius: 80px;
}

.link-group-wrapper {
  display: inline;
  margin: 0;
  padding: 0;

}

.link-wrapper {
  list-style: none;
  text-align: center;
  font-size: 1.2em;
  background-color: #0b215c;
  width: 120px;
  display: inline-block;
  height: 90px;
  line-height: 90px;
}

.link {
  color: white;
  display: block;
  text-decoration: none;


}

答案 12 :(得分:0)

我尝试更改默认的解析程序优先级并更改默认的迭代器输出以实现您的目的。

  1. 更改默认的解析程序优先级,在所有标准类型验证之前执行:

      

    继承json.JSONEncoder并覆盖iterencode()方法。

         

    所有值都应该用 ValueWrapper 类型包装,避免默认标准解析器解析这些值。

  2. 更改默认迭代器输出;

      

    实施三个自定义包装类 ValueWrapper ListWrapper DictWrapper 。 ListWrapper实现__iter__()和DictWrapper实现__iter__()items()iteritems()

  3. import datetime
    import json
    
    class DebugJsonEncoder(json.JSONEncoder):
        def iterencode(self, o, _one_shot=False):
            default_resolver = self.default
            # Rewrites the default resolve, self.default(), with the custom resolver.
            # It will process the Wrapper classes
            def _resolve(o):
                if isinstance(o, ValueWrapper):
                    # Calls custom resolver precede others. Due to the _make_iterencode()
                    # call the custom resolver following by all standard type verifying 
                    # failed. But we want custom resolver can be executed by all standard 
                    # verifying.
                    # see https://github.com/python/cpython/blob/2.7/Lib/json/encoder.py#L442
                    result = default_resolver(o.data)
                    if (o.data is not None) and (result is not None):
                        return result
                    elif isinstance(o.data, (list, tuple)):
                        return ListWrapper(o.data)
                    elif isinstance(o.data, dict):
                        return DictWrapper(o.data)
                    else:
                        return o.data
                else:
                    return default_resolver(o)
    
            # re-assign the default resolver self.default with custom resolver.
            # see https://github.com/python/cpython/blob/2.7/Lib/json/encoder.py#L161
            self.default = _resolve
            # The input value must be wrapped by ValueWrapper, avoid the values are 
            # resolved by the standard resolvers.
            # The last one arguemnt _one_shot must be False, we want to encode with
            # _make_iterencode().
            # see https://github.com/python/cpython/blob/2.7/Lib/json/encoder.py#L259
            return json.JSONEncoder.iterencode(self, _resolve(ValueWrapper(o)), False)
    
    
    class ValueWrapper():
        """
        a wrapper wrapped the given object
        """
    
        def __init__(self, o):
            self.data = o
    
    class ListWrapper(ValueWrapper, list):
        """
        a wrapper wrapped the given list
        """
    
        def __init__(self, o):
            ValueWrapper.__init__(self, o)
    
        # see https://github.com/python/cpython/blob/2.7/Lib/json/encoder.py#L307
        def __iter__(self):
            for chunk in self.data:
                yield ValueWrapper(chunk)
    
    class DictWrapper(ValueWrapper, dict):
        """
        a wrapper wrapped the given dict
        """
    
        def __init__(self, d):
            dict.__init__(self, d)
    
        def __iter__(self):
            for key, value in dict.items(self):
                yield key, ValueWrapper(value)
    
        # see https://github.com/python/cpython/blob/2.7/Lib/json/encoder.py#L361
        def items(self):
            for key, value in dict.items(self):
                yield key, ValueWrapper(value)
    
        # see https://github.com/python/cpython/blob/2.7/Lib/json/encoder.py#L363
        def iteritems(self):
            for key, value in dict.iteritems(self):
                yield key, ValueWrapper(value)
    
    
    def json_debug_handler(obj):
        print("object received:")
        print type(obj)
        print("\n\n")
        if  isinstance(obj, datetime.datetime):
            return obj.isoformat()
        elif isinstance(obj,mDict):
            return {'orig':obj , 'attrs': vars(obj)}
        elif isinstance(obj,mList):
            return {'orig':obj, 'attrs': vars(obj)}
        else:
            return None
    
    
    class mDict(dict):
        pass
    
    class mList(list):
        pass
    
    
    def test_debug_json():
        games = mList(['mario','contra','tetris'])
        games.src = 'console'
        scores = mDict({'dp':10,'pk':45})
        scores.processed = "unprocessed"
        test_json = { 'games' : games , 'scores' : scores , 'date': datetime.datetime.now(), 'default': None}
        print(json.dumps(test_json,cls=DebugJsonEncoder,default=json_debug_handler))
    
    if __name__ == '__main__':
        test_debug_json()