MK
摩柯社区 - 一个极简的技术知识社区
AI 面试

Python使用json.dumps序列化数据

2022-08-292.9k 阅读

理解Python中的JSON序列化

在Python的编程世界里,处理数据序列化与反序列化是一项非常重要的操作。其中,JSON(JavaScript Object Notation)作为一种轻量级的数据交换格式,被广泛应用于各种应用场景,如Web应用的数据传输、配置文件存储等。Python标准库中的json模块提供了对JSON数据处理的支持,其中json.dumps函数用于将Python数据结构转换为JSON格式的字符串,即序列化操作。

JSON序列化基础概念

序列化,简单来说,就是将程序中的数据结构(如Python中的字典、列表等)转换为一种可以存储或传输的格式的过程。反序列化则是相反的操作,将存储或传输的格式数据转换回程序能够使用的数据结构。在网络通信或数据存储场景中,由于不同系统或语言的数据表示方式可能不同,因此需要一种通用的数据交换格式。JSON以其简洁、易于阅读和编写的特点,成为了这种通用格式的首选之一。

Python的json模块使得处理JSON数据变得非常便捷。json.dumps函数就是负责将Python对象转换为JSON字符串。例如,Python中的字典对象可以轻松地转换为JSON格式的字符串。

import json

data = {'name': 'John', 'age': 30, 'city': 'New York'}
json_str = json.dumps(data)
print(json_str)

上述代码中,首先导入了json模块,然后定义了一个字典data。接着使用json.dumps函数将字典data转换为JSON格式的字符串,并将结果赋值给json_str变量,最后打印出这个JSON字符串。运行这段代码,你会得到类似{"name": "John", "age": 30, "city": "New York"}的输出。

JSON数据类型与Python数据类型的映射

在使用json.dumps进行序列化时,需要注意Python数据类型与JSON数据类型之间的映射关系。

JSON数据类型Python数据类型
objectdict
arraylist
stringstr
number(整数)int
number(浮点数)float
trueTrue
falseFalse
nullNone

这种映射关系保证了Python数据能够正确地转换为JSON格式。例如,Python中的列表会被转换为JSON中的数组:

import json

my_list = [1, 2, 3, 'four', {'key': 'value'}]
json_list = json.dumps(my_list)
print(json_list)

在上述代码中,定义了一个包含多种数据类型的列表my_list,通过json.dumps函数将其转换为JSON字符串并打印。输出结果为[1, 2, 3, "four", {"key": "value"}],可以看到列表中的数据类型按照映射关系正确地转换为了JSON格式。

json.dumps函数的参数详解

json.dumps函数除了接受要序列化的Python对象作为必选参数外,还提供了多个可选参数,这些参数可以帮助我们更灵活地控制序列化的过程。

sort_keys参数

sort_keys参数用于指定是否对字典的键进行排序。默认值为False,即不排序。当设置为True时,json.dumps会按照字典键的字母顺序对字典进行排序后再进行序列化。

import json

data = {'city': 'New York', 'name': 'John', 'age': 30}
sorted_json = json.dumps(data, sort_keys=True)
unsorted_json = json.dumps(data)

print('Sorted JSON:', sorted_json)
print('Unsorted JSON:', unsorted_json)

运行上述代码,你会发现sorted_json中字典的键按照字母顺序排列,而unsorted_json则保持原始顺序。这在需要固定数据格式以便于比较或缓存等场景中非常有用。

indent参数

indent参数用于控制生成的JSON字符串的缩进格式,使其更易读。当indent为整数时,它表示缩进的空格数;当indent为字符串时,它表示使用该字符串作为缩进。如果indentNone(默认值),则生成的JSON字符串是紧凑格式,没有缩进。

import json

data = {'name': 'John', 'age': 30, 'city': 'New York', 'hobbies': ['reading', 'traveling']}
compact_json = json.dumps(data)
indented_json = json.dumps(data, indent=4)

print('Compact JSON:', compact_json)
print('Indented JSON:', indented_json)

在上述代码中,compact_json是紧凑格式的JSON字符串,而indented_json使用了4个空格进行缩进,更加清晰易读。这在处理复杂的JSON数据结构,或者需要将JSON数据输出到日志文件、配置文件等场景中,方便人工查看和编辑。

