Hoe verander je het json-coderingsgedrag voor serialiseerbaar python-object?

Het is gemakkelijk om de indeling te wijzigen van een object dat niet JSON-serialiseerbaar is, bijvoorbeeld datetime.datetime.

Mijn vereiste, voor foutopsporingsdoeleinden, is om de manier te wijzigen waarop sommige aangepaste objecten die zijn uitgebreid van basisobjecten zoals dicten list, worden geserialiseerd in json-indeling . Code :

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

Uitvoer:

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

Gewenste uitvoer:

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

Werkt de defaulthandler niet voor serialiseerbare objecten?
Zo niet, hoe kan ik dit overschrijven, zonder toJSON-methoden toe te voegen aan de uitgebreide klassen?

Er is ook een versie van de JSON-encoder die niet werkt:

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)

Als er een hack is met pickle,__getstate__,__setstate__,en dan json.dumps over pickle.loads object gebruikt, sta ik daar ook voor open, ik heb het geprobeerd, maar dat werkte niet .


Antwoord 1, autoriteit 100%

Het lijkt erop dat je, om het gewenste gedrag te bereiken, met de gegeven beperkingen, je een beetje in de klasse JSONEncodermoet verdiepen. Hieronder heb ik een aangepaste JSONEncodergeschreven die de iterencode-methode overschrijft om een ​​aangepaste isinstance-methode door te geven aan _make_iterencode. Het is niet het schoonste ding ter wereld, maar lijkt de beste te zijn gezien de opties en het beperkt maatwerk tot een minimum.

# 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)

U kunt de CustomObjectEncodernu subclasseren, zodat uw aangepaste objecten correct worden geserialiseerd. De CustomObjectEncoderkan ook coole dingen doen, zoals geneste objecten afhandelen.

# 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()

Antwoord 2, autoriteit 44%

Het antwoord van FastTurtle zou een veel schonere oplossing kunnen zijn.

Hier is iets dat in de buurt komt van wat je wilt op basis van de techniek zoals uitgelegd in mijn vraag/antwoord: Overschrijven van geneste JSON-codering van overgenomen standaard ondersteunde objecten zoals dict, lijst

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()

Dit resulteert in:

{"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"}}}}

Op deze manier kun je het coderen en terug decoderen naar het python-object waar het vandaan kwam.

BEWERKEN:

Hier is een versie die het daadwerkelijk codeert naar de gewenste uitvoer en het ook kan decoderen. Wanneer een woordenboek ‘orig’ en ‘attr’ bevat, zal het controleren of ‘orig’ een woordenboek of een lijst bevat, zo ja, dan converteert het het object respectievelijk terug naar de mDict of 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()

Hier is wat meer informatie over de uitvoer:

# 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}}

Sorry voor eventuele slechte naamgevingsconventies, het is een snelle installatie. 😉

Opmerking: de datetime wordt niet gedecodeerd naar de python-representatie. Dat zou kunnen worden geïmplementeerd door te controleren op een dict-sleutel die ‘date’ wordt genoemd en een geldige tekenreeksrepresentatie van een datetime bevat.


Antwoord 3, autoriteit 28%

Zoals anderen al hebben opgemerkt, wordt de standaardhandler alleen aangeroepen voor waarden die niet tot de herkende typen behoren. Mijn voorgestelde oplossing voor dit probleem is om het object dat je wilt serialiseren voor te verwerken, waarbij je lijsten, tupels en woordenboeken herhaalt, maar elke andere waarde in een aangepaste klasse verpakt.

Zoiets:

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)

U zou deze functie als volgt aanroepen, voordat u uw object doorgeeft aan json.dumps:

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

Houd er rekening mee dat deze code controleert op objecten waarvan de klasse exact overeenkomt met een lijst, tuple of woordenboek, dus alle aangepaste objecten die van die typen worden uitgebreid, worden verpakt in plaats van geparseerd. Als gevolg hiervan worden de reguliere lijsten, tuples en woordenboeken zoals gewoonlijk geserialiseerd, maar alle andere waarden worden doorgegeven aan de standaardhandler.

