简体   繁体   English

如何将JSON数据转换成Python object?

[英]How to convert JSON data into a Python object?

I want to convert JSON data into a Python object.我想将JSON数据转换成Python object。

I receive JSON data objects from the Facebook API, which I want to store in my database.我从 Facebook API 收到 JSON 数据对象,我想将其存储在我的数据库中。

My current View in Django (Python) ( request.POST contains the JSON):我在 Django (Python) 中的当前视图( request.POST包含 JSON):

response = request.POST
user = FbApiUser(user_id = response['id'])
user.name = response['name']
user.username = response['username']
user.save()
  • This works fine, but how do I handle complex JSON data objects?这工作正常,但我如何处理复杂的 JSON 数据对象?
  • Wouldn't it be much better if I could somehow convert this JSON object into a Python object for easy use?如果我能以某种方式将这个 JSON object 转换成 Python object 以便于使用,岂不是更好?

UPDATE更新

With Python3, you can do it in one line, using SimpleNamespace and object_hook :使用 Python3,您可以使用SimpleNamespaceobject_hook在一行中完成:

import json
from types import SimpleNamespace

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: SimpleNamespace(**d))
print(x.name, x.hometown.name, x.hometown.id)

OLD ANSWER (Python2)旧答案(Python2)

In Python2, you can do it in one line, using namedtuple and object_hook (but it's very slow with many nested objects):在 Python2 中,您可以使用namedtupleobject_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

or, to reuse this easily:或者,要轻松地重用它:

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)

If you want it to handle keys that aren't good attribute names, check out namedtuple 's rename parameter .如果您希望它处理不是好的属性名称的键,请查看namedtuplerename参数

Check out the section titled Specializing JSON object decoding in the json module documentation .查看json模块文档中标题为“专业化 JSON object 解码”的部分。 You can use that to decode a JSON object into a specific Python type.您可以使用它将 JSON object 解码为特定的 Python 类型。

Here's an example:这是一个例子:

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更新

If you want to access data in a dictionary via the json module do this:如果您想通过 json 模块访问字典中的数据,请执行以下操作:

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

Just like a regular dictionary.就像一本普通的字典。

You could try this:你可以试试这个:

class User(object):
    def __init__(self, name, username):
        self.name = name
        self.username = username

import json
j = json.loads(your_json)
u = User(**j)

Just create a new object, and pass the parameters as a map.只需创建一个新的 object,并将参数作为 map 传递。

Note: It does not work for nested classes.注意:它不适用于嵌套类。


You can have a JSON with objects too:您也可以使用带有对象的 JSON :

import json
class Address(object):
    def __init__(self, street, number):
        self.street = street
        self.number = number

    def __str__(self):
        return "{0} {1}".format(self.street, self.number)

class User(object):
    def __init__(self, name, address):
        self.name = name
        self.address = Address(**address)

    def __str__(self):
        return "{0} ,{1}".format(self.name, self.address)

if __name__ == '__main__':
    js = '''{"name":"Cristian", "address":{"street":"Sesame","number":122}}'''
    j = json.loads(js)
    print(j)
    u = User(**j)
    print(u)

This is not code golf, but here is my shortest trick, using types.SimpleNamespace as the container for JSON objects.这不是代码高尔夫,但这是我最短的技巧,使用types.SimpleNamespace作为 JSON 对象的容器。

Compared to the leading namedtuple solution, it is:与领先的namedtuple解决方案相比,它是:

  • probably faster/smaller as it does not create a class for each object可能更快/更小,因为它不会为每个 object 创建 class
  • shorter更短
  • no rename option, and probably the same limitation on keys that are not valid identifiers (uses setattr under the covers)没有rename选项,并且可能对无效标识符的键有相同的限制(在封面下使用setattr

Example:例子:

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)

Here's a quick and dirty json pickle alternative这是一个快速而肮脏的 json 泡菜替代品

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