separators参数

separators参数用于指定JSON字符串中分隔项的字符。它是一个包含两个元素的元组,第一个元素用于分隔字典中的键值对,第二个元素用于分隔数组中的元素。默认值为(', ', ': '),即使用逗号和空格分隔元素,冒号和空格分隔键值对。

import json

data = {'name': 'John', 'age': 30}
default_separators = json.dumps(data)
custom_separators = json.dumps(data, separators=(',', ':'))

print('Default Separators:', default_separators)
print('Custom Separators:', custom_separators)

在上述代码中,default_separators使用默认的分隔符,而custom_separators使用自定义的分隔符,使得生成的JSON字符串更加紧凑。这在对数据大小有严格要求,需要尽量减少数据体积的场景中很有用,比如在网络传输大量JSON数据时。

ensure_ascii参数

ensure_ascii参数用于控制非ASCII字符的显示方式。默认值为True,即所有非ASCII字符都会被转义为\uXXXX的形式。当设置为False时,非ASCII字符会以其原始形式显示。

import json

data = {'name': '张三', 'age': 30}
escaped_json = json.dumps(data)
unescaped_json = json.dumps(data, ensure_ascii=False)

print('Escaped JSON:', escaped_json)
print('Unescaped JSON:', unescaped_json)

在上述代码中,escaped_json中的中文字符被转义,而unescaped_json中的中文字符以原始形式显示。如果你的应用场景需要处理包含非ASCII字符的数据,并且希望这些字符能够直接显示而不是被转义,就可以将ensure_ascii设置为False

处理复杂数据结构的序列化

在实际应用中,我们经常会遇到比简单字典和列表更复杂的数据结构,如嵌套的字典、列表,自定义类的实例等。json.dumps在处理这些复杂结构时,需要一些额外的处理方式。

嵌套数据结构的序列化

当数据结构中包含嵌套的字典和列表时,json.dumps能够递归地处理这些嵌套结构。

import json

data = {
    'name': 'John',
    'age': 30,
    'address': {
       'street': '123 Main St',
        'city': 'Anytown',
        'zip': '12345'
    },
    'phone_numbers': ['123-456-7890', '098-765-4321'],
    'friends': [
        {'name': 'Jane', 'age': 28},
        {'name': 'Bob', 'age': 32}
    ]
}

json_data = json.dumps(data, indent=4)
print(json_data)

上述代码定义了一个复杂的嵌套数据结构data,其中包含字典、列表以及嵌套的字典和列表。通过json.dumps函数并设置适当的缩进,我们可以将其转换为清晰易读的JSON字符串。输出结果展示了json.dumps对嵌套结构的正确处理,每个层级的结构都被正确地转换为JSON格式。

自定义类实例的序列化

默认情况下,json.dumps不能直接序列化自定义类的实例,因为它不知道如何将自定义类的属性转换为JSON数据。但是,我们可以通过在类中定义__dict__属性或者使用json.JSONEncoder的子类来实现自定义类实例的序列化。

import json


class Person:
    def __init__(self, name, age):
        self.name = name
        self.age = age


person = Person('John', 30)

# 方法一:使用__dict__属性
try:
    json_person = json.dumps(person.__dict__)
    print(json_person)
except TypeError:
    print('直接使用__dict__属性失败')

# 方法二:自定义JSONEncoder子类
class PersonEncoder(json.JSONEncoder):
    def default(self, o):
        if isinstance(o, Person):
            return {'name': o.name, 'age': o.age}
        return super().default(o)


try:
    json_person = json.dumps(person, cls=PersonEncoder)
    print(json_person)
except TypeError:
    print('使用自定义JSONEncoder子类失败')

在上述代码中,首先定义了一个Person类。方法一是尝试直接使用person.__dict__属性进行序列化,这种方式在简单情况下可行,但对于更复杂的类结构可能会有问题。方法二是通过定义一个继承自json.JSONEncoderPersonEncoder类,并重写default方法,在default方法中,判断对象是否为Person类的实例,如果是,则将其转换为字典形式进行序列化。最后使用json.dumps并指定cls参数为PersonEncoder来实现自定义类实例的序列化。

序列化过程中的常见问题及解决方法

