JSON wiedereinlesen mit eigenen Objekten

Wenn du dir nicht sicher bist, in welchem der anderen Foren du die Frage stellen sollst, dann bist du hier im Forum für allgemeine Fragen sicher richtig.
Antworten
palango
User
Beiträge: 3
Registriert: Samstag 11. Februar 2012, 06:19

Hey,

ich versuche gerade einen JSON serialisierer für eine eigene Klasse zu schreiben. Das ist der Code:

Code: Alles auswählen

def to_json(pobject):
	if isinstance(pobject, Catalog):
		data = {"name": pobject.name, "comment": pobject.comment}
		return {'__class__': "Catalog",
				'__value__': {"metadata": data, "content": pobject.words}}
	raise TypeError(repr(pobject) + ' is not JSON serializable')
	
def from_json(json_object):
	if "__class__" in json_object and json_object["__class__"] == "Catalog":
		data = json_object["__value__"]
		metadata = data["metadata"]
		
		result = Catalog(metadata["name"], metadata["comment"])
		result.words = data["content"]
		
		return result
Beim Versuch das zu nutzen bekomme ich immer diesen Fehler.

Code: Alles auswählen

  File "catalog.py", line 31, in from_json
    metadata = data["metadata"]
TypeError: 'NoneType' object is not subscriptable
Sieht jemand den fehler oder weiß wie ich die zurückgegebenen Maps zu Python Maps umwandeln kann?

Danke!
BlackJack

@palango: Kann ich nicht nachvollziehen. Das hier läuft bei mir problemlos durch:

Code: Alles auswählen

from pprint import pprint


class Catalog(object):
    def __init__(self, name, comment):
        self.name = name
        self.comment = comment
        self.words = 'Worte'
    
    def __repr__(self):
        return '<%s name:%r comment:%r words:%s>' % (
            self.__class__.__name__, self.name, self.comment, self.words
        )


def to_json(pobject):
    if isinstance(pobject, Catalog):
        data = {'name': pobject.name, 'comment': pobject.comment}
        return {'__class__': 'Catalog',
                '__value__': {'metadata': data, 'content': pobject.words}}
    raise TypeError(repr(pobject) + ' is not JSON serializable')


def from_json(json_object):
    if '__class__' in json_object and json_object['__class__'] == 'Catalog':
        data = json_object['__value__']
        metadata = data['metadata']

        result = Catalog(metadata['name'], metadata['comment'])
        result.words = data['content']

        return result


def main():
    catalog = Catalog('Name', 'Kommentar')
    print catalog
    json_catalog = to_json(catalog)
    pprint(json_catalog)
    catalog = from_json(json_catalog)
    print catalog


if __name__ == '__main__':
    main()
Es sieht so aus als wenn Du etwas deserialisieren willst, was nicht mit dieser Version von `to_json()` umgewandelt wurde. Wie sehen denn die JSON-Daten aus?

Ausserdem wären minimale, aber lauffähige Beispiele hilfreich, die das Problem aufzeigen, damit das einfacher nachvollziehen kann, ohne selbst noch viel Code drumherum schreiben zu müssen. Sowie vollständige Tracebacks, statt nur der letzten Zeilen.
BlackJack

@palango: *So* bekomme ich die Ausnahme auch:

Code: Alles auswählen

def main():
    catalog = Catalog('Name', 'Kommentar')
    print catalog
    json_catalog = json.dumps(catalog, indent=2, default=to_json)
    print json_catalog
    catalog = json.loads(json_catalog, object_hook=from_json)
    print catalog
Die `object_hook`-Funktion wird für *jedes* JSON-Objekt aufgerufen. Und Deine gibt bei allen JSON-Objekten, die kein "__class__"-Attribut mit dem Wert "Catalog" haben den Wert `None` zurück, weil das der Wert ist, der implizit von allen Funktionen zurückgegeben wird, wenn der Programmfluss das Funktionsende ohne ein explizites ``return`` erreicht. Lass Dir mal am Anfang der `from_json()`-Funktion das Argument ausgegen um zu sehen mit welchen Argumenten die Funktion in welcher Reihenfolge aufgerufen wird.
mutetella
User
Beiträge: 1695
Registriert: Donnerstag 5. März 2009, 17:10
Kontaktdaten:

Hallo,

auf den ersten Blick würde ich sagen, dass das nicht JSON ist, was Deine 'to_json'-Funktion zurückgibt.
Grundsätzlich aber erst einmal die Frage: Warum nutzt Du nicht das json-Modul?

Code: Alles auswählen

In [1]: import json

In [2]: vitamine = {'obst': {'C': ['zitrone', 'orange'], 'A': ['aprikose']},'gemuese': {'C': ['kartoffeln'], 'D': ['karotten']}}

In [3]: saved_vitamine = json.dumps(vitamine)

In [4]: del vitamine

