用 Python 读写 JSON

原文: https://thepythonguru.com/reading-and-writing-json-in-python/


于 2020 年 1 月 7 日更新


JSON(JavaScript 对象表示法)是与语言无关的数据交换格式。 它是由道格拉斯·克罗克福德(Douglas Crockford)创建和推广的。 在短短的历史中,JSON 已成为事实上的跨网络数据传输标准。

JSON 是从 JavaScript 对象语法派生的基于文本的格式。 但是,它完全独立于 JavaScript,因此您无需知道任何 JavaScript 即可使用 JSON。

Web 应用通常使用 JSON 在客户端和服务器之间传输数据。 如果您使用的是 Web 服务,则很有可能默认情况下以 JSON 格式将数据返回给您。

在 JSON 诞生之前,XML 主要用于在客户端和服务器之间发送和接收数据。 XML 的问题在于它冗长,繁重且不容易解析。 但是,JSON 并非如此,您将很快看到。

以下是描述人的 XML 文档的示例。

  1. <?xml version="1.0" encoding="UTF-8" ?>
  2. <root>
  3. <firstName>John</firstName>
  4. <lastName>Smith</lastName>
  5. <isAlive>true</isAlive>
  6. <age>27</age>
  7. <address>
  8. <streetAddress>21 2nd Street</streetAddress>
  9. <city>New York</city>
  10. <state>NY</state>
  11. <postalCode>10021-3100</postalCode>
  12. </address>
  13. <phoneNumbers>
  14. <type>home</type>
  15. <number>212 555-1234</number>
  16. </phoneNumbers>
  17. <phoneNumbers>
  18. <type>office</type>
  19. <number>646 555-4567</number>
  20. </phoneNumbers>
  21. <phoneNumbers>
  22. <type>mobile</type>
  23. <number>123 456-7890</number>
  24. </phoneNumbers>
  25. <spouse />
  26. </root>

可以使用 JSON 表示相同的信息,如下所示:

  1. {
  2. "firstName": "John",
  3. "lastName": "Smith",
  4. "isAlive": true,
  5. "age": 27,
  6. "address": {
  7. "streetAddress": "21 2nd Street",
  8. "city": "New York",
  9. "state": "NY",
  10. "postalCode": "10021-3100"
  11. },
  12. "phoneNumbers": [
  13. {
  14. "type": "home",
  15. "number": "212 555-1234"
  16. },
  17. {
  18. "type": "office",
  19. "number": "646 555-4567"
  20. },
  21. {
  22. "type": "mobile",
  23. "number": "123 456-7890"
  24. }
  25. ],
  26. "children": [],
  27. "spouse": null
  28. }

我相信您会同意 JSON 副本更容易读写。

另外,请注意,JSON 格式与 Python 中的字典非常相似。

序列化和反序列化


序列化:将对象转换为适合通过网络传输或存储在文件或数据库中的特殊格式的过程称为序列化。

反序列化:与序列化相反。 它将序列化返回的特殊格式转换回可用的对象。

在 JSON 的情况下,当我们序列化对象时,实际上是将 Python 对象转换为 JSON 字符串,反序列化则通过其 JSON 字符串表示形式构建 Python 对象。

Python 提供了一个称为json的内置模块,用于对对象进行序列化和反序列化。 要使用json模块,请按以下步骤导入它:

  1. >>>
  2. >>> import json
  3. >>>

json模块主要提供以下用于序列化和反序列化的函数。

  1. dump(obj, fileobj)
  2. dumps(obj)
  3. load(fileobj)
  4. loads(s)

让我们从dump()函数开始。

使用dump()进行序列化


dump()函数用于序列化数据。 它需要一个 Python 对象,对其进行序列化,然后将输出(它是 JSON 字符串)写入对象之类的文件。

dump()函数的语法如下:

语法dump(obj, fp)

参数 描述
obj 要序列化的对象。
fp 一个类似文件的对象,将在其中写入序列化数据。