For complex objects, you can use JSON Pickle对于复杂的对象,可以使用JSON Pickle

Python library for serializing any arbitrary object graph into JSON. Python 库,用于将任意 object 图形序列化为 JSON。 It can take almost any Python object and turn the object into JSON.几乎可以使用任何 Python object 并将 object 变成 Z0ECD11C1D7A287401D148A23BBD7。 Additionally, it can reconstitute the object back into Python.此外,它可以将 object 重组回 Python。

If you're using Python 3.5+, you can use jsons to serialize and deserialize to plain old Python objects:如果您使用的是 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()

You could also make FbApiUser inherit from jsons.JsonSerializable for more elegance:您还可以让FbApiUser继承自jsons.JsonSerializable以获得更优雅:

user = FbApiUser.from_json(response)

These examples will work if your class consists of Python default types, like strings, integers, lists, datetimes, etc. The jsons lib will require type hints for custom types though.如果您的 class 包含 Python 默认类型(如字符串、整数、列表、日期时间等),则这些示例将起作用。不过, jsons库将需要自定义类型的类型提示。

If you are using python 3.6+, you can use marshmallow-dataclass .如果您使用的是 python 3.6+,则可以使用marshmallow- dataclass 。 Contrarily to all the solutions listed above, it is both simple, and type safe:与上面列出的所有解决方案相反,它既简单又类型安全:

from marshmallow_dataclass import dataclass

@dataclass
class User:
    name: str

user = User.Schema().load({"name": "Ramirez"})

Improving the lovasoa's very good answer.改进 lovasoa 的非常好的答案。

If you are using python 3.6+, you can use:如果您使用的是 python 3.6+,您可以使用:
pip install marshmallow-enum and pip install marshmallow-enum
pip install marshmallow-dataclass

Its simple and type safe.它简单且类型安全。

You can transform your class in a string-json and vice-versa:您可以将 class 转换为 string-json,反之亦然:

From Object to String Json:从 Object 到字符串 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

From String Json to Object:从字符串 Json 到 Object:

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

Class definitions: Class 定义:

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

dacite may also be a solution for you, it supports following features: dacite也可能是您的解决方案,它支持以下功能:

  • nested structures嵌套结构
  • (basic) types checking (基本)类型检查
  • optional fields (ie typing.Optional)可选字段(即打字。可选)
  • unions工会
  • forward references前向引用
  • collections collections
  • custom type hooks自定义类型挂钩

https://pypi.org/project/dacite/ https://pypi.org/project/dacite/

from dataclasses import dataclass
from dacite import from_dict


@dataclass
class User:
    name: str
    age: int
    is_active: bool


data = {
    'name': 'John',
    'age': 30,
    'is_active': True,
}

user = from_dict(data_class=User, data=data)

assert user == User(name='John', age=30, is_active=True)

Since no one provided an answer quite like mine, I am going to post it here.由于没有人提供像我这样的答案,我将在此处发布。

It is a robust class that can easily convert back and forth between JSON str and dict that I have copied from my answer to another question :它是一个强大的 class 可以轻松地在 JSON strdict之间来回转换,我从我对另一个问题的回答中复制了它:

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)

I have written a small (de)serialization framework called any2any that helps doing complex transformations between two Python types.我编写了一个名为any2any的小型(反)序列化框架,它有助于在两个 Python 类型之间进行复杂的转换。

In your case, I guess you want to transform from a dictionary (obtained with json.loads ) to an complex object response.education; response.name在您的情况下,我想您想从字典(使用json.loads获得)转换为复杂的 object response.education; response.name response.education; response.name , with a nested structure response.education.id , etc... So that's exactly what this framework is made for. response.education; response.name ,具有嵌套结构response.education.id等......所以这正是这个框架的目的。 The documentation is not great yet, but by using any2any.simple.MappingToObject , you should be able to do that very easily.文档还不是很好,但是通过使用any2any.simple.MappingToObject ,您应该能够非常轻松地做到这一点。 Please ask if you need help.请询问您是否需要帮助。

