So konvertieren Sie JSON-Daten in ein Python-Objekt

281

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.POSTenthält den 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 zur einfachen Verwendung in ein Python-Objekt konvertieren könnte?

Sai Krishna
quelle
Normalerweise wird JSON in Vanille-Listen oder Diktate konvertiert. Ist es das was du willst? Oder möchten Sie JSON direkt in einen benutzerdefinierten Typ konvertieren?
Shakakai
Ich möchte es in ein Objekt konvertieren, auf das ich mit dem "." Zugreifen kann. . Wie aus dem obigen Beispiel -> reponse.name, response.education.id etc ....
Sai Krishna
44
Die Verwendung von dicts ist eine Methode mit schwacher Sauce, um objektorientiert zu programmieren. Wörterbücher sind eine sehr schlechte Möglichkeit, den Lesern Ihres Codes Erwartungen zu vermitteln. Wie können Sie mithilfe eines Wörterbuchs klar und wiederverwendbar angeben, dass einige Wörterbuchschlüssel-Wert-Paare erforderlich sind, andere nicht? Wie wäre es mit der Bestätigung, dass ein bestimmter Wert im akzeptablen Bereich oder Satz liegt? Was ist mit Funktionen, die spezifisch für den Objekttyp sind, mit dem Sie arbeiten (auch bekannt als Methoden)? Wörterbücher sind praktisch und vielseitig, aber zu viele Entwickler tun so, als hätten sie vergessen, dass Python aus einem bestimmten Grund eine objektorientierte Sprache ist.
Eintopf
1
Es gibt eine Python-Bibliothek für diese github.com/jsonpickle/jsonpickle (Kommentar, da die Antwort zu unten im Thread ist und nicht erreichbar ist.)
Beste Wünsche

Antworten:

355

Sie können dies in einer Zeile mit namedtupleund tun object_hook:

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 möchten, dass Schlüssel verarbeitet werden, die keine guten Attributnamen sind, überprüfen Sie namedtupleden renameParameter .

DS.
quelle
8
Dies kann zu einem Wertefehler führen. ValueError: Typnamen und Feldnamen dürfen nicht mit einer Zahl beginnen: '123'
PvdL
3
Als Python-Neuling bin ich interessiert, ob dies auch dann eine sichere Sache ist, wenn es um Sicherheit geht.
Benjamin
8
Dadurch wird eine neue unterschiedliche Klasse jedes Mal ein JSON - Objekt zu stoßen beim Parsen, nicht wahr?
fikr4n
2
Interessant. Ich dachte , dass es nicht garantiert ist, sich auf dieselbe Reihenfolge zu verlassen d.keys()und sie zu d.values()wiederholen, aber ich habe mich geirrt. In den Dokumenten heißt es: "Wenn Schlüssel-, Werte- und Elementansichten ohne dazwischenliegende Änderungen am Wörterbuch wiederholt werden, entspricht die Reihenfolge der Elemente direkt." Gut zu wissen für so kleine, lokale Codeblöcke. Ich würde jedoch einen Kommentar hinzufügen, um die Betreuer des Codes explizit auf eine solche Abhängigkeit aufmerksam zu machen.
cfi
1
Mir ist keine nette Allzweck-Rückwärtsoperation bekannt. Jedes einzelne benannte Tupel kann mit einem Diktat verwendet werden x._asdict(), was in einfachen Fällen hilfreich sein kann .
DS.
127

Lesen Sie den Abschnitt mit dem Titel Spezialisieren der JSON-Objektdecodierung in der json Moduldokumentation . 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'>

Aktualisieren

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']

Genau wie ein normales Wörterbuch.