这是一个例子:

  1. >>>
  2. >>> import json
  3. >>>
  4. >>> person = {
  5. ... 'first_name': "John",
  6. ... "isAlive": True,
  7. ... "age": 27,
  8. ... "address": {
  9. ... "streetAddress": "21 2nd Street",
  10. ... "city": "New York",
  11. ... "state": "NY",
  12. ... "postalCode": "10021-3100"
  13. ... },
  14. ... "hasMortgage": None
  15. ... }
  16. >>>
  17. >>>
  18. >>> with open('person.json', 'w') as f: # writing JSON object
  19. ... json.dump(person, f)
  20. ...
  21. >>>
  22. >>>
  23. >>> open('person.json', 'r').read() # reading JSON object as string
  24. '{"hasMortgage": null, "isAlive": true, "age": 27, "address": {"state": "NY", "streetAddress": "21 2nd Street", "city": "New York", "postalCode": "10021-3100"}, "first_name": "John"}'
  25. >>>
  26. >>>
  27. >>> type(open('person.json', 'r').read())
  28. <class 'str'>
  29. >>>
  30. >>>

请注意,在序列化对象时,Python 的None类型将转换为 JSON 的null类型。

下表列出了序列化数据时类型之间的转换。

Python 类型 JSON 类型
dict object
listtuple array
int number
float number
str string
True true
False false
None null

当我们反序列化对象时,JSON 类型将转换回其等效的 Python 类型。 下表中描述了此操作:

JSON 类型 Python 类型
object dict
array list
string str
number (int) int
number (real) float
true True
false False
null None

这是另一个序列化两个人的列表的示例:

  1. >>>
  2. >>>
  3. >>> persons = \
  4. ... [
  5. ... {
  6. ... 'first_name': "John",
  7. ... "isAlive": True,
  8. ... "age": 27,
  9. ... "address": {
  10. ... "streetAddress": "21 2nd Street",
  11. ... "city": "New York",
  12. ... "state": "NY",
  13. ... "postalCode": "10021-3100"
  14. ... },
  15. ... "hasMortgage": None,
  16. ... },
  17. ... {
  18. ... 'first_name': "Bob",
  19. ... "isAlive": True,
  20. ... "age": 32,
  21. ... "address": {
  22. ... "streetAddress": "2428 O Conner Street",
  23. ... "city": " Ocean Springs",
  24. ... "state": "Mississippi",
  25. ... "postalCode": "20031-9110"
  26. ... },
  27. ... "hasMortgage": True,
  28. ... }
  29. ...
  30. ... ]
  31. >>>
  32. >>> with open('person_list.json', 'w') as f:
  33. ... json.dump(persons, f)
  34. ...
  35. >>>
  36. >>>
  37. >>> open('person_list.json', 'r').read()
  38. '[{"hasMortgage": null, "isAlive": true, "age": 27, "address": {"state": "NY", "streetAddress": "21 2nd Street", "city": "New York", "postalCode": "10021-3100"}, "first_name": "John"}, {"hasMortgage": true, "isAlive": true, "age": 32, "address": {"state": "Mississippi", "streetAddress": "2428 O Conner Street", "city": " Ocean Springs", "postalCode": "20031-9110"}, "first_name": "Bob"}]'
  39. >>>
  40. >>>

现在,我们的 Python 对象已序列化到文件。 要将其反序列化回 Python 对象,我们使用load()函数。

load()反序列化


load()函数从类似于对象的文件中反序列化 JSON 对象并返回它。

其语法如下:

  1. load(fp) -> a Python object
参数 描述
fp 从中读取 JSON 字符串的类似文件的对象。

Here is an example:

  1. >>>
  2. >>> with open('person.json', 'r') as f:
  3. ... person = json.load(f)
  4. ...
  5. >>>
  6. >>> type(person) # notice the type of data returned by load()
  7. <class 'dict'>
  8. >>>
  9. >>> person
  10. {'age': 27, 'isAlive': True, 'hasMortgage': None, 'address': {'state': 'NY', 'streetAddress': '21 2nd Street', 'city': 'New York', 'postalCode': '10021-3100'}, 'first_name': 'John'}
  11. >>>
  12. >>>

使用dumps()loads()进行序列化和反序列化


dumps()函数的工作原理与dump()完全相同,但是它不是将输出发送到类似文件的对象,而是将输出作为字符串返回。

同样,loads()函数与load()相同,但是它不是从文件反序列化 JSON 字符串,而是从字符串反序列化。