Expanding on DS's answer a bit, if you need the object to be mutable (which namedtuple is not), you can use the recordclass library instead of namedtuple:稍微扩展 DS 的答案,如果您需要 object 是可变的(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()))

The modified object can then be converted back to json very easily using simplejson :修改后的 object 然后可以使用simplejson很容易地转换回 json :

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

JSON to python object JSON 至 python object

The follwing code creates dynamic attributes with the objects keys recursively.以下代码使用对象键递归地创建动态属性。

JSON object - fb_data.json : JSON object - fb_data.json

{
    "name": "John Smith",
    "hometown": {
        "name": "New York",
        "id": 123
    },
    "list": [
        "a",
        "b",
        "c",
        1,
        {
            "key": 1
        }
    ],
    "object": {
        "key": {
            "key": 1
        }
    }
}

On the conversion we have 3 cases:在转换中,我们有 3 种情况:

  • lists列表
  • dicts (new object) dicts(新对象)
  • bool, int, float and str bool、int、float 和 str
import json


class AppConfiguration(object):
    def __init__(self, data=None):
        if data is None:
            with open("fb_data.json") as fh:
                data = json.loads(fh.read())
        else:
            data = dict(data)

        for key, val in data.items():
            setattr(self, key, self.compute_attr_value(val))

    def compute_attr_value(self, value):
        if isinstance(value, list):
            return [self.compute_attr_value(x) for x in value]
        elif isinstance(value, dict):
            return AppConfiguration(value)
        else:
            return value


if __name__ == "__main__":
    instance = AppConfiguration()

    print(instance.name)
    print(instance.hometown.name)
    print(instance.hometown.id)
    print(instance.list[4].key)
    print(instance.object.key.key)

Now the key, value pairs are attributes - objects.现在键值对是属性 - 对象。

output: output:

John Smith
New York
123
1
1

Paste JSON as Code将 JSON 粘贴为代码

Supports TypeScript , Python , Go , Ruby , C# , Java , Swift , Rust , Kotlin , C++ , Flow , Objective-C , JavaScript , Elm , and JSON Schema . Supports TypeScript , Python , Go , Ruby , C# , Java , Swift , Rust , Kotlin , C++ , Flow , Objective-C , JavaScript , Elm , and JSON Schema .

  • Interactively generate types and (de-)serialization code from JSON, JSON Schema, and TypeScript从 JSON、JSON Schema 和 TypeScript 交互式生成类型和(反)序列化代码
  • Paste JSON/JSON Schema/TypeScript as code将 JSON/JSON Schema/TypeScript 粘贴为代码

在此处输入图像描述

quicktype infers types from sample JSON data, then outputs strongly typed models and serializers for working with that data in your desired programming language. quicktype从样本 JSON 数据中推断类型,然后输出强类型模型和序列化程序,以便以所需的编程语言处理该数据。

output: output:

# Generated by https://quicktype.io
#
# To change quicktype's target language, run command:
#
#   "Set quicktype target language"

from typing import List, Union


class Hometown:
    name: str
    id: int

    def __init__(self, name: str, id: int) -> None:
        self.name = name
        self.id = id


class Key:
    key: int

    def __init__(self, key: int) -> None:
        self.key = key


class Object:
    key: Key

    def __init__(self, key: Key) -> None:
        self.key = key


class FbData:
    name: str
    hometown: Hometown
    list: List[Union[Key, int, str]]
    object: Object

    def __init__(self, name: str, hometown: Hometown, list: List[Union[Key, int, str]], object: Object) -> None:
        self.name = name
        self.hometown = hometown
        self.list = list
        self.object = object

This extension is available for free in the Visual Studio Code Marketplace .此扩展在Visual Studio Code Marketplace中免费提供。