Shakakai
quelle
1
Hey, ich habe gerade gelesen und festgestellt, dass Wörterbücher völlig funktionieren. Nur ich habe mich gefragt, wie ich JSON-Objekte in Wörterbücher konvertieren und wie ich aus dem Wörterbuch auf diese Daten zugreifen kann.
Sai Krishna
Genial, es ist fast klar, ich wollte nur noch eine Kleinigkeit wissen: Wenn es dieses Objekt gibt -> {'education': {'name1': 456, 'name2': 567}}, wie greife ich auf diese Daten zu?
Sai Krishna
es wäre nur topLevelData ['education'] ['name1'] ==> 456. Sinn machen?
Shakakai
1
@ Ben: Ich denke dein Kommentar ist unangemessen. Von allen Antworten hier ist es derzeit die einzige, die den Unterricht richtig macht. Was bedeutet: Es ist eine Operation in einem Durchgang und das Ergebnis verwendet die richtigen Typen. Pickle selbst ist für andere Anwendungen als JSON (Binär- oder Textwiederholung) vorgesehen, und jsonpickle ist eine nicht standardmäßige Bibliothek. Es würde mich interessieren, wie Sie das Problem lösen, dass die std json lib den oberen
Analysebaum
Da muss ich @Ben zustimmen. Dies ist eine wirklich schlechte Lösung. Überhaupt nicht skalierbar. Sie müssen die Feldnamen als Zeichenfolge und als Feld pflegen. Wenn Sie Ihre Felder umgestalten möchten, schlägt die Dekodierung fehl (natürlich sind die bereits serialisierten Daten ohnehin nicht mehr relevant). Das gleiche Konzept ist bereits gut mit jsonpickle
guyarad
98

Dies ist kein Code-Golf, aber hier ist mein kürzester Trick, der types.SimpleNamespaceals Container für JSON-Objekte verwendet wird.

Im Vergleich zur führenden namedtupleLösung ist es:

  • wahrscheinlich schneller / kleiner, da nicht für jedes Objekt eine Klasse erstellt wird
  • kürzer
  • Keine renameOption und wahrscheinlich die gleiche Einschränkung für Schlüssel, die keine gültigen Bezeichner sind (Verwendung setattrunter dem Deckmantel).

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)
Eddygeek
quelle
2
Übrigens bietet die Serialisierungsbibliothek Marshmallow mit ihrem @post_loadDekorator eine ähnliche Funktion . marshmallow.readthedocs.io/en/latest/…
Taylor Edmiston
3
Um die Abhängigkeit von Argparse zu vermeiden: Ersetzen Sie den Argparse-Import durch from types import SimpleNamespaceund verwenden Sie:x = json.loads(data, object_hook=lambda d: SimpleNamespace(**d))
maxschlepzig
8
Dies ist die eleganteste Lösung, sollte oben sein.
ScalaWilliam
4
Bearbeitet, um die Lösung von @ maxschlepzig unter Python 3.x zu verwenden ( types.SimpleNamespaceexistiert in 2.7 leider nicht).
Dan Lenski
1
warum print_function?
Chwi
90

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.

cmaluenda
quelle
1
Ich erhalte TypeError: 'Benutzer'-Objekt ist nicht abonnierbar
Mahdi
1
Dies sollte die akzeptierte Antwort sein. arbeitete für mich viel einfacher als alle anderen.
Izik
Ich habe keine * args, ** kwargs verwendet, aber die Lösung hat funktioniert.
Malkaviano
1
Benutzer (** j) sagt, dass die Parameter für Name und Benutzername fehlen. Wie wird das Diktat initialisiert?
Aaron Stainback
40

Hier ist eine schnelle und schmutzige Alternative zu json pickle

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()
ubershmekel
quelle
1
Dies ist kein guter Ansatz. Zunächst sollten to_json und from_json nicht in Ihre Klasse aufgenommen werden. Zweitens funktioniert es nicht für verschachtelte Klassen.
Jurass
17

Für komplexe Objekte können Sie JSON Pickle verwenden

Python-Bibliothek zum Serialisieren eines beliebigen Objektgraphen in JSON. Es kann fast jedes Python-Objekt aufnehmen und das Objekt in JSON verwandeln. Darüber hinaus kann das Objekt wieder in Python wiederhergestellt werden.

