Ich möchte Python verwenden, um JSON-Daten in ein Python-Objekt zu konvertieren.
Ich erhalte JSON-Datenobjekte von der Facebook-API, die ich in meiner Datenbank speichern möchte.
Meine aktuelle Ansicht in Django (Python) (request.POST
enthält die JSON):
response = request.POST
user = FbApiUser(user_id = response['id'])
user.name = response['name']
user.username = response['username']
user.save()
Das funktioniert gut, aber wie gehe ich mit komplexen JSON-Datenobjekten um?
Wäre es nicht viel besser, wenn ich dieses JSON-Objekt für die einfache Verwendung irgendwie in ein Python-Objekt konvertieren könnte?
Sie können dies in einer Zeile tun, indem Sie namedtuple
und object_hook
verwenden:
import json
from collections import namedtuple
data = '{"name": "John Smith", "hometown": {"name": "New York", "id": 123}}'
# Parse JSON into an object with attributes corresponding to dict keys.
x = json.loads(data, object_hook=lambda d: namedtuple('X', d.keys())(*d.values()))
print x.name, x.hometown.name, x.hometown.id
oder, um dies einfach wiederzuverwenden:
def _json_object_hook(d): return namedtuple('X', d.keys())(*d.values())
def json2obj(data): return json.loads(data, object_hook=_json_object_hook)
x = json2obj(data)
Wenn Sie Schlüssel verwenden möchten, die keine guten Attributnamen enthalten, überprüfen Sie namedtuple
s rename
-Parameter .
Schauen Sie sich den Abschnitt mit der Überschrift Spezialisierung der JSON-Objektdekodierung in der json
Moduldokumentation an. Damit können Sie ein JSON-Objekt in einen bestimmten Python-Typ dekodieren.
Hier ist ein Beispiel:
class User(object):
def __init__(self, name, username):
self.name = name
self.username = username
import json
def object_decoder(obj):
if '__type__' in obj and obj['__type__'] == 'User':
return User(obj['name'], obj['username'])
return obj
json.loads('{"__type__": "User", "name": "John Smith", "username": "jsmith"}',
object_hook=object_decoder)
print type(User) # -> <type 'type'>
Update
Wenn Sie über das json-Modul auf Daten in einem Wörterbuch zugreifen möchten, gehen Sie folgendermaßen vor:
user = json.loads('{"__type__": "User", "name": "John Smith", "username": "jsmith"}')
print user['name']
print user['username']
Wie ein normales Wörterbuch.
Dies ist kein Code-Golf, aber hier ist mein kürzester Trick, als Container für JSON-Objekte types.SimpleNamespace
zu verwenden.
Verglichen mit der führenden namedtuple
-Lösung ist dies:
rename
-Option und wahrscheinlich die gleiche Einschränkung für Schlüssel, die keine gültigen Bezeichner sind (verwendet setattr
unter den Deckblättern)Beispiel:
from __future__ import print_function
import json
try:
from types import SimpleNamespace as Namespace
except ImportError:
# Python 2.x fallback
from argparse import Namespace
data = '{"name": "John Smith", "hometown": {"name": "New York", "id": 123}}'
x = json.loads(data, object_hook=lambda d: Namespace(**d))
print (x.name, x.hometown.name, x.hometown.id)
Sie könnten dies versuchen:
class User(object):
def __init__(self, name, username, *args, **kwargs):
self.name = name
self.username = username
import json
j = json.loads(your_json)
u = User(**j)
Erstellen Sie einfach ein neues Objekt und übergeben Sie die Parameter als Karte.
Hier ist eine schnelle und schmutzige Json Pickle-Alternative
import json
class User:
def __init__(self, name, username):
self.name = name
self.username = username
def to_json(self):
return json.dumps(self.__dict__)
@classmethod
def from_json(cls, json_str):
json_dict = json.loads(json_str)
return cls(**json_dict)
# example usage
User("tbrown", "Tom Brown").to_json()
User.from_json(User("tbrown", "Tom Brown").to_json()).to_json()
Für komplexe Objekte können Sie JSON Pickle verwenden.
Python-Bibliothek zur Serialisierung beliebiger Objektgraphen in JSON . Es kann fast jedes Python-Objekt verwenden und das Objekt in JSON ..__ umwandeln. Außerdem kann das Objekt wieder in Python rekonstruiert werden.
Ich habe ein kleines (De-) Serialisierungs-Framework namens any2any geschrieben, das komplexe Transformationen zwischen zwei Python-Typen ermöglicht.
In Ihrem Fall möchte ich, dass Sie aus einem Wörterbuch (mit json.loads
erhalten) ein komplexes Objekt response.education ; response.name
mit einer geschachtelten Struktur response.education.id
usw. konvertieren möchten. Die Dokumentation ist noch nicht großartig, aber durch die Verwendung von any2any.simple.MappingToObject
sollte dies sehr einfach möglich sein. Bitte fragen Sie, wenn Sie Hilfe benötigen.
Wenn Sie Python 3.5+ verwenden, können Sie jsons
verwenden, um alte Python-Objekte zu serialisieren und zu deserialisieren:
import jsons
response = request.POST
# You'll need your class attributes to match your dict keys, so in your case do:
response['id'] = response.pop('user_id')
# Then you can load that dict into your class:
user = jsons.load(response, FbApiUser)
user.save()
Sie könnten auch FbApiUser
von jsons.JsonSerializable
für mehr Eleganz erben lassen:
user = FbApiUser.from_json(response)
Diese Beispiele funktionieren, wenn Ihre Klasse aus Python-Standardtypen besteht, wie z. B. Zeichenfolgen, Ganzzahlen, Listen, Datumsangaben usw. Die jsons
lib erfordert jedoch Typhinweise für benutzerdefinierte Typen.
@DS-Antwort ein wenig ändern, um sie aus einer Datei zu laden:
def _json_object_hook(d): return namedtuple('X', d.keys())(*d.values())
def load_data(file_name):
with open(file_name, 'r') as file_data:
return file_data.read().replace('\n', '')
def json2obj(file_name): return json.loads(load_data(file_name), object_hook=_json_object_hook)
Eine Sache: Dies kann keine Artikel mit vorausgehenden Nummern laden. So was:
{
"1_first_item": {
"A": "1",
"B": "2"
}
}
Weil "1_first_item" kein gültiger Python-Feldname ist.
Python3.x
Der beste Ansatz, den ich mit meinem Wissen erreichen konnte, war dieser.
Beachten Sie, dass dieser Code auch set () behandelt.
Dieser Ansatz ist allgemein und erfordert nur die Erweiterung der Klasse (im zweiten Beispiel).
Beachten Sie, dass ich es nur für Dateien mache, aber es ist einfach, das Verhalten an Ihren Geschmack anzupassen.
Dies ist jedoch ein CoDec.
Mit etwas mehr Arbeit können Sie Ihre Klasse auf andere Weise konstruieren ... Ich gehe davon aus, dass ein Standardkonstruktor eine Instanz darstellt, und aktualisiere dann den Klassendikt.
import json
import collections
class JsonClassSerializable(json.JSONEncoder):
REGISTERED_CLASS = {}
def register(ctype):
JsonClassSerializable.REGISTERED_CLASS[ctype.__name__] = ctype
def default(self, obj):
if isinstance(obj, collections.Set):
return dict(_set_object=list(obj))
if isinstance(obj, JsonClassSerializable):
jclass = {}
jclass["name"] = type(obj).__name__
jclass["dict"] = obj.__dict__
return dict(_class_object=jclass)
else:
return json.JSONEncoder.default(self, obj)
def json_to_class(self, dct):
if '_set_object' in dct:
return set(dct['_set_object'])
Elif '_class_object' in dct:
cclass = dct['_class_object']
cclass_name = cclass["name"]
if cclass_name not in self.REGISTERED_CLASS:
raise RuntimeError(
"Class {} not registered in JSON Parser"
.format(cclass["name"])
)
instance = self.REGISTERED_CLASS[cclass_name]()
instance.__dict__ = cclass["dict"]
return instance
return dct
def encode_(self, file):
with open(file, 'w') as outfile:
json.dump(
self.__dict__, outfile,
cls=JsonClassSerializable,
indent=4,
sort_keys=True
)
def decode_(self, file):
try:
with open(file, 'r') as infile:
self.__dict__ = json.load(
infile,
object_hook=self.json_to_class
)
except FileNotFoundError:
print("Persistence load failed "
"'{}' do not exists".format(file)
)
class C(JsonClassSerializable):
def __init__(self):
self.mill = "s"
JsonClassSerializable.register(C)
class B(JsonClassSerializable):
def __init__(self):
self.a = 1230
self.c = C()
JsonClassSerializable.register(B)
class A(JsonClassSerializable):
def __init__(self):
self.a = 1
self.b = {1, 2}
self.c = B()
JsonClassSerializable.register(A)
A().encode_("test")
b = A()
b.decode_("test")
print(b.a)
print(b.b)
print(b.c.a)
Bearbeiten
Mit etwas mehr Recherche fand ich einen Weg, zu verallgemeinern, ohne dass derSUPERKLASSEregister-Methodenaufruf verwendet werden sollte, indem eine metaclass
import json
import collections
REGISTERED_CLASS = {}
class MetaSerializable(type):
def __call__(cls, *args, **kwargs):
if cls.__not in REGISTERED_CLASS:
REGISTERED_CLASS[cls.__name__] = cls
return super(MetaSerializable, cls).__call__(*args, **kwargs)
class JsonClassSerializable(json.JSONEncoder, metaclass=MetaSerializable):
def default(self, obj):
if isinstance(obj, collections.Set):
return dict(_set_object=list(obj))
if isinstance(obj, JsonClassSerializable):
jclass = {}
jclass["name"] = type(obj).__name__
jclass["dict"] = obj.__dict__
return dict(_class_object=jclass)
else:
return json.JSONEncoder.default(self, obj)
def json_to_class(self, dct):
if '_set_object' in dct:
return set(dct['_set_object'])
Elif '_class_object' in dct:
cclass = dct['_class_object']
cclass_name = cclass["name"]
if cclass_name not in REGISTERED_CLASS:
raise RuntimeError(
"Class {} not registered in JSON Parser"
.format(cclass["name"])
)
instance = REGISTERED_CLASS[cclass_name]()
instance.__dict__ = cclass["dict"]
return instance
return dct
def encode_(self, file):
with open(file, 'w') as outfile:
json.dump(
self.__dict__, outfile,
cls=JsonClassSerializable,
indent=4,
sort_keys=True
)
def decode_(self, file):
try:
with open(file, 'r') as infile:
self.__dict__ = json.load(
infile,
object_hook=self.json_to_class
)
except FileNotFoundError:
print("Persistence load failed "
"'{}' do not exists".format(file)
)
class C(JsonClassSerializable):
def __init__(self):
self.mill = "s"
class B(JsonClassSerializable):
def __init__(self):
self.a = 1230
self.c = C()
class A(JsonClassSerializable):
def __init__(self):
self.a = 1
self.b = {1, 2}
self.c = B()
A().encode_("test")
b = A()
b.decode_("test")
print(b.a)
# 1
print(b.b)
# {1, 2}
print(b.c.a)
# 1230
print(b.c.c.mill)
# s
Verbesserung der sehr guten Antwort des Lovasoa.
Wenn Sie Python 3.6 oder höher verwenden, können Sie Folgendes verwenden:pip install Marshmallow-enum
undpip install Marshmallow-dataclass
Es ist einfach und typsicher.
Sie können Ihre Klasse in einen String-Json umwandeln und umgekehrt:
Vom Objekt zum String Json:
from Marshmallow_dataclass import dataclass
user = User("Danilo","50","RedBull",15,OrderStatus.CREATED)
user_json = User.Schema().dumps(user)
user_json_str = user_json.data
Vom String Json zum Objekt:
json_str = '{"name":"Danilo", "orderId":"50", "productName":"RedBull", "quantity":15, "status":"Created"}'
user, err = User.Schema().loads(json_str)
print(user,flush=True)
Klassendefinitionen:
class OrderStatus(Enum):
CREATED = 'Created'
PENDING = 'Pending'
CONFIRMED = 'Confirmed'
FAILED = 'Failed'
@dataclass
class User:
def __init__(self, name, orderId, productName, quantity, status):
self.name = name
self.orderId = orderId
self.productName = productName
self.quantity = quantity
self.status = status
name: str
orderId: str
productName: str
quantity: int
status: OrderStatus
Bei der Suche nach einer Lösung bin ich auf diesen Blogbeitrag gestoßen: https://blog.mosthege.net/2016/11/12/json-deserialization-of-nested-objects/
Es verwendet die gleiche Technik wie in den vorherigen Antworten, jedoch unter Verwendung von Dekorateuren. .__ Eine andere Sache, die ich nützlich fand, ist die Tatsache, dass sie am Ende der Deserialisierung ein typisiertes Objekt zurückgibt
class JsonConvert(object):
class_mappings = {}
@classmethod
def class_mapper(cls, d):
for keys, cls in clsself.mappings.items():
if keys.issuperset(d.keys()): # are all required arguments present?
return cls(**d)
else:
# Raise exception instead of silently returning None
raise ValueError('Unable to find a matching class for object: {!s}'.format(d))
@classmethod
def complex_handler(cls, Obj):
if hasattr(Obj, '__dict__'):
return Obj.__dict__
else:
raise TypeError('Object of type %s with value of %s is not JSON serializable' % (type(Obj), repr(Obj)))
@classmethod
def register(cls, claz):
clsself.mappings[frozenset(Tuple([attr for attr,val in cls().__dict__.items()]))] = cls
return cls
@classmethod
def to_json(cls, obj):
return json.dumps(obj.__dict__, default=cls.complex_handler, indent=4)
@classmethod
def from_json(cls, json_str):
return json.loads(json_str, object_hook=cls.class_mapper)
Verwendungszweck:
@JsonConvert.register
class Employee(object):
def __init__(self, Name:int=None, Age:int=None):
self.Name = Name
self.Age = Age
return
@JsonConvert.register
class Company(object):
def __init__(self, Name:str="", Employees:[Employee]=None):
self.Name = Name
self.Employees = [] if Employees is None else Employees
return
company = Company("Contonso")
company.Employees.append(Employee("Werner", 38))
company.Employees.append(Employee("Mary"))
as_json = JsonConvert.to_json(company)
from_json = JsonConvert.from_json(as_json)
as_json_from_json = JsonConvert.to_json(from_json)
assert(as_json_from_json == as_json)
print(as_json_from_json)
Da niemand eine ähnliche Antwort wie meine gegeben hat, werde ich sie hier posten.
Es ist eine robuste Klasse, die leicht zwischen json str
und dict
hin und her konvertieren kann, die ich aus meiner Antwort auf eine andere Frage kopiert habe:
import json
class PyJSON(object):
def __init__(self, d):
if type(d) is str:
d = json.loads(d)
self.from_dict(d)
def from_dict(self, d):
self.__dict__ = {}
for key, value in d.items():
if type(value) is dict:
value = PyJSON(value)
self.__dict__[key] = value
def to_dict(self):
d = {}
for key, value in self.__dict__.items():
if type(value) is PyJSON:
value = value.to_dict()
d[key] = value
return d
def __repr__(self):
return str(self.to_dict())
def __setitem__(self, key, value):
self.__dict__[key] = value
def __getitem__(self, key):
return self.__dict__[key]
json_str = """... json string ..."""
py_json = PyJSON(json_str)
Wenn Sie die Antwort von DS etwas erweitern möchten, können Sie die recordclass bibliothek anstelle von namedtuple verwenden, wenn das Objekt veränderbar sein soll (was namedtuple nicht ist):
import json
from recordclass import recordclass
data = '{"name": "John Smith", "hometown": {"name": "New York", "id": 123}}'
# Parse into a mutable object
x = json.loads(data, object_hook=lambda d: recordclass('X', d.keys())(*d.values()))
Das modifizierte Objekt kann dann sehr einfach mit simplejson in json konvertiert werden:
x.name = "John Doe"
new_json = simplejson.dumps(x)
Wenn Sie Python 3.6 oder höher verwenden, können Sie Marshmallow-dataclass verwenden. Im Gegensatz zu allen oben aufgeführten Lösungen ist es sowohl einfach als auch typsicher:
from Marshmallow_dataclass import dataclass
@dataclass
class User:
name: str
user, err = User.Schema().load({"name": "Ramirez"})