在使用json.dumps进行序列化时,可能会遇到一些问题,下面我们来分析这些常见问题及其解决方法。

不支持的数据类型

json.dumps只能处理特定的Python数据类型,如前面提到的与JSON数据类型有映射关系的那些类型。如果数据结构中包含不支持的数据类型,如datetimenumpy.ndarray等,会抛出TypeError

import json
import datetime

data = {'timestamp': datetime.datetime.now()}
try:
    json_data = json.dumps(data)
except TypeError as e:
    print(f'序列化失败: {e}')

上述代码中,data字典中包含一个datetime类型的对象,当尝试使用json.dumps进行序列化时,会抛出TypeError

解决方法是将不支持的数据类型转换为支持的类型。对于datetime类型,可以将其转换为字符串:

import json
import datetime

data = {'timestamp': datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S')}
json_data = json.dumps(data)
print(json_data)

在上述代码中,通过strftime方法将datetime对象转换为字符串,这样就可以成功进行序列化。

循环引用问题

当数据结构中存在循环引用时,json.dumps也会抛出RecursionError。例如:

import json

a = []
b = {'a': a}
a.append(b)

try:
    json_data = json.dumps(a)
except RecursionError as e:
    print(f'序列化失败: {e}')

在上述代码中,a列表和b字典之间形成了循环引用,a包含b,而b又包含指向a的引用。当尝试序列化a时,会因为无限递归而抛出RecursionError

解决循环引用问题通常需要手动打破循环引用或者使用专门处理循环引用的数据结构。一种简单的方法是在序列化之前检测并处理循环引用:

import json


def remove_circular_refs(data, memo=None):
    if memo is None:
        memo = set()
    if isinstance(data, dict):
        new_data = {}
        for key, value in data.items():
            if id(value) in memo:
                new_data[key] = '<CIRCULAR_REF>'
            else:
                memo.add(id(value))
                new_data[key] = remove_circular_refs(value, memo)
        return new_data
    elif isinstance(data, list):
        new_data = []
        for item in data:
            if id(item) in memo:
                new_data.append('<CIRCULAR_REF>')
            else:
                memo.add(id(item))
                new_data.append(remove_circular_refs(item, memo))
        return new_data
    else:
        return data


a = []
b = {'a': a}
a.append(b)

new_a = remove_circular_refs(a)
json_data = json.dumps(new_a)
print(json_data)

在上述代码中,定义了remove_circular_refs函数来检测并处理循环引用。该函数使用一个memo集合来记录已经处理过的对象的ID,如果遇到已经处理过的对象(即循环引用),则将其替换为<CIRCULAR_REF>字符串。这样就可以避免循环引用导致的序列化失败。

在实际项目中的应用场景

Web应用中的数据传输

在Web开发中,JSON是一种常用的数据交换格式。后端使用Python编写的API通常会将数据以JSON格式返回给前端。例如,使用Flask框架开发的API:

from flask import Flask, jsonify
import json

app = Flask(__name__)


@app.route('/data')
def get_data():
    data = {'name': 'John', 'age': 30, 'city': 'New York'}
    # jsonify内部使用了json.dumps进行序列化
    return jsonify(data)


if __name__ == '__main__':
    app.run()

在上述代码中,Flask的jsonify函数实际上是对json.dumps的封装,将Python字典转换为JSON格式的响应返回给前端。前端可以轻松地解析这个JSON数据并进行展示。

配置文件的存储与读取

许多应用程序使用JSON格式的配置文件来存储各种配置信息。Python可以使用json.dumps将配置数据结构转换为JSON字符串并写入文件,也可以使用json.loads(反序列化函数)从文件中读取并转换回Python数据结构。

import json

config = {
    'database': {
        'host': 'localhost',
        'port': 3306,
        'user': 'root',
        'password': 'password'
    },
    'logging': {
        'level': 'INFO',
        'file': 'app.log'
    }
}

# 将配置数据写入文件
with open('config.json', 'w') as f:
    json.dump(config, f, indent=4)

# 从文件中读取配置数据
with open('config.json', 'r') as f:
    loaded_config = json.load(f)

print(loaded_config)

在上述代码中,首先定义了一个配置数据结构config,然后使用json.dumpjson.dumpjson.dumps结合文件写入操作的简化函数)将其写入config.json文件,并设置了缩进格式。接着使用json.load从文件中读取数据并转换回Python数据结构。这样可以方便地管理和维护应用程序的配置信息。