sputnikus
quelle
6
Ich denke, jsonstruct ist besser. jsonstruct originally a fork of jsonpickle (Thanks guys!). The key difference between this library and jsonpickle is that during deserialization, jsonpickle requires Python types to be recorded as part of the JSON. This library intends to remove this requirement, instead, requires a class to be passed in as an argument so that its definition can be inspected. It will then return an instance of the given class. This approach is similar to how Jackson (of Java) works.
Abhishek Gupta
3
Das Problem mit jsonstruct ist, dass es nicht gewartet zu werden scheint (tatsächlich sieht es verlassen aus) und eine Liste von Objekten wie z '[{"name":"object1"},{"name":"object2"}]'. jsonpickle geht auch nicht sehr gut damit um.
LS
1
Ich habe keine Ahnung, warum diese Antwort nicht mehr Stimmen bekommt. Die meisten anderen Lösungen sind ziemlich weit draußen. Jemand hat eine großartige Bibliothek für die JSON-De- / Serialisierung entwickelt - warum nicht? Außerdem scheint es gut mit Listen zu funktionieren - was war Ihr Problem damit @LS?
Guyarad
1
@guyarad, das Problem ist: x = jsonpickle.decode ('[{"name": "object1"}, {"name": "object2"}]') gibt eine Liste von Wörterbüchern an ([{'name': ' Objekt1 '}, {' Name ':' Objekt2 '}]), keine Liste von Objekten mit Eigenschaften (x [0] .name ==' Objekt1 '), was die ursprüngliche Frage erforderte. Um das zu erreichen, habe ich den von eddygeek vorgeschlagenen object_hook / Namespace-Ansatz verwendet, aber der schnelle / schmutzige Ansatz von ubershmekel sieht auch gut aus. Ich denke, ich könnte object_hook mit jsonpickles set_encoder_options () verwenden (undokumentiert!), Aber es würde mehr Code benötigen als das grundlegende json-Modul. Ich würde gerne das Gegenteil beweisen!
LS
@LS Wenn Sie keine Kontrolle über die Eingabe haben, was das OP wirklich verlangt hat, ist jsonpickle nicht ideal, da es den tatsächlichen Typ in jeder Ebene erwartet (und grundlegende Typen annimmt, wenn diese fehlen). Beide Lösungen sind "süß".
Guyarad
12

Wenn Sie Python 3.5+ verwenden, können Sie damit jsonsalte Python-Objekte serialisieren und 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 für mehr Eleganz FbApiUsererben jsons.JsonSerializable:

user = FbApiUser.from_json(response)

Diese Beispiele funktionieren, wenn Ihre Klasse aus Python-Standardtypen wie Zeichenfolgen, Ganzzahlen, Listen, Datumsangaben usw. besteht. Die Bibliothek jsonserfordert jedoch Typhinweise für benutzerdefinierte Typen.

RH
quelle
7

Wenn Sie Python 3.6+ verwenden, können Sie die Marshmallow-Datenklasse 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"})
Lovasoa
quelle
TypeError: make_data_class() got an unexpected keyword argument 'many'
JOhn
@JOhn: Sie sollten ein Problem mit einem reproduzierbaren Testfall in github.com/lovasoa/marshmallow_dataclass/issues
lovasoa
5

Ich habe ein kleines ( De- ) Serialisierungsframework namens any2any geschrieben , das bei komplexen Transformationen zwischen zwei Python-Typen hilft.

In Ihrem Fall möchten Sie wahrscheinlich von einem Wörterbuch (erhalten mit json.loads) zu einem komplexen Objekt response.education ; response.namemit einer verschachtelten Struktur response.education.idusw. transformieren. Genau dafür ist dieses Framework gemacht. Die Dokumentation ist noch nicht großartig, aber wenn Sie sie verwenden any2any.simple.MappingToObject, sollten Sie dies sehr einfach tun können. Bitte fragen Sie, ob Sie Hilfe benötigen.