这里有些例子:

  1. >>>
  2. >>> person = {
  3. ... 'first_name': "John",
  4. ... "isAlive": True,
  5. ... "age": 27,
  6. ... "address": {
  7. ... "streetAddress": "21 2nd Street",
  8. ... "city": "New York",
  9. ... "state": "NY",
  10. ... "postalCode": "10021-3100"
  11. ... },
  12. ... "hasMortgage": None
  13. ... }
  14. >>>
  15. >>> data = json.dumps(person) # serialize
  16. >>>
  17. >>> data
  18. '{"hasMortgage": null, "isAlive": true, "age": 27, "address": {"state": "NY", "streetAddress": "21 2nd Street", "city": "New York", "postalCode": "10021-3100"}, "first_name": "John"}'
  19. >>>
  20. >>>
  21. >>> person = json.loads(data) # deserialize from string
  22. >>>
  23. >>> type(person)
  24. <class 'dict'>
  25. >>>
  26. >>> person
  27. {'age': 27, 'isAlive': True, 'hasMortgage': None, 'address': {'state': 'NY', 'streetAddress': '21 2nd Street', 'city': 'New York', 'postalCode': '10021-3100'}, 'first_name': 'John'}
  28. >>>
  29. >>>

注意

由于字典不保留元素的顺序,因此获取键的顺序可能会有所不同。

自定义序列化器


以下是一些可选的关键字参数,可以将这些参数传递给dumpsdump()函数以自定义串行器。

参数 描述
indent 一个正整数,用于确定每个级别的键值对的缩进量。 如果您具有深层嵌套的数据结构,则indent参数可以方便地美化输出。 indent的默认值为None
sort_keys 布尔值标志(如果设置为True)将返回按键排序的 JSON 字符串,而不是随机排序的。 其默认值为False
skipkeys JSON 格式期望键为字符串,如果您尝试使用无法转换为字符串的类型(如元组),则会引发TypeError异常。 为防止引发异常并跳过非字符串键,请将skipkeys参数设置为True
separators 它是指形式为(item_separator, key_separator)的元组。 item_separator是一个字符串,用于分隔列表中的项目。 key_separator也是一个字符串,用于分隔字典中的键和值。 默认情况下,separators设置为(',', ': ')

以下是一些示例,演示了如何在操作中使用这些参数:

示例 1 :使用indent

  1. >>>
  2. >>> print(json.dumps(person)) # without indent
  3. {"age": 27, "isAlive": true, "hasMortgage": null, "address": {"state": "NY", "streetAddress": "21 2nd Street", "city": "New York", "postalCode": "10021-3100"}, "first_name": "John"}
  4. >>>
  5. >>>
  6. >>> print(json.dumps(person, indent=4)) # with 4 levels of indentation
  7. {
  8. "age": 27,
  9. "isAlive": true,
  10. "hasMortgage": null,
  11. "address": {
  12. "state": "NY",
  13. "streetAddress": "21 2nd Street",
  14. "city": "New York",
  15. "postalCode": "10021-3100"
  16. },
  17. "first_name": "John"
  18. }
  19. >>>
  20. >>>

请记住,增加缩进量也会增加数据的大小。 因此,请勿在生产环境中使用indent

示例 2 :使用sort_keys

  1. >>>
  2. >>> print(json.dumps(person, indent=4)) # print JSON string in random order
  3. {
  4. "address": {
  5. "state": "NY",
  6. "postalCode": "10021-3100",
  7. "city": "New York",
  8. "streetAddress": "21 2nd Street"
  9. },
  10. "hasMortgage": null,
  11. "first_name": "John",
  12. "isAlive": true,
  13. "age": 27
  14. }
  15. >>>
  16. >>>
  17. >>> print(json.dumps(person, indent=4, sort_keys=True)) # print JSON string in order by keys
  18. {
  19. "address": {
  20. "city": "New York",
  21. "postalCode": "10021-3100",
  22. "state": "NY",
  23. "streetAddress": "21 2nd Street"
  24. },
  25. "age": 27,
  26. "first_name": "John",
  27. "hasMortgage": null,
  28. "isAlive": true
  29. }
  30. >>>
  31. >>>

示例 3 :使用skipkeys

  1. >>>
  2. >>> data = {'one': 1, 'two': 2, (1,2): 3}
  3. >>>
  4. >>> json.dumps(data, indent=4)
  5. Traceback (most recent call last):
  6. ...
  7. TypeError: key (1, 2) is not a string
  8. >>>
  9. >>>

在这种情况下,键(1,2)无法转换为字符串,因此会引发TypeError异常。 为防止引发异常并跳过非字符串键,请使用skipkeys参数。

  1. >>>
  2. >>> print(json.dumps(data, indent=4, skipkeys=True))
  3. {
  4. "two": 2,
  5. "one": 1
  6. }
  7. >>>
  8. >>>

