it-swarm.com.de

So konvertieren Sie JSON-Daten in ein Python-Objekt

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?

191
Sai Krishna

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 namedtuples rename-Parameter .

270
DS.

Schauen Sie sich den Abschnitt mit der Überschrift Spezialisierung der JSON-Objektdekodierung in der jsonModuldokumentation 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.

112
Shakakai

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:

  • wahrscheinlich schneller/kleiner, da für jedes Objekt keine Klasse erstellt wird
  • kürzer
  • keine 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)
72
eddygeek

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.

57
cmaluenda

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()
22
ubershmekel

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.

14
sputnikus

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.

5
sebpiq

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.

3
R H

@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.

2

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 und
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
1
danilo

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)
1
enazar

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)
0

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)
0
BeneStr

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"})
0
lovasoa