sebpiq
quelle
Sebpiq, haben any2any installiert und haben Probleme, die beabsichtigte Abfolge von Methodenaufrufen zu verstehen. Können Sie ein einfaches Beispiel für die Konvertierung eines Wörterbuchs in ein Python-Objekt mit einer Eigenschaft für jeden Schlüssel geben?
Sansjoe
Hallo @sansjoe! Wenn Sie es von pypi installiert haben, ist die Version völlig veraltet. Ich habe vor einigen Wochen ein vollständiges Refactoring durchgeführt. Sie sollten die Github-Version verwenden (ich muss eine ordnungsgemäße Veröffentlichung machen!)
Sebpiq
Ich habe es von Pypy installiert, weil der Github sagte, es von Pypy zu installieren. Außerdem hast du gesagt, dass Pypy vor Monaten veraltet war. Es hat nicht funktioniert :( Ich habe einen Fehlerbericht eingereicht tho! Github.com/sebpiq/any2any/issues/11
sneilan
5

Verbesserung der sehr guten Antwort der Lovasoa.

Wenn Sie Python 3.6+ verwenden, können Sie Folgendes verwenden:
pip install marshmallow-enumund
pip 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
Danilo
quelle
1
Sie brauchen den Konstruktor nicht, übergeben Sie einfach init = True an die Datenklasse und los geht's.
Josef Korbel
4

Da niemand eine Antwort wie meine gegeben hat, werde ich sie hier posten.

Es ist eine robuste Klasse, die leicht zwischen json hin und her konvertieren kann strund dictdie 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)
Božo Stojković
quelle
2

Ändern der @ DS-Antwort ein wenig, 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 Elemente mit Nummern vor laden. So was:

{
  "1_first_item": {
    "A": "1",
    "B": "2"
  }
}

Weil "1_first_item" kein gültiger Python-Feldname ist.

Valtoni Boaventura
quelle
2

Bei der Suche nach einer Lösung bin ich auf diesen Blog-Beitrag gestoßen: https://blog.mosthege.net/2016/11/12/json-deserialization-of-nested-objects/

Es verwendet die gleiche Technik wie in den vorherigen Antworten angegeben, 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)
Enazar
quelle
2

