Deserialisieren Sie einen JSON-String für ein Objekt in Python

69

Ich habe die folgende Zeichenfolge

{"action":"print","method":"onData","data":"Madan Mohan"}

Ich möchte zu einem Objekt der Klasse deserialisieren

class payload
    string action
    string method
    string data

Ich benutze Python 2.6 und 2.7

Gurpreet Singh
quelle
Mögliches Duplikat: stackoverflow.com/questions/6578986/…
Sami N

Antworten:

122
>>> j = '{"action": "print", "method": "onData", "data": "Madan Mohan"}'
>>> import json
>>> 
>>> class Payload(object):
...     def __init__(self, j):
...         self.__dict__ = json.loads(j)
... 
>>> p = Payload(j)
>>>
>>> p.action
'print'
>>> p.method
'onData'
>>> p.data
'Madan Mohan'
John La Rooy
quelle
7
Wenn das jemandem hilft, ist meiner Meinung nach die einzig richtige Lösung eine Bibliothek. Keine maßgeschneiderte Serialisierung. Siehe jsonpickle
guyarad
8
Weisen Sie für eine Klasse mit vorhandenen Mitgliedern nicht __dict__so zu. Sie verlieren vorhandene Mitglieder, wenn sie nicht in der JSON-Zeichenfolge enthalten sind. Wenn Sie es tun müssen, verwenden Sieself.__dict__.update(otherdict)
nurettin
51

Um Samis Antwort näher zu erläutern:

Aus den Dokumenten :

class Payload(object):
    def __init__(self, action, method, data):
        self.action = action
        self.method = method
        self.data = data

import json

def as_payload(dct):
    return Payload(dct['action'], dct['method'], dct['data'])

payload = json.loads(message, object_hook = as_payload)

Mein Einwand gegen die

.__dict__ 

Die Lösung besteht darin, dass die Payload-Klasse, während sie die Arbeit erledigt und präzise ist, vollständig generisch wird - sie dokumentiert ihre Felder nicht.

Wenn die Payload-Nachricht beispielsweise ein unerwartetes Format hatte, wurde beim Erstellen der Payload kein Fehler generiert, anstatt einen nicht gefundenen Schlüsselfehler auszulösen, bis die Payload verwendet wurde.

Alex
quelle
2
my 2 cents, ersetzt return Payload(dct['action'], dct['method'], dct['data'])mit return Payload(dct.get('action', None), dct.get('method', None), dct.get('data', None))einigen Feldern zu ermöglichen , optional zu sein.
Gulats
12

Wenn Sie die Typhinweise in Python 3.6 übernehmen, können Sie dies folgendermaßen tun:

def from_json(data, cls):
    annotations: dict = cls.__annotations__ if hasattr(cls, '__annotations__') else None
    if issubclass(cls, List):
        list_type = cls.__args__[0]
        instance: list = list()
        for value in data:
            instance.append(from_json(value, list_type))
        return instance
    elif issubclass(cls, Dict):
            key_type = cls.__args__[0]
            val_type = cls.__args__[1]
            instance: dict = dict()
            for key, value in data.items():
                instance.update(from_json(key, key_type), from_json(value, val_type))
            return instance
    else:
        instance : cls = cls()
        for name, value in data.items():
            field_type = annotations.get(name)
            if inspect.isclass(field_type) and isinstance(value, (dict, tuple, list, set, frozenset)):
                setattr(instance, name, from_json(value, field_type))
            else:
                setattr(instance, name, value)
        return instance

Damit können Sie typisierte Objekte wie folgt instanziieren:

class Bar:
    value : int

class Foo:
    x : int
    bar : List[Bar]


obj : Foo = from_json(json.loads('{"x": 123, "bar":[{"value": 3}, {"value": 2}, {"value": 1}]}'), Foo)
print(obj.x)
print(obj.bar[2].value)

Diese Syntax erfordert jedoch Python 3.6 und deckt nicht alle Fälle ab - zum Beispiel die Unterstützung der Eingabe. Alle ... Aber zumindest verschmutzt sie nicht die Klassen, die mit zusätzlichen init / tojson-Methoden deserialisiert werden müssen.

GaspardP
quelle
10

Wenn Sie Codezeilen speichern und die flexibelste Lösung beibehalten möchten, können wir die JSON-Zeichenfolge in ein dynamisches Objekt deserialisieren:

p = lambda:None
p.__dict__ = json.loads('{"action": "print", "method": "onData", "data": "Madan Mohan"}')


>>>> p.action
output: u'print '

>>>> p.method
output: u'onData '

Nery Jr.
quelle
5
Dies ist verlockend nah, behandelt jedoch nicht verschachteltes JSON. p.__dict__ = json.loads('{"parent":{"child":{"name":"henry"}}}')erfordert immer noch den Zugriff auf ein Kind wie ein Diktat.
MarkHu
7