示例 4 :使用separators

  1. >>>
  2. >>> employee = {
  3. ... 'first_name': "Tom",
  4. ... "designation": 'CEO',
  5. ... "Salary": '2000000',
  6. ... "age": 35,
  7. ... "cars": ['chevy cavalier', 'ford taurus', 'tesla model x']
  8. ... }
  9. >>>
  10. >>>
  11. >>> print(json.dumps(employee, indent=4, skipkeys=True,))
  12. {
  13. "designation": "CEO",
  14. "age": 35,
  15. "cars": [
  16. "chevy cavalier",
  17. "ford taurus",
  18. "tesla model x"
  19. ],
  20. "Salary": "2000000",
  21. "first_name": "Tom"
  22. }
  23. >>>
  24. >>>

以上输出中需要注意三件事:

  1. 每个键值对使用逗号(,)分隔。
  2. 数组中的项(例如cars)也使用逗号(,)分隔。
  3. JSON 对象的键使用': '与值分开(即冒号后跟一个空格)。

前两种情况下的分隔符使用item_separator字符串控制,最后一种情况下使用key_separator控制。 以下示例将item_separatorkey_separator分别更改为竖线(|)和破折号(-)字符

  1. >>>
  2. >>> print(json.dumps(employee, indent=4, skipkeys=True, separators=('|', '-')))
  3. {
  4. "designation"-"CEO"|
  5. "age"-35|
  6. "cars"-[
  7. "chevy cavalier"|
  8. "ford taurus"|
  9. "tesla model x"
  10. ]|
  11. "Salary"-"2000000"|
  12. "first_name"-"Tom"
  13. }
  14. >>>
  15. >>>

现在您知道separators的工作原理,我们可以通过从item_separator字符串中删除空格字符来使输出更紧凑。 例如:

  1. >>>
  2. >>> print(json.dumps(employee, indent=4, skipkeys=True, separators=(',', ':')))
  3. {
  4. "designation":"CEO",
  5. "age":35,
  6. "cars":[
  7. "chevy cavalier",
  8. "ford taurus",
  9. "tesla model x"
  10. ],
  11. "Salary":"2000000",
  12. "first_name":"Tom"
  13. }
  14. >>>
  15. >>>

序列化自定义对象


默认情况下,json模块仅允许我们序列化以下基本类型:

  • int
  • float
  • str
  • bool
  • list
  • tuple
  • dict
  • None

如果您尝试序列化或反序列化自定义对象或任何其他内置类型,将引发TypeError异常。 例如:

  1. >>>
  2. >>> from datetime import datetime
  3. >>>
  4. >>> now = datetime.now()
  5. >>>
  6. >>> now
  7. datetime.datetime(2018, 9, 28, 22, 16, 46, 16944)
  8. >>>
  9. >>> d = {'name': 'bob', 'dob': now}
  10. >>>
  11. >>> json.dumps(d)
  12. Traceback (most recent call last):
  13. ...
  14. TypeError: datetime.datetime(2018, 9, 28, 22, 7, 0, 622242) is not JSON serializable
  15. >>>
  16. >>>
  17. >>>
  18. >>>
  19. >>> class Employee:
  20. ...
  21. ... def __init__(self, name):
  22. ... self.name = name
  23. ...
  24. >>>
  25. >>> e = Employee('John')
  26. >>>
  27. >>> e
  28. <__main__.Employee object at 0x7f20c82ee4e0>
  29. >>>
  30. >>>
  31. >>> json.dumps(e)
  32. Traceback (most recent call last):
  33. ...
  34. TypeError: <__main__.Employee object at 0x7f20c82ee4e0> is not JSON serializable
  35. >>>
  36. >>>

要序列化自定义对象或内置类型,我们必须创建自己的序列化函数。

  1. def serialize_objects(obj):
  2. # serialize datetime object
  3. if isinstance(obj, datetime):
  4. return {
  5. '__class__': datetime.__name__,
  6. '__value__': str(obj)
  7. }
  8. # serialize Employee object
  9. #
  10. # if isinstance(obj, Employee):
  11. # return {
  12. # '__class__': 'Employee',
  13. # '__value__': obj.name
  14. # }
  15. raise TypeError(str(obj) + ' is not JSON serializable')

