Seite 1 von 1

JSON wiedereinlesen mit eigenen Objekten

Verfasst: Samstag 11. Februar 2012, 06:26
von palango
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!

Re: JSON wiedereinlesen mit eigenen Objekten

Verfasst: Samstag 11. Februar 2012, 09:34
von 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.

Re: JSON wiedereinlesen mit eigenen Objekten

Verfasst: Samstag 11. Februar 2012, 09:47
von 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.

Re: JSON wiedereinlesen mit eigenen Objekten

Verfasst: Samstag 11. Februar 2012, 09:51
von mutetella
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

Re: JSON wiedereinlesen mit eigenen Objekten

Verfasst: Samstag 11. Februar 2012, 09:58
von 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.

Re: JSON wiedereinlesen mit eigenen Objekten

Verfasst: Samstag 11. Februar 2012, 21:14
von palango
@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.

Re: JSON wiedereinlesen mit eigenen Objekten

Verfasst: Samstag 11. Februar 2012, 21:42
von palango
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?

Re: JSON wiedereinlesen mit eigenen Objekten

Verfasst: Sonntag 12. Februar 2012, 00:04
von nomnom
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.

Re: JSON wiedereinlesen mit eigenen Objekten

Verfasst: Sonntag 12. Februar 2012, 12:07
von sma
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