数据缓存与持久化

在一些需要缓存数据或者进行数据持久化的场景中,JSON也是常用的格式。例如,将Python中的一些中间计算结果以JSON格式存储在文件中,下次使用时可以直接读取并反序列化,避免重复计算。

import json


def expensive_computation():
    # 模拟一些复杂的计算
    result = {'key': 'value', 'data': [1, 2, 3, 4, 5]}
    return result


try:
    with open('cache.json', 'r') as f:
        cached_result = json.load(f)
except FileNotFoundError:
    cached_result = expensive_computation()
    with open('cache.json', 'w') as f:
        json.dump(cached_result, f)

print(cached_result)

在上述代码中,expensive_computation函数模拟了一个复杂的计算过程。首先尝试从cache.json文件中读取缓存结果,如果文件不存在,则执行计算并将结果缓存到文件中。通过这种方式,使用json.dumps(这里通过json.dump间接使用)和json.loads(通过json.load间接使用)实现了数据的缓存与持久化。

与其他序列化格式的比较

虽然JSON在数据交换和存储方面有很多优点,但在某些场景下,其他序列化格式可能更合适。下面我们将JSON与其他常见的序列化格式进行比较。

与XML的比较

  1. 可读性与简洁性:JSON的格式更加简洁,易于阅读和编写,它使用简洁的键值对和数组表示方式。而XML使用标签来表示数据结构,相对来说更加冗长。例如,同样表示一个简单的人员信息:
    • JSON: {"name": "John", "age": 30}
    • XML: <person><name>John</name><age>30</age></person>
  2. 数据体积:由于JSON格式简洁,在表示相同数据时,通常数据体积比XML小,这在网络传输和存储方面具有优势。
  3. 解析与生成效率:在Python中,json模块解析和生成JSON数据的速度通常比解析和生成XML数据快。对于XML,通常需要使用更复杂的库,如xml.etree.ElementTreelxml,这些库虽然功能强大,但使用起来相对复杂,并且在性能上可能不如json模块。

与Pickle的比较

  1. 通用性:JSON是一种跨语言的数据交换格式,不同语言都有对JSON的支持,适合在不同系统间进行数据传输。而Pickle是Python特有的序列化格式,只能在Python环境中使用,不适合跨语言场景。
  2. 安全性:Pickle在反序列化时存在安全风险,如果反序列化的数据来自不可信的源,可能会导致代码执行等安全问题。而JSON相对来说更加安全,因为它的语法简单,不容易被恶意利用。
  3. 数据类型支持:Pickle可以序列化几乎所有的Python对象,包括自定义类的实例、函数等复杂对象。而JSON只能序列化特定的Python数据类型,如字典、列表、字符串、数字等,对于自定义类等复杂对象需要额外处理。

与Msgpack的比较

  1. 数据体积:Msgpack是一种二进制的序列化格式,与JSON相比,它生成的数据体积更小,特别适合在网络带宽有限或者存储容量紧张的场景中使用。
  2. 解析与生成效率:Msgpack的解析和生成速度通常比JSON快,因为它是二进制格式,在处理大数据量时性能优势更明显。
  3. 可读性:JSON是文本格式,具有很好的可读性,方便人工查看和编辑。而Msgpack是二进制格式,不具备可读性,需要专门的工具进行查看和调试。

在实际应用中,需要根据具体的需求来选择合适的序列化格式。如果是在跨语言的Web应用中进行数据交换,JSON通常是首选;如果是在纯Python环境中进行数据持久化并且对性能和复杂对象支持有较高要求,Pickle可能更合适;如果对数据体积和性能要求极高,并且不需要可读性,Msgpack是不错的选择。

通过对Python中json.dumps函数的深入了解,我们掌握了如何将Python数据结构序列化成为JSON格式的字符串,以及在各种场景下的应用、常见问题的解决方法,还有与其他序列化格式的比较。这将有助于我们在实际项目中更加高效、灵活地处理数据的存储、传输和交换等操作。