In [5]: vitamine
---------------------------------------------------------------------------
NameError                                 Traceback (most recent call last)
<ipython-input-5-da014a0489c1> in <module>()
----> 1 vitamine

NameError: name 'vitamine' is not defined

In [6]: vitamine = json.loads(saved_vitamine)

In [7]: vitamine
Out[7]: 
{u'gemuese': {u'C': [u'kartoffeln'], u'D': [u'karotten']},
 u'obst': {u'A': [u'aprikose'], u'C': [u'zitrone', u'orange']}}
Oder verkenn' ich das Problem?

mutetella
Entspanne dich und wisse, dass es Zeit für alles gibt. (YogiTea Teebeutel Weisheit ;-) )
BlackJack

@mutetella: Schau Dir die `main()` in meinem letzten Beitrag an, wo die beiden Funktion mit dem `json`-Modul verwendet werden, und zu dem Problem aus dem Ursprungsbeitrag führen.
palango
User
Beiträge: 3
Registriert: Samstag 11. Februar 2012, 06:19

@BlackJack: danke für die Hilfe, ein einfaches

Code: Alles auswählen

return json_object
am Ende der Deserialisierung tut seinen Dienst!

@Mutella: Gundproblem ist das ich ein eigenes Objekt serialisieren will, deswegen der ganze Aufwand.
palango
User
Beiträge: 3
Registriert: Samstag 11. Februar 2012, 06:19

So, nächstes Problem. Ich habe jetzt meine Catalog-Klasse von list abgeleitet um das ganze ein bisschen besser nutzbar zu machen. Der Code inkl. Beispiel sieht jetzt so aus:

Code: Alles auswählen

#!/usr/bin/python
# -*- coding: utf-8 -*-

import sys, json
from pprint import pprint

class Catalog(list):
	
	def __init__(self, name, comment="", data=[]):
		list.__init__(self, data)
		self.name = name
		self.comment = comment
		
		self.append(["hängen", "colgar", 0])
		self.append(["essen", "comer", 0])
		self.append(["arbeiten", "trabajar", 0])
		self.append(["sitzen", "sentar", 0])
	
	def __repr__(self):
		return '<%s name:%r comment:%r words:%s>' % (
			self.__class__.__name__, self.name, self.comment, list(self)
		)

def to_json(pobject):
	print pobject.__class__
	if isinstance(pobject, Catalog):
		data = {"name": pobject.name, "comment": pobject.comment}
		pprint(data)
		return {'__class__': "Catalog",
				'__value__': {"metadata": data, "content": pobject}}
	raise TypeError(repr(pobject) + ' is not JSON serializable')
	
def from_json(json_object):
	if "__class__" in json_object and json_object["__class__"] == "Catalog":
		data = json_object["__value__"]
		metadata = data["metadata"]
		
		return Catalog(metadata["name"], metadata["comment"], data["content"])
	return json_object

with open("json.test", "w") as f:
	c = Catalog("test", "blah")
	print c
	json.dump(c, f, default=to_json, indent=2, encoding="UTF-8")
	print "dumped"

with open('json.test', 'r') as f:
	entry = json.load(f, object_hook=from_json, encoding="UTF-8")
	print entry.name
	pprint (entry)
Der Output ist folgender:

Code: Alles auswählen

python catalog.py 
<Catalog name:'test' comment:'blah' words:[['h\xc3\xa4ngen', 'colgar', 0], ['essen', 'comer', 0], ['arbeiten', 'trabajar', 0], ['sitzen', 'sentar', 0]]>
dumped
Traceback (most recent call last):
  File "catalog.py", line 49, in <module>
    print entry.name
AttributeError: 'list' object has no attribute 'name'
Es scheint so, als würde meine Serialisierungsfunktion gar nciht mehr genutzt werden, weil nie die Klasse von pobject ausgegeben wird. Meinungen dazu?
nomnom
User
Beiträge: 487
Registriert: Mittwoch 19. Mai 2010, 16:25

Wird sie auch nicht. Vermutlich erkennt JSONEncoder, dass `Catalog` von `list` abgeleitet wird, und speichert einfach die Liste.
JSONEncoder.__doc__ hat geschrieben:If specified, default is a function that gets called for objects that can't otherwise be serialized.
Ich würde dir einfach dazu raten, einfach to_json(obj) zu dumpen, statt `default` zu nutzen. Außerdem würde ich (to|from)_json als (Klassen-)Methoden implementieren.
sma
User
Beiträge: 3018
Registriert: Montag 19. November 2007, 19:57
Wohnort: Kiel

Es sieht übrigens nicht richtig aus, Textdateien mit "r" und "w" zu öffnen, und dann mit Bytestrings (encoding="utf-8") zu arbeiten. Ich würde empfehlen, "rb" und "wb" zu benutzen, um keine Überraschungen zu erleben.

Stefan
Antworten