如何将JSON数据转换为Python对象

How to convert JSON data into a Python object

我想使用python将JSON数据转换为python对象。

我从Facebook API接收JSON数据对象,我想将其存储在我的数据库中。

我在Django(python)中的当前视图(request.POST包含json):

1
2
3
4
5
response = request.POST
user = FbApiUser(user_id = response['id'])
user.name = response['name']
user.username = response['username']
user.save()
  • 这很好,但是如何处理复杂的JSON数据对象呢?

  • 如果我能以某种方式将这个JSON对象转换成一个易于使用的Python对象,那不是更好吗?


您可以使用namedtupleobject_hook在一行中完成:

1
2
3
4
5
6
7
8
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

或者,为了方便地重用:

1
2
3
4
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)

如果您希望它处理的键不是好的属性名,请检查namedtuplerename参数。


查看json模块文档中标题为专门化JSON对象解码的部分。您可以使用它将JSON对象解码为特定的Python类型。

下面是一个例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
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模块访问字典中的数据,请执行以下操作:

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

就像一本普通字典。


这不是代码高尔夫,但这里是我的最短技巧,使用types.SimpleNamespace作为JSON对象的容器。

与领先的namedtuple解决方案相比,它是:

  • 可能更快/更小,因为它不为每个对象创建类
  • 更短的
  • 没有rename选项,可能对不是有效标识符的密钥有相同的限制(在封面下使用setattr)

例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
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)


你可以试试这个:

1
2
3
4
5
6
7
8
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)

只需创建一个新对象,并将参数作为映射传递。


这里有一个快速而肮脏的JSON泡菜替代品

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
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()


对于复杂对象,可以使用json pickle

Python library for serializing any arbitrary object graph into JSON.
It can take almost any Python object and turn the object into JSON.
Additionally, it can reconstitute the object back into Python.


我编写了一个称为any2any的小型(反)序列化框架,它有助于在两个Python类型之间进行复杂的转换。

在您的例子中,我猜您希望从字典(使用json.loads获得)转换为复杂对象response.education ; response.name,使用嵌套结构response.education.id等…所以这就是这个框架的目的。文档还不是很好,但是通过使用any2any.simple.MappingToObject,您应该能够非常容易地做到这一点。请询问您是否需要帮助。


如果您使用的是python 3.5+,则可以使用jsons对普通的旧python对象进行序列化和反序列化:

1
2
3
4
5
6
7
8
9
10
11
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的遗产,以获得更优雅的风格:

1
user = FbApiUser.from_json(response)

如果类由python默认类型(如字符串、整数、列表、日期时间等)组成,则这些示例可以工作。但是,jsonslib需要自定义类型的类型提示。


因为没有人提供和我一样的答案,所以我要把它贴在这里。

这是一个健壮的类,可以很容易地在json strdict之间来回转换,我从我的答案中复制到另一个问题:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
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)

如果您使用的是python 3.6+,则可以使用棉花糖数据类。与上面列出的所有解决方案相反,它既简单又类型安全:

1
2
3
4
5
6
7
from marshmallow_dataclass import dataclass

@dataclass
class User:
    name: str

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

稍微修改@ds response,从文件加载:

1
2
3
4
5
6
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('
'
, '')
def json2obj(file_name): return json.loads(load_data(file_name), object_hook=_json_object_hook)

一件事:这不能加载前面有数字的项目。这样地:

1
2
3
4
5
6
{
 "1_first_item": {
   "A":"1",
   "B":"2"
  }
}

因为"1_first_item"不是有效的python字段名。


在寻找解决方案时,我偶然发现了这个博客帖子:https://blog.mothege.net/2016/11/12/json-deserialization-of-nested-objects/

它使用与前面答案中所述相同的技术,但使用了装饰器。我发现另一件有用的事情是它在反序列化结束时返回一个类型化的对象

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
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)

用途:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
@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)

Python 3.x

据我所知,我能找到的最好的方法就是这个。
请注意,此代码也会处理set()。
这种方法是通用的,只需要类的扩展(在第二个示例中)。
请注意,我只是处理文件,但是很容易根据您的喜好修改行为。

然而,这是一个编解码器。

再多做一点工作,你就可以用其他方法构造你的类了。我假设一个默认的构造函数来实例它,然后更新类dict。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
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)

编辑

通过更多的研究,我发现了一种不需要超类注册方法调用的通用方法,使用元类

10


改进洛瓦萨的好答案。

如果您使用的是python 3.6+,则可以使用:pip install marshmallow-enumpip install marshmallow-dataclass

它简单,类型安全。

可以在字符串JSON中转换类,反之亦然:

从对象到字符串JSON:

1
2
3
4
    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到对象:

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

类定义:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
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

稍微扩展一下DS的答案,如果您需要对象是可变的(name-duple不是可变的),您可以使用recordclass库而不是name-duple:

1
2
3
4
5
6
7
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()))

然后可以很容易地使用simplejson将修改后的对象转换回json:

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

使用几乎总是安装的json模块(在python 2.6中是新的)或simplejson模块。