While searching for a solution, I've stumbled upon this blog post: https://blog.mosthege.net/2016/11/12/json-deserialization-of-nested-objects/在寻找解决方案时,我偶然发现了这篇博文: https://blog.mosthege.net/2016/11/12/json-deserialization-of-nested-objects/

It uses the same technique as stated in previous answers but with a usage of decorators.它使用与先前答案中所述相同的技术,但使用了装饰器。 Another thing I found useful is the fact that it returns a typed object at the end of deserialisation我发现有用的另一件事是它在反序列化结束时返回一个类型为 object

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)

Usage:用法:

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

The lightest solution I think is我认为最轻的解决方案是

import orjson  # faster then json =)
from typing import NamedTuple

_j = '{"name":"Иван","age":37,"mother":{"name":"Ольга","age":58},"children":["Маша","Игорь","Таня"],"married": true,' \
     '"dog":null} '


class PersonNameAge(NamedTuple):
    name: str
    age: int


class UserInfo(NamedTuple):
    name: str
    age: int
    mother: PersonNameAge
    children: list
    married: bool
    dog: str


j = json.loads(_j)
u = UserInfo(**j)

print(u.name, u.age, u.mother, u.children, u.married, u.dog)

>>> Ivan 37 {'name': 'Olga', 'age': 58} ['Mary', 'Igor', 'Jane'] True None

Modifying @DS response a bit, to load from a file:稍微修改 @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)

One thing: this cannot load items with numbers ahead.一件事:这无法加载带有数字的项目。 Like this:像这样:

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

Because "1_first_item" is not a valid python field name.因为“1_first_item”不是有效的 python 字段名称。

The answers given here does not return the correct object type, hence I created these methods below.这里给出的答案没有返回正确的 object 类型,因此我在下面创建了这些方法。 They also fail if you try to add more fields to the class that does not exist in the given JSON:如果您尝试向给定 JSON 中不存在的 class 添加更多字段,它们也会失败:

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)

There are multiple viable answers already, but there are some minor libraries made by individuals that can do the trick for most users.已经有多个可行的答案,但是有一些个人制作的小型库可以为大多数用户解决问题。

An example would be json2object .一个例子是json2object Given a defined class, it deserialises json data to your custom model, including custom attributes and child objects.给定一个定义的 class,它将 json 数据反序列化为您的自定义 model,包括自定义属性和子对象。

Its use is very simple.它的使用非常简单。 An example from the library wiki:图书馆维基的一个例子:

 from json2object import jsontoobject as jo class Student: def __init__(self): self.firstName = None self.lastName = None self.courses = [Course('')] class Course: def __init__(self, name): self.name = name data = '''{ "firstName": "James", "lastName": "Bond", "courses": [{ "name": "Fighting"}, { "name": "Shooting"} ] } ''' model = Student() result = jo.deserialize(data, model) print(result.courses[0].name)

class SimpleClass:
    def __init__(self, **kwargs):
        for k, v in kwargs.items():
            if type(v) is dict:
                setattr(self, k, SimpleClass(**v))
            else:
                setattr(self, k, v)


json_dict = {'name': 'jane doe', 'username': 'jane', 'test': {'foo': 1}}

class_instance = SimpleClass(**json_dict)

print(class_instance.name, class_instance.test.foo)
print(vars(class_instance))

dataclass-wizard is a modern option that can similarly work for you. dataclass-wizard是一个现代选项,同样可以为您工作。 It supports auto key casing transforms, such as camelCase or TitleCase , both of which is quite common in API responses.它支持自动键大小写转换,例如camelCaseTitleCase ,这两者在 API 响应中都很常见。

The default key transform when dumping instance to a dict /JSON is camelCase , but this can be easily overriden using a Meta config supplied on the main dataclass.将实例转储到dict /JSON 时的默认键转换是camelCase ,但可以使用主数据类上提供的元配置轻松覆盖。

https://pypi.org/project/dataclass-wizard/ https://pypi.org/project/dataclass-wizard/

from dataclasses import dataclass