Ich dachte, ich verliere alle Haare, um diese „Herausforderung“ zu lösen. Ich hatte folgende Probleme:

  1. Deserialisieren verschachtelter Objekte, Listen usw.
  2. Ich mag Konstruktoren mit bestimmten Feldern
  3. Ich mag keine dynamischen Felder
  4. Ich mag keine hackigen Lösungen

Ich habe eine Bibliothek namens gefunden jsonpickle die sich als sehr nützlich erwiesen hat.

Installation:

pip install jsonpickle

Hier ist ein Codebeispiel zum Schreiben verschachtelter Objekte in eine Datei:

import jsonpickle


class SubObject:
    def __init__(self, sub_name, sub_age):
        self.sub_name = sub_name
        self.sub_age = sub_age


class TestClass:

    def __init__(self, name, age, sub_object):
        self.name = name
        self.age = age
        self.sub_object = sub_object


john_junior = SubObject("John jr.", 2)

john = TestClass("John", 21, john_junior)

file_name = 'JohnWithSon' + '.json'

john_string = jsonpickle.encode(john)

with open(file_name, 'w') as fp:
    fp.write(john_string)

john_from_file = open(file_name).read()

test_class_2 = jsonpickle.decode(john_from_file)

print(test_class_2.name)
print(test_class_2.age)
print(test_class_2.sub_object.sub_name)

Ausgabe:

John
21
John jr.

Webseite: http://jsonpickle.github.io/

Hoffe, es wird Ihre Zeit (und Haare) sparen.

LukaszTaraszka
quelle
1
Unterstützt es die Validierung? auch tipps tipps?
Anum Sheraz
6

Ich ziehe es vor, die Felder zu überprüfen, z. B. damit Sie Fehler abfangen können, z. B. wenn Sie einen ungültigen JSON erhalten, oder nicht den erwarteten JSON, also habe ich namedtuples verwendet:

from collections import namedtuple
payload = namedtuple('payload', ['action', 'method', 'data'])
def deserialize_payload(json):
    kwargs =  dict([(field, json[field]) for field in payload._fields]) 
    return payload(**kwargs)

Auf diese Weise erhalten Sie nette Fehler, wenn der JSON, den Sie analysieren, nicht mit dem übereinstimmt, was Sie analysieren möchten

>>> json = {"action":"print","method":"onData","data":"Madan Mohan"}
>>> deserialize_payload(json)
payload(action='print', method='onData', data='Madan Mohan')
>>> badjson = {"error":"404","info":"page not found"}
>>> deserialize_payload(badjson)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "<stdin>", line 2, in deserialize_payload
KeyError: 'action'

Wenn Sie verschachtelte Relationen analysieren möchten, '{"parent":{"child":{"name":"henry"}}}' können Sie z. B. weiterhin die Namenstupel und sogar eine wiederverwendbarere Funktion verwenden

Person = namedtuple("Person", ['parent'])
Parent = namedtuple("Parent", ['child'])
Child = namedtuple('Child', ['name'])
def deserialize_json_to_namedtuple(json, namedtuple):
    return namedtuple(**dict([(field, json[field]) for field in namedtuple._fields]))

def deserialize_person(json):
     json['parent']['child']  = deserialize_json_to_namedtuple(json['parent']['child'], Child)
     json['parent'] =  deserialize_json_to_namedtuple(json['parent'], Parent) 
     person = deserialize_json_to_namedtuple(json, Person)
     return person

dir geben

>>> deserialize_person({"parent":{"child":{"name":"henry"}}})
Person(parent=Parent(child=Child(name='henry')))
>>> deserialize_person({"error":"404","info":"page not found"})
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "<stdin>", line 2, in deserialize_person
KeyError: 'parent'
Jens Timmerman
quelle
6

In neueren Versionen von Python können Sie die Marshmallow-Datenklasse verwenden :

from marshmallow_dataclass import dataclass

@dataclass
class Payload
    action:str
    method:str
    data:str

Payload.Schema().load({"action":"print","method":"onData","data":"Madan Mohan"})
Lovasoa
quelle
1
Dies scheint der bequemste Weg zu sein.
Vab2048
5

Sie können einen Encoder für die Objekterstellung spezialisieren: http://docs.python.org/2/library/json.html

import json
class ComplexEncoder(json.JSONEncoder):
    def default(self, obj):
        if isinstance(obj, complex):
            return {"real": obj.real,
            "imag": obj.imag,
            "__class__": "complex"}
        return json.JSONEncoder.default(self, obj)

print json.dumps(2 + 1j, cls=ComplexEncoder)
Sami N.
quelle
1
Bitte fügen Sie nicht nur die Links in die Antworten ein, da diese jederzeit ausfallen können. Geben Sie auch hier eine kurze Antwort von diesem Link.
Anum Sheraz
Bei dieser Frage geht es um Dekodierung, nicht um Kodierung, daher hilft dies überhaupt nicht
Garr Godfrey
3

