it-swarm.com.ru

Как преобразовать данные JSON в объект Python

Я хочу использовать Python для преобразования данных JSON в объект Python.

Я получаю объекты данных JSON из API Facebook, которые я хочу сохранить в своей базе данных. 

Мой текущий вид в Django (Python) (request.POST содержит JSON):

response = request.POST
user = FbApiUser(user_id = response['id'])
user.name = response['name']
user.username = response['username']
user.save()
  • Это прекрасно работает, но как мне обрабатывать сложные объекты данных JSON? 

  • Разве не было бы намного лучше, если бы я мог как-то преобразовать этот объект JSON в объект Python для простоты использования?

191
Sai Krishna

Вы можете сделать это в одну строку, используя namedtuple и 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

или, чтобы использовать это легко:

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)

Если вы хотите, чтобы он обрабатывал ключи, которые не являются хорошими именами атрибутов, проверьте параметр namedtuple's rename .

270
DS.

Изучите раздел под названием Специализация декодирования объекта JSON в jsonдокументация к модулю . Вы можете использовать это для декодирования объекта JSON в определенный тип Python.

Вот пример:

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

Обновление

Если вы хотите получить доступ к данным в словаре через модуль json, сделайте это:

user = json.loads('{"__type__": "User", "name": "John Smith", "username": "jsmith"}')
print user['name']
print user['username']

Так же, как обычный словарь.

112
Shakakai

Это не кодовый гольф, но вот мой самый короткий трюк, использующий types.SimpleNamespace в качестве контейнера для объектов JSON.

По сравнению с ведущим решением namedtuple это:

  • возможно быстрее/меньше, так как не создает класс для каждого объекта
  • короче
  • нет опции rename и, вероятно, такое же ограничение для ключей, которые не являются допустимыми идентификаторами (использует setattr под прикрытием)

Пример:

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

Вы можете попробовать это:

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)

Просто создайте новый объект и передайте параметры в виде карты.

57
cmaluenda

Вот быстрая и грязная альтернатива 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()
22
ubershmekel

Для сложных объектов вы можете использовать JSON Pickle

Библиотека Python для сериализации любого произвольного графа объектов в JSON . Он может взять практически любой объект Python и превратить объект в JSON . Кроме того, он может восстановить объект обратно в Python.

14
sputnikus

Я написал небольшую (de) платформу сериализации под названием any2any , которая помогает выполнять сложные преобразования между двумя типами Python.

В вашем случае, я думаю, вы хотите преобразовать из словаря (полученного с помощью json.loads) в сложный объект response.education ; response.name с вложенной структурой response.education.id и т.д. ..... Так что именно для этого и создана эта структура. Документация пока невелика, но, используя any2any.simple.MappingToObject, вы сможете сделать это очень легко. Пожалуйста, спросите, нужна ли вам помощь.

5
sebpiq

Если вы используете Python 3.5+, вы можете использовать jsons для сериализации и десериализации в простые старые объекты Python:

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

Вы также можете сделать FbApiUser наследовать от jsons.JsonSerializable для большей элегантности:

user = FbApiUser.from_json(response)

Эти примеры будут работать, если ваш класс состоит из типов Python по умолчанию, таких как строки, целые числа, списки, даты и т.д. Для библиотеки jsons требуются подсказки типов для пользовательских типов.

3
R H

Немного изменив ответ @DS, чтобы загрузить из файла:

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)

Одна вещь: это не может загружать предметы с номерами впереди. Как это:

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

Потому что "1_first_item" не является допустимым именем поля Python.

2
Valtoni Boaventura

Python3.x

Наилучшим подходом, которого я мог достичь с помощью моих знаний, было это.
Обратите внимание, что этот код также обрабатывает set ().
Этот подход универсален, просто требует расширения класса (во втором примере).
Обратите внимание, что я просто делаю это с файлами, но поведение легко изменить на ваш вкус.

Однако это кодек.

Приложив немного больше работы, вы можете создать свой класс другими способами .... Я предполагаю, что конструктор по умолчанию будет его экземпляром, а затем я обновлю dict класса.

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)

Правка

Проведя дополнительные исследования, я нашел способ обобщения без необходимости вызова методаSUPERCLASSregister с использованием 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
1
Davi Abreu Wasserberg

Улучшение Lovasoa очень хороший ответ.

Если вы используете Python 3.6+, вы можете использовать:
pip install Marshmallow-enum и
pip install Marshmallow-dataclass

Это просто и безопасно.

Вы можете преобразовать свой класс в строку-json и наоборот:

От объекта к струне 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

От струны Джсон до объекта:

    json_str = '{"name":"Danilo", "orderId":"50", "productName":"RedBull", "quantity":15, "status":"Created"}'
    user, err = User.Schema().loads(json_str)
    print(user,flush=True)

Определения классов:

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

В поисках решения я наткнулся на этот пост в блоге: https://blog.mosthege.net/2016/11/12/json-deserialization-of-nested-objects/

Он использует ту же технику, что и в предыдущих ответах, но с использованием декораторов. Еще одна полезная вещь, которую я нашел, это то, что она возвращает типизированный объект в конце десериализации 

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)

Использование:

@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

Поскольку никто не дал такого ответа, как я, я опубликую его здесь.

Это надежный класс, который может легко конвертировать туда и обратно между json str и dict, которые я скопировал из мой ответ на другой вопрос :

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
Božo Stojković

Если немного расширить ответ DS, то если вам нужно, чтобы объект был изменяемым (это не namedtuple), вы можете использовать библиотеку recordclass вместо namedtuple:

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

Модифицированный объект может быть легко преобразован обратно в json с помощью simplejson :

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

Если вы используете python 3.6+, вы можете использовать Marshmallow-dataclass . Вопреки всем решениям, перечисленным выше, он прост и безопасен:

from Marshmallow_dataclass import dataclass

@dataclass
class User:
    name: str

user, err = User.Schema().load({"name": "Ramirez"})
0
lovasoa