Het eindresultaat van dit alles is dat elke waarde die de standaardhandler bereikt, gegarandeerd wordt verpakt in een van deze Debug-klassen. Dus het eerste dat u wilt doen, is het originele object extraheren, zoals dit:

obj = obj.originalObject

Je kunt dan het type van het originele object controleren en de typen afhandelen die speciale verwerking nodig hebben. Voor al het andere moet u gewoon het originele object retourneren (dus de laatste return van de handler moet return objzijn en niet 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

Houd er rekening mee dat deze code niet controleert op waarden die niet serialiseerbaar zijn. Deze vallen door de laatste return obj, worden vervolgens afgewezen door de serializer en weer teruggestuurd naar de standaardhandler – alleen deze keer zonder de Debug-wrapper.

Als u met dat scenario moet omgaan, kunt u een vinkje bovenaan de handler als volgt toevoegen:

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

Ideone-demo: http://ideone.com/tOloNq


Antwoord 4, autoriteit 20%

De standaardfunctie wordt alleen aangeroepen als de node die wordt gedumpt niet native serialiseerbaar is en je mDict-klassen serialiseren zoals ze zijn. Hier is een kleine demo die laat zien wanneer standaard wordt aangeroepen en wanneer niet:

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)

En de uitvoer:

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

Houd er rekening mee dat sets niet standaard serialiseerbaar zijn, maar dictaten wel.

Omdat je m___ klassen serialiseerbaar zijn, wordt je handler nooit aangeroepen.

Update #1 —–

U kunt de JSON-encodercode wijzigen. De details over hoe u dit kunt doen, zijn afhankelijk van de JSON-implementatie die u gebruikt. In simplejson is de relevante code bijvoorbeeld deze, in 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
                ...

Met andere woorden, er is een vast bedrade gedrag dat alleen standaard aanroept als het knooppunt dat wordt gecodeerd niet een van de herkende basistypen is. U kunt dit op verschillende manieren overschrijven:

1 — subklasse JSONEncoder zoals je hierboven hebt gedaan, maar voeg een parameter toe aan de initialisator die de functie specificeert die moet worden gebruikt in plaats van de standaard _make_iterencode, waarin je een test toevoegt die standaard zou aanroepen voor klassen die voldoen aan uw criteria. Dit is een schone benadering omdat je de JSON-module niet wijzigt, maar je zou veel code uit de originele _make_iterencode herhalen. (Andere variaties op deze aanpak zijn onder andere monkeypatching _make_iterencode of de subfunctie _iterencode_dict).

2 — wijzig de bron van de JSON-module en gebruik de constante __debug__om het gedrag te veranderen:

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:
            ...

Idealiter zou de JSONEncoder-klasse een parameter bieden om “gebruik standaard voor alle typen” op te geven, maar dat is niet het geval. Het bovenstaande is een eenvoudige eenmalige wijziging die doet wat u zoekt.


Antwoord 5, autoriteit 16%

Probeer het onderstaande. Het produceert de output die u wilt en ziet er relatief eenvoudig uit. Het enige echte verschil met uw encoderklasse is dat we zowel de decoderings- als de coderingsmethoden moeten overschrijven (aangezien de laatste nog steeds wordt genoemd voor typen die de encoder weet te hanteren).

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()

6, Autoriteit 8%

Waarom kan u niet gewoon een nieuw objecttype maken om door te gaan naar de encoder? Probeer:

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

U kunt indien gewenst validatie op de MDICT- en MLIST toevoegen.


Antwoord 7, autoriteit 4%

Als je de manier kunt wijzigen waarop json.dumpswordt aangeroepen. U kunt alle benodigde bewerkingen uitvoeren voordat de JSON-encoder deze in handen krijgt. Deze versie maakt geen gebruik van enige vorm van kopiëren en zal de structuren ter plaatse bewerken. U kunt indien nodig copy()toevoegen.

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()