Eine andere Möglichkeit besteht darin, die JSON-Zeichenfolge einfach als Diktat an den Konstruktor Ihres Objekts zu übergeben. Zum Beispiel ist Ihr Objekt:

class Payload(object):
    def __init__(self, action, method, data, *args, **kwargs):
        self.action = action
        self.method = method
        self.data = data

Und die folgenden zwei Zeilen Python-Code werden es konstruieren:

j = json.loads(yourJsonString)
payload = Payload(**j)

Grundsätzlich erstellen wir zuerst ein generisches JSON-Objekt aus der JSON-Zeichenfolge. Dann übergeben wir das generische json-Objekt als Diktat an den Konstruktor der Payload-Klasse. Der Konstruktor der Payload-Klasse interpretiert das Diktat als Schlüsselwortargumente und legt alle entsprechenden Felder fest.

Rubel
quelle
2

Es gibt verschiedene Methoden, um JSON-Zeichenfolgen für ein Objekt zu deserialisieren. Alle oben genannten Methoden sind akzeptabel, aber ich empfehle die Verwendung einer Bibliothek, um doppelte Schlüsselprobleme oder das Serialisieren / Deserialisieren verschachtelter Objekte zu verhindern.

Pykson ist ein JSON Serializer und Deserializer für Python, mit denen Sie dies erreichen können. Definieren Sie einfach das Payload-Klassenmodell als JsonObject und konvertieren Sie die Pyson-Zeichenfolge mit Pykson in ein Objekt.

from pykson import Pykson, JsonObject, StringField

class Payload(pykson.JsonObject):
    action = StringField()
    method = StringField()
    data = StringField()

json_text = '{"action":"print","method":"onData","data":"Madan Mohan"}'
payload = Pykson.from_json(json_text, Payload)
Sina Rezaei
quelle
2

pydantic ist eine zunehmend beliebte Bibliothek für Python 3.6+ -Projekte. Es führt hauptsächlich Datenvalidierung und Einstellungsverwaltung mithilfe von Typhinweisen durch.

Ein einfaches Beispiel mit verschiedenen Typen:

from pydantic import BaseModel

class ClassicBar(BaseModel):
    count_drinks: int
    is_open: bool
 
data = {'count_drinks': '226', 'is_open': 'False'}
cb = ClassicBar(**data)
>>> cb
ClassicBar(count_drinks=226, is_open=False)

Was ich an der Bibliothek liebe, ist, dass man viele Goodies kostenlos bekommt, wie zum Beispiel

>>> cb.json()
'{"count_drinks": 226, "is_open": false}'
>>> cb.dict()
{'count_drinks': 226, 'is_open': False}
LarsVegas
quelle
1

Während Alex ' Antwort auf eine gute Technik hinweist, stößt die von ihm angegebene Implementierung auf ein Problem, wenn wir verschachtelte Objekte haben.

class more_info
    string status

class payload
    string action
    string method
    string data
    class more_info

mit dem folgenden Code:

def as_more_info(dct):
    return MoreInfo(dct['status'])

def as_payload(dct):
    return Payload(dct['action'], dct['method'], dct['data'], as_more_info(dct['more_info']))

payload = json.loads(message, object_hook = as_payload)

payload.more_infowird auch als eine Instanz behandelt, payloaddie zu Analysefehlern führt.

Aus den offiziellen Dokumenten:

object_hook ist eine optionale Funktion, die mit dem Ergebnis eines dekodierten Objektliteral (einem Diktat) aufgerufen wird. Der Rückgabewert von object_hook wird anstelle des Diktats verwendet.

Daher würde ich lieber die folgende Lösung vorschlagen:

class MoreInfo(object):
    def __init__(self, status):
        self.status = status

    @staticmethod
    def fromJson(mapping):
        if mapping is None:
            return None

        return MoreInfo(
            mapping.get('status')
        )

class Payload(object):
    def __init__(self, action, method, data, more_info):
        self.action = action
        self.method = method
        self.data = data
        self.more_info = more_info

    @staticmethod
    def fromJson(mapping):
        if mapping is None:
            return None

        return Payload(
            mapping.get('action'),
            mapping.get('method'),
            mapping.get('data'),
            MoreInfo.fromJson(mapping.get('more_info'))
        )

import json
def toJson(obj, **kwargs):
    return json.dumps(obj, default=lambda j: j.__dict__, **kwargs)

def fromJson(msg, cls, **kwargs):
    return cls.fromJson(json.loads(msg, **kwargs))

info = MoreInfo('ok')
payload = Payload('print', 'onData', 'better_solution', info)
pl_json = toJson(payload)
l1 = fromJson(pl_json, Payload)

Gulats
quelle