from dataclass_wizard import fromdict, asdict


@dataclass
class User:
    name: str
    age: int
    is_active: bool


data = {
    'name': 'John',
    'age': 30,
    'isActive': True,
}

user = fromdict(User, data)
assert user == User(name='John', age=30, is_active=True)

json_dict = asdict(user)
assert json_dict == {'name': 'John', 'age': 30, 'isActive': True}

Example of setting a Meta config, which converts fields to lisp-case when serializing to dict /JSON:设置 Meta 配置的示例,当序列化为dict /JSON 时将字段转换为lisp-case

DumpMeta(key_transform='LISP').bind_to(User)

If you're using Python 3.6 or newer, you could have a look at squema - a lightweight module for statically typed data structures.如果您使用的是 Python 3.6 或更新版本,您可以查看squema - 一个用于静态类型数据结构的轻量级模块。 It makes your code easy to read while at the same time providing simple data validation, conversion and serialization without extra work.它使您的代码易于阅读,同时提供简单的数据验证、转换和序列化,无需额外工作。 You can think of it as a more sophisticated and opinionated alternative to namedtuples and dataclasses.您可以将其视为命名元组和数据类的更复杂和自以为是的替代方案。 Here's how you could use it:以下是您可以使用它的方法:

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

You can use您可以使用

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

where在哪里

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]

For a generic, future-proof solution.对于通用的、面向未来的解决方案。

I was searching for a solution that worked with recordclass.RecordClass , supports nested objects and works for both json serialization and json deserialization.我正在寻找与recordclass.RecordClass一起使用的解决方案,支持嵌套对象并且适用于 json 序列化和 json 反序列化。

Expanding on DS's answer, and expanding on solution from BeneStr, I came up with the following that seems to work:扩展 DS 的答案,并扩展 BeneStr 的解决方案,我想出了以下似乎可行的方法:

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)

Usage:用法:

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
#  }
#}

Python3.x Python3.x

The best aproach I could reach with my knowledge was this.我能用我的知识达到的最好方法就是这个。
Note that this code treat set() too.请注意,此代码也处理 set()。
This approach is generic just needing the extension of class (in the second example).这种方法是通用的,只需要扩展 class(在第二个示例中)。
Note that I'm just doing it to files, but it's easy to modify the behavior to your taste.请注意,我只是对文件执行此操作,但根据您的喜好修改行为很容易。

However this is a CoDec.然而,这是一个编解码器。

With a little more work you can construct your class in other ways.多做一点工作,您就可以用其他方式构建您的 class。 I assume a default constructor to instance it, then I update the class dict.我假设一个默认构造函数来实例化它,然后我更新 class 字典。

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)

Edit编辑

With some more of research I found a way to generalize without the need of the SUPERCLASS register method call, using a metaclass通过更多的研究,我找到了一种无需SUPERCLASS注册方法调用即可泛化的方法,使用元类

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

this is not a very difficult thing, i saw the answers above, most of them had a performance problem in the "list"这不是一件很困难的事情,我看到了上面的答案,其中大多数在“列表”中都有性能问题

this code is much faster than the above这段代码比上面的要快得多

import json 

class jsonify:
    def __init__(self, data):
        self.jsonify = data

    def __getattr__(self, attr):
        value = self.jsonify.get(attr)
        if isinstance(value, (list, dict)):
            return jsonify(value)
        return value

    def __getitem__(self, index):
        value = self.jsonify[index]
        if isinstance(value, (list, dict)):
            return jsonify(value)
        return value

    def __setitem__(self, index, value):
        self.jsonify[index] = value

    def __delattr__(self, index):
        self.jsonify.pop(index)

    def __delitem__(self, index):
        self.jsonify.pop(index)

    def __repr__(self):
        return json.dumps(self.jsonify, indent=2, default=lambda x: str(x))

exmaple示例