Wenn Sie die Antwort von DS ein wenig erweitern möchten, müssen Sie die Recordclass- Bibliothek anstelle von namedtuple verwenden , wenn das Objekt veränderbar sein soll (was benanntes Tupel 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 geänderte Objekt kann dann mit simplejson sehr einfach wieder in json konvertiert werden :

x.name = "John Doe"
new_json = simplejson.dumps(x)
BeneStr
quelle
1

Wenn Sie Python 3.6 oder höher verwenden , können Sie sich Squema ansehen - ein leichtes Modul für statisch typisierte Datenstrukturen. Es macht Ihren Code einfach zu lesen und bietet gleichzeitig eine einfache Datenvalidierung, -konvertierung und -serialisierung ohne zusätzlichen Aufwand. Sie können sich das als eine ausgefeiltere und einfühlsame Alternative zu Namedtuples und Datenklassen vorstellen. So können Sie es verwenden:

from uuid import UUID
from squema import Squema


class FbApiUser(Squema):
    id: UUID
    age: int
    name: str

    def save(self):
        pass


user = FbApiUser(**json.loads(response))
user.save()
Funkatisch
quelle
Dies ähnelt auch eher den Methoden der JVM-Sprache.
Javadba
1

Ich suchte nach einer Lösung, die funktioniert recordclass.RecordClass, verschachtelte Objekte unterstützt und sowohl für die JSON-Serialisierung als auch für die JSON-Deserialisierung funktioniert.

Als ich die Antwort von DS und die Lösung von BeneStr erweiterte, fand ich Folgendes, das zu funktionieren scheint:

Code:

import json
import recordclass

class NestedRec(recordclass.RecordClass):
    a : int = 0
    b : int = 0

class ExampleRec(recordclass.RecordClass):
    x : int       = None
    y : int       = None
    nested : NestedRec = NestedRec()

class JsonSerializer:
    @staticmethod
    def dumps(obj, ensure_ascii=True, indent=None, sort_keys=False):
        return json.dumps(obj, default=JsonSerializer.__obj_to_dict, ensure_ascii=ensure_ascii, indent=indent, sort_keys=sort_keys)

    @staticmethod
    def loads(s, klass):
        return JsonSerializer.__dict_to_obj(klass, json.loads(s))

    @staticmethod
    def __obj_to_dict(obj):
        if hasattr(obj, "_asdict"):
            return obj._asdict()
        else:
            return json.JSONEncoder().default(obj)

    @staticmethod
    def __dict_to_obj(klass, s_dict):
        kwargs = {
            key : JsonSerializer.__dict_to_obj(cls, s_dict[key]) if hasattr(cls,'_asdict') else s_dict[key] \
                for key,cls in klass.__annotations__.items() \
                    if s_dict is not None and key in s_dict
        }
        return klass(**kwargs)

Verwendungszweck:

example_0 = ExampleRec(x = 10, y = 20, nested = NestedRec( a = 30, b = 40 ) )

#Serialize to JSON

json_str = JsonSerializer.dumps(example_0)
print(json_str)
#{
#  "x": 10,
#  "y": 20,
#  "nested": {
#    "a": 30,
#    "b": 40
#  }
#}

# Deserialize from JSON
example_1 = JsonSerializer.loads(json_str, ExampleRec)
example_1.x += 1
example_1.y += 1
example_1.nested.a += 1
example_1.nested.b += 1

json_str = JsonSerializer.dumps(example_1)
print(json_str)
#{
#  "x": 11,
#  "y": 21,
#  "nested": {
#    "a": 31,
#    "b": 41
#  }
#}
Shriram V.
quelle
1

Die hier gegebenen Antworten geben nicht den richtigen Objekttyp zurück, daher habe ich diese Methoden unten erstellt. Sie schlagen auch fehl, wenn Sie versuchen, der Klasse weitere Felder hinzuzufügen, die im angegebenen JSON nicht vorhanden sind:

def dict_to_class(class_name: Any, dictionary: dict) -> Any:
    instance = class_name()
    for key in dictionary.keys():
        setattr(instance, key, dictionary[key])
    return instance


def json_to_class(class_name: Any, json_string: str) -> Any:
    dict_object = json.loads(json_string)
    return dict_to_class(class_name, dict_object)
Caner
quelle
0

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 generisch und benötigt nur die Erweiterung der Klasse (im zweiten Beispiel).
Beachten Sie, dass ich es nur mit Dateien mache, aber es ist einfach, das Verhalten nach Ihrem Geschmack zu ändern.

Dies ist jedoch ein CoDec.

Mit etwas mehr Arbeit können Sie Ihre Klasse auf andere Weise aufbauen. Ich gehe davon aus, dass ein Standardkonstruktor es instanziiert, und aktualisiere dann das Klassendiktat.

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 Forschung habe ich einen Weg gefunden, ohne die Notwendigkeit des Aufrufs der SUPERCLASS- Registermethode unter Verwendung einer Metaklasse zu verallgemeinern

import json
import collections

REGISTERED_CLASS = {}

class MetaSerializable(type):

    def __call__(cls, *args, **kwargs):
        if cls.__name__ 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
Davi Abreu Wasserberg
quelle
0

Sie können verwenden

x = Map(json.loads(response))
x.__class__ = MyClass

wo

class Map(dict):
    def __init__(self, *args, **kwargs):
        super(Map, self).__init__(*args, **kwargs)
        for arg in args:
            if isinstance(arg, dict):
                for k, v in arg.iteritems():
                    self[k] = v
                    if isinstance(v, dict):
                        self[k] = Map(v)

        if kwargs:
            # for python 3 use kwargs.items()
            for k, v in kwargs.iteritems():
                self[k] = v
                if isinstance(v, dict):
                    self[k] = Map(v)

    def __getattr__(self, attr):
        return self.get(attr)

    def __setattr__(self, key, value):
        self.__setitem__(key, value)

    def __setitem__(self, key, value):
        super(Map, self).__setitem__(key, value)
        self.__dict__.update({key: value})

    def __delattr__(self, item):
        self.__delitem__(item)

    def __delitem__(self, key):
        super(Map, self).__delitem__(key)
        del self.__dict__[key]

Für eine generische, zukunftssichere Lösung.

Gulzar
quelle
-5

Verwenden Sie das jsonModul ( neu in Python 2.6 ) oder das simplejsonModul, das fast immer installiert ist.

Chris Morgan
quelle
2
Hey, danke, dass du geantwortet hast. Können Sie bitte ein Beispiel veröffentlichen, wie Sie den JSON dekodieren und dann auf diese Daten zugreifen können?
Sai Krishna
Hey, jetzt hast du einen Punkt, aber irgendwie ziehe ich es vor, ohne es zu wissen und es dann rückzuentwickeln: D.
Sai Krishna
1
@Zach: Es gibt Beispiele ganz oben in den Dokumenten, mit denen ich verlinkt habe.
Chris Morgan