Je roept json_debug_handleraan op het object dat je serialiseert voordat je het doorgeeft aan de json.dumps. Met dit patroon kun je de wijzigingen ook gemakkelijk terugdraaien en/of extra conversieregels toevoegen.

bewerken:

Als je de aanroep van json.dumpsniet kunt veranderen, kun je het altijd met een monkeypatch doen wat je wilt. Zoals dit doen:

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

Antwoord 8, autoriteit 4%

Kunnen we de test_jsongewoon voorverwerken om het geschikt te maken voor uw vereiste? Het is gemakkelijker om een ​​python-dictaat te manipuleren dan een nutteloze code te schrijven.

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()

Antwoord 9

Je zou 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)

en als je het vervolgens in json.dumpswilt patchen, lijkt het op http://docs.buildbot.net/latest/reference/json-pysrc.htmlalsof je json._default_encodermoet vervangen door een instantie van MyEncoder.


Antwoord 10

Als je alleen op zoek bent naar serialisatie en niet naar deserialisatie, dan kun je het object verwerken voordat je het naar json.dumpsstuurt. Zie onderstaand voorbeeld

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()

De uitvoer van hetzelfde is

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

Het is ook mogelijk om de JSONEncoder te overschrijven, maar aangezien het geneste methoden gebruikt, zou het complex zijn en technieken vereisen die hieronder worden besproken

Kun je *alleen* een geneste functie met sluiting patchen, of moet de hele uiterlijke functie worden herhaald?

Omdat je het simpel wilt houden, raad ik je niet aan om die weg te gaan


Antwoord 11

Ik probeer de standaard prioriteit van de resolver te wijzigen en de standaard output van de iterator te wijzigen om je doelen te bereiken.

  1. wijzig de standaard resolver-prioriteit, uitgevoerd voorafgaand aan alle standaard type-verificatie:

    Erft de json.JSONEncoder en overschrijft de iterencode()methode.

    Alle waarden moeten worden ingepakt door het type ValueWrapper, vermijd dat de waarden worden opgelost door standaard standaardresolvers.

  2. wijzig de standaard iterator-uitvoer;

    Implementeer drie aangepaste wrapper-klassen ValueWrapper, ListWrapperen DictWrapper. De ListWrapper implementeert __iter__()en de DictWrapper implementeert __iter__(), items()en iteritems().

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()

Antwoord 12

Vereenvoudiging voor Python 3 (alleen getest op 3.9):

from json.encoder import (_make_iterencode, JSONEncoder,
                          encode_basestring_ascii, INFINITY,
                          encode_basestring)
class CustomObjectEncoder(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)
        Change from json.encoder.JSONEncoder.iterencode is setting
        _one_shot=False and isinstance=self.isinstance
        in call to `_make_iterencode`.
        And not using `c_make_encoder`.
        """
        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=False, isinstance=self.isinstance)
        return _iterencode(o, 0)

Voorbeeld subklasse:

import datetime
from rdflib.term import Literal, BNode
class RDFTermEncoder(CustomObjectEncoder):
    def isinstance(self, o, cls):
        if isinstance(o, (Literal, BNode)):
            return False
        return isinstance(o, cls)
    def default(self, o):
        if isinstance(o, Literal):
            rv = {"value": o.value}
            if o.datatype is not None:
                rv["datatype"] = o.datatype
            if o.language is not None:
                rv["lang"] = o.language
            return rv
        if isinstance(o, BNode):
            return "http://localhost/bnode/" + str(o)
        if isinstance(o, datetime.datetime):
            return o.isoformat()
        if isinstance(o, datetime.date):
            return str(o)
        # Let the base class default method raise the TypeError
        return super().default(o)

Ik heb dit zojuist met succes gebruikt voor mijn werk als

db_json = json.loads(json.dumps(db_custom, cls=RDFTermEncoder))

Bedankt allemaal in deze thread!

Other episodes