response = jsonify(
    {
        'test': {
            'test1': [{'ok': 1}]
        }
    }
)
response.test -> jsonify({'test1': [{'ok': 1}]})
response.test.test1 -> jsonify([{'ok': 1}])
response.test.test1[0] -> jsonify({'ok': 1})
response.test.test1[0].ok -> int(1)

This appears to be an AB question (asking A where the actual problem is B).这似乎是一个 AB 问题(问 A 实际问题在哪里 B)。

The root of the issue is: How to effectively reference/modify deep-nested JSON structures without having to do ob['foo']['bar'][42]['quux'], which poses a typing challenge, a code-bloat issue, a readability issue and an error-trapping issue?问题的根源是:如何有效地引用/修改深层嵌套的 JSON 结构,而无需执行 ob['foo']['bar'][42]['quux'],这带来了打字挑战,代码-膨胀问题,可读性问题和错误捕获问题?

Use glom使用glom

https://glom.readthedocs.io/en/latest/tutorial.html https://glom.readthedocs.io/en/latest/tutorial.html

from glom import glom

# Basic deep get

data = {'a': {'b': {'c': 'd'}}}

print(glom(data, 'a.b.c'))

It will handle list items also: glom(data, 'abc.42.d')它还将处理列表项: glom(data, 'abc.42.d')

I've benchmarked it against a naive implementation:我已经将它与一个幼稚的实现进行了基准测试:

def extract(J, levels):
    # Twice as fast as using glom
    for level in levels.split('.'):
        J = J[int(level) if level.isnumeric() else level]
    return J

... and it returns 0.14ms on a complex JSON object, compared with 0.06ms for the naive impl. ...它在复杂的 JSON object 上返回 0.14 毫秒,而天真的 impl 则为 0.06 毫秒。

It can also handle comlex queries, eg pulling out all foo.bar.records where .name == 'Joe Bloggs'它还可以处理复杂的查询,例如提取所有foo.bar.records where .name == 'Joe Bloggs'

EDIT:编辑:

Another performant approach is to recursively use a class that overrides __getitem__ and __getattr__ :另一种高效的方法是递归使用覆盖__getitem____getattr__的 class :

class Ob:
    def __init__(self, J):
        self.J = J

    def __getitem__(self, index):
        return Ob(self.J[index])

    def __getattr__(self, attr):
        value = self.J.get(attr, None)
        return Ob(value) if type(value) in (list, dict) else value

Now you can do:现在你可以这样做:

ob = Ob(J)

# if you're fetching a final raw value (not list/dict
ob.foo.bar[42].quux.leaf

# for intermediate values
ob.foo.bar[42].quux.J

This also benchmarks surprisingly well.这也令人惊讶地很好地进行了基准测试。 Comparable with my previous naive impl.可与我之前的幼稚 impl 相媲美。 If anyone can spot a way to tidy up access for non-leaf queries, leave a comment!如果有人能找到一种方法来整理非叶查询的访问权限,请发表评论!

Here is my way.这是我的方式。

Features特征

  • support type hints支持类型提示
  • raise error if key is missing.如果缺少密钥,则引发错误。
  • skip extra value in data跳过数据中的额外值
import typing

class User:
    name: str
    age: int

    def __init__(self, data: dict):
        for k, _ in typing.get_type_hints(self).items():
            setattr(self, k, data[k])

data = {
    "name": "Susan",
    "age": 18
}

user = User(data)
print(user.name, user.age)

# Output: Susan 18
def load_model_from_dict(self, data: dict):
    for key, value in data.items():
        self.__dict__[key] = value
    return self

It help returns your own model, with unforeseenable variables from the dict.它有助于返回您自己的 model,其中包含 dict 中不可预见的变量。

So I was hunting for a way to unmarshal any arbitrary type (think dict of dataclass, or dict of a dict of an array of dataclass) without a ton of custom deserialization code.所以我正在寻找一种方法来解组任何任意类型(想想数据类的字典,或者数据类数组的字典),而不需要大量的自定义反序列化代码。