以下是有关该函数的一些注意事项。

  1. 该函数采用一个名为obj的参数。

  2. 在第 5 行中,我们使用isinstance()函数检查对象的类型。 如果您的函数仅序列化单个类型,则严格地不必检查类型,但是可以轻松添加其他类型的序列化。

  3. 在 6-9 行中,我们使用两个键创建一个字典:__class____value____class__键存储该类的原始名称,并将用于反序列化数据。 __value__键存储对象的值,在这种情况下,我们仅需使用内置的str()函数将datetime.datetime对象转换为其字符串表示形式。

  4. 在第 18 行中,我们引发了TypeError异常。 这是必要的,否则我们的序列化函数不会为无法序列化的对象报告错误。

我们的序列化函数现在可以序列化datetime.datetime对象。

下一个问题是-我们如何将自定义序列化函数传递给dumps()dump()

我们可以使用default关键字参数将自定义序列化函数传递给dumps()dump()。 这是一个例子:

  1. >>>
  2. >>> def serialize_objects(obj):
  3. ... if isinstance(obj, datetime):
  4. ... return {
  5. ... '__class__': datetime.__name__,
  6. ... '__value__': str(obj)
  7. ... }
  8. ... raise TypeError(str(obj) + ' is not JSON serializable')
  9. ...
  10. >>>
  11. >>> employee = {
  12. ... 'first_name': "Mike",
  13. ... "designation": 'Manager',
  14. ... "doj": datetime(year=2016, month=5, day=2), # date of joining
  15. ... }
  16. >>>
  17. >>>
  18. >>> emp_json = json.dumps(employee, indent=4, default=serialize_objects)
  19. >>>
  20. >>>
  21. >>> print(emp_json)
  22. {
  23. "designation": "Manager",
  24. "doj": {
  25. "__value__": "2016-05-02 00:00:00",
  26. "__class__": "datetime"
  27. },
  28. "first_name": "Mike"
  29. }
  30. >>>
  31. >>>

注意datetime.datetime对象如何被序列化为带有两个键的字典。

重要的是要注意,将仅调用serialize_objects()函数来序列化不是 Python 基本类型之一的对象。

现在,我们已经成功地序列化了datetime.datetime对象。 让我们看看如果尝试反序列化会发生什么。

  1. >>>
  2. >>> emp_dict = json.loads(emp_json)
  3. >>>
  4. >>> type(emp_dict)
  5. <class 'dict'>
  6. >>>
  7. >>> emp_dict
  8. {'designation': 'Manager', 'doj': {'__value__': '2016-05-02 00:00:00', '__class__': 'datetime'}, 'first_name': 'Mike'}
  9. >>>
  10. >>> emp_dict['doj']
  11. {'__value__': '2016-05-02 00:00:00', '__class__': 'datetime'}
  12. >>>
  13. >>>

请注意,doj键的值作为字典而不是datetime.datetime对象返回。

发生这种情况是因为loads()函数对首先将datetime.datetime对象序列化的serialize_objects()函数一无所知。

我们需要的是serialize_objects()函数的反面-该函数接受字典对象,检查__class__键的存在,并根据__value__键中存储的字符串表示形式构建datetime.datetime对象。

  1. def deserialize_objects(obj):
  2. if '__class__' in obj:
  3. if obj['__class__'] == 'datetime':
  4. return datetime.strptime(obj['__value__'], "%Y-%m-%d %H:%M:%S")
  5. # if obj['__class__'] == 'Employee':
  6. # return Employee(obj['__value__'])
  7. return obj

这里唯一需要注意的是,我们正在使用datetime.strptime函数将日期时间字符串转换为datetime.datetime对象。

要将自定义反序列化函数传递给loads()方法,我们使用object_hook关键字参数。

  1. >>>
  2. >>> def deserialize_objects(obj):
  3. ... if '__class__' in obj:
  4. ... if obj['__class__'] == 'datetime':
  5. ... return datetime.strptime(obj['__value__'], "%Y-%m-%d %H:%M:%S")
  6. ... # if obj['__class__'] == 'Employee':
  7. ... # return Employee(obj['__value__'])
  8. ... return obj
  9. ...
  10. >>>
  11. >>>
  12. >>> emp_dict = json.loads(emp_json, object_hook=deserialize_objects)
  13. >>>
  14. >>> emp_dict
  15. {'designation': 'Manager', 'doj': datetime.datetime(2016, 5, 2, 0, 0), 'first_name': 'Mike'}
  16. >>>
  17. >>> emp_dict['doj']
  18. datetime.datetime(2016, 5, 2, 0, 0)
  19. >>>
  20. >>>

不出所料,这次doj键的值是datetime.datetime对象而不是字典。