How to convert JSON data into a Python object
我想使用python将JSON数据转换为python对象。
我从Facebook API接收JSON数据对象,我想将其存储在我的数据库中。
我在Django(python)中的当前视图(
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对象,那不是更好吗?
您可以使用
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) |
如果您希望它处理的键不是好的属性名,请检查
查看
下面是一个例子:
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'] |
就像一本普通字典。
这不是代码高尔夫,但这里是我的最短技巧,使用
与领先的
- 可能更快/更小,因为它不为每个对象创建类
- 更短的
- 没有
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类型之间进行复杂的转换。
在您的例子中,我猜您希望从字典(使用
如果您使用的是python 3.5+,则可以使用
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() |
您还可以让
1 | user = FbApiUser.from_json(response) |
如果类由python默认类型(如字符串、整数、列表、日期时间等)组成,则这些示例可以工作。但是,
因为没有人提供和我一样的答案,所以我要把它贴在这里。
这是一个健壮的类,可以很容易地在json
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+,则可以使用:
它简单,类型安全。
可以在字符串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) |
使用几乎总是安装的