This is my approach:这是我的方法:

import json
from dataclasses import dataclass, make_dataclass

from dataclasses_json import DataClassJsonMixin, dataclass_json


@dataclass_json
@dataclass
class Person:
    name: str


def unmarshal_json(data, t):
    Unmarhsal = make_dataclass('Unmarhsal', [('res', t)],
                               bases=(DataClassJsonMixin,))
    d = json.loads(data)
    out = Unmarhsal.from_dict({"res": d})
    return out.res


unmarshalled = unmarshal_json('{"1": {"name": "john"} }', dict[str, Person])
print(unmarshalled)

Prints: {'1': Person(name='john')}打印: {'1': Person(name='john')}

If you are looking for type safe deserialization of JSON or any complex dict into a python class I would highly recommend pydantic , which not only has a succinct API (does not require writing 'helper' boilerplate), can integrate with Python dataclasses but has static and runtime type validation of complex and nested data structures. If you are looking for type safe deserialization of JSON or any complex dict into a python class I would highly recommend pydantic , which not only has a succinct API (does not require writing 'helper' boilerplate), can integrate with Python dataclasses but has static复杂和嵌套数据结构的运行时类型验证。

Example usage:示例用法:

from pydantic import BaseModel
from datetime import datetime

class Item(BaseModel):
    field1: str | int           # union
    field2: int | None = None   # optional
    field3: str = 'default'     # default values

class User(BaseModel):
    name: str | None = None
    username: str
    created: datetime           # default type converters
    items: list[Item] = []      # nested complex types

data = {
    'name': 'Jane Doe',
    'username': 'user1',
    'created': '2020-12-31T23:59:00+10:00',
    'items': [
        {'field1': 1, 'field2': 2},
        {'field1': 'b'},
        {'field1': 'c', 'field3': 'override'}
    ]
}

user: User = User(**data)

For more details and features, check out pydantic's rational section in their documentation.有关更多详细信息和功能,请查看 pydantic 在其文档中的理性部分。

Using python 3.7, I find the following quite simple and effective.使用 python 3.7,我发现以下内容非常简单有效。 In this case, loading JSON from a file into a dictionary:在这种情况下,将 JSON 从文件加载到字典中:

class Characteristic:
    def __init__(self, characteristicName, characteristicUUID):
        self.characteristicName = characteristicName
        self.characteristicUUID = characteristicUUID


class Service:
    def __init__(self, serviceName, serviceUUID, characteristics):
        self.serviceName = serviceName
        self.serviceUUID = serviceUUID
        self.characteristics = characteristics

class Definitions:
    def __init__(self, services):
        self.services = []
        for service in services:
            self.services.append(Service(**service))


def main():
    parser = argparse.ArgumentParser(
        prog="BLEStructureGenerator",
        description="Taking in a JSON input file which lists all of the services, "
                    "characteristics and encoded properties. The encoding takes in "
                    "another optional template services and/or characteristics "
                    "file where the JSON file contents are applied to the templates.",
        epilog="Copyright Brown & Watson International"
    )

    parser.add_argument('definitionfile',
                        type=argparse.FileType('r', encoding='UTF-8'),
                        help="JSON file which contains the list of characteristics and "
                             "services in the required format")
    parser.add_argument('-s', '--services',
                        type=argparse.FileType('r', encoding='UTF-8'),
                        help="Services template file to be used for each service in the "
                             "JSON file list")
    parser.add_argument('-c', '--characteristics',
                        type=argparse.FileType('r', encoding='UTF-8'),
                        help="Characteristics template file to be used for each service in the "
                             "JSON file list")

    args = parser.parse_args()
    definition_dict = json.load(args.definitionfile)
    definitions = Definitions(**definition_dict)

Use the json module ( new in Python 2.6 ) or the simplejson module which is almost always installed.使用json模块Python 2.6 中的新模块)或几乎总是安装的simplejson模块。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM