Python 标准库之 json 编码和解码器『详解』

Posted XianZhe_

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Python 标准库之 json 编码和解码器『详解』相关的知识,希望对你有一定的参考价值。

Python 标准库之 json 编码和解码器

文章目录


一、Python json库介绍

JSON(javascript Object Notation)是由道格拉斯·克罗克福特构想和设计的一种轻量级资料交换格式。其内容由属性和值所组成,因此也有易于阅读和处理的优势。JSON是独立于编程语言的资料格式,其不仅是JavaScript的子集,也采用了C语言家族的习惯用法,目前也有许多编程语言都能够将其解析和字符串化,其广泛使用的程度也使其成为通用的资料格式。

Python 的 json 库提供了对 json 序列化的支持,与标准库 marshalpickle 相似的API接口。


二、导入 json 库

在看下列内容前,别忘记导入 json 标准库呀

import json

三、Python对应JSON数据类型

1)、JSON 到 Python 数据类型的转换

JSON 数据格式To Python 数据格式
objectdict
arraylist
stringstr
number (int)int
number (real)float
trueTrue
falseFalse
nullNone

2)、Python 到 JSON 数据类型的转换

Python 数据格式To JSON 数据格式
dictobject
list, tuplearray
strstring
int, float, int 和 float 派生的枚举number
Truetrue
Falsefalse
Nonenull

3)、源码查阅

在 JSON 标准库的编码器和解码器源码里,能看到对转换类型的注释

JSONEncoder

class JSONEncoder(object):
    """Extensible JSON <http://json.org> encoder for Python data structures.

    Supports the following objects and types by default:

    +-------------------+---------------+
    | Python            | JSON          |
    +===================+===============+
    | dict              | object        |
    +-------------------+---------------+
    | list, tuple       | array         |
    +-------------------+---------------+
    | str               | string        |
    +-------------------+---------------+
    | int, float        | number        |
    +-------------------+---------------+
    | True              | true          |
    +-------------------+---------------+
    | False             | false         |
    +-------------------+---------------+
    | None              | null          |
    +-------------------+---------------+

    To extend this to recognize other objects, subclass and implement a
    ``.default()`` method with another method that returns a serializable
    object for ``o`` if possible, otherwise it should call the superclass
    implementation (to raise ``TypeError``).

    """
    pass

JSONDecoder

class JSONDecoder(object):
    """Simple JSON <http://json.org> decoder

    Performs the following translations in decoding by default:

    +---------------+-------------------+
    | JSON          | Python            |
    +===============+===================+
    | object        | dict              |
    +---------------+-------------------+
    | array         | list              |
    +---------------+-------------------+
    | string        | str               |
    +---------------+-------------------+
    | number (int)  | int               |
    +---------------+-------------------+
    | number (real) | float             |
    +---------------+-------------------+
    | true          | True              |
    +---------------+-------------------+
    | false         | False             |
    +---------------+-------------------+
    | null          | None              |
    +---------------+-------------------+

    It also understands ``NaN``, ``Infinity``, and ``-Infinity`` as
    their corresponding ``float`` values, which is outside the JSON spec.

    """
    pass

四、基本使用「重点」🧊

1、序列化操作

1)、json.dump(obj, fp, *, skipkeys=False, ensure_ascii=True, check_circular=True, allow_nan=True, cls=None, indent=None, separators=None, default=None, sort_keys=False, **kw)

obj 对象序列化为 JSON 格式化流形式并存储到 文件,需要注意的是 json 模块始终返回的是 str 字符串对象,因此在将 JSON 读写文件时应以文本模式操作,在序列化操作时应确保 fp.write 支持 str 写入。

参数如下:

  • obj: 需要被序列化的对象
  • fp: 传入一个拥有 .write() 写入方法的文件对象,比如 file-like object,需要注意的是应传入一个文本模式的文件对象,并且编码应当是 UTF-8 , UTF-16 或者 UTF-32 。
  • skipkeys:在为 True 时(默认为 False)跳过不是基本对象(包括 str, intfloatboolNone)字典的键,否则引发一个 TypeError 异常。
  • ensure_ascii:在为 True 时(默认为 True),输出保证将所有输入的非 ASCII 字符转义。如果 ensure_ascii 是 false,这些字符会原样输出。
  • check_circular:检查循环。如果为 False(默认为 True),那么容器类型的循环引用检验会被跳过并且循环引用回引发一个 OverflowError (或者更糟的情况)。
  • allow_nan:数据类型之间的转换遵守。如果为 False(默认为 True),那么在对严格 JSON 规格范围外的 float 类型值(naninf-inf)进行序列化时会引发一个 ValueError 异常。如果为 True,则使用它们的 JavaScript 等价形式(NaNInfinity-Infinity)。
  • cls:JSON 编码器的类别,默认使用自带的 JSONEncoder。如果想使用自定义的 JSON 编码器,比如说 JSONEncoder 子类,通过此参数实现。
  • indent: 控制缩进内容。
    a、 默认值为 None 会选择最紧凑的表达,即一行数据。
    b、 如果 indent 是一个非负整数或者字符串,那么 JSON 数组元素和对象成员会被美化输出为该值指定的缩进内容,使用一个正整数会让每一层缩进同样数量的空格。
      如果indent是 4 那么将会以4个空格做完每一层的缩进,indent 是一个字符串 (比如 “\\t”),那个字符串会被用于缩进每一层。
    c、 如果缩进等级为零、负数或者 "",则只会添加换行符,在每个缩进前都不会有字符。
  • separators: 分割字符,因为用两种分割字符,所以应该是一个 (item_separator, key_separator) 元组(项分割字符,键分割字符)。
      但因为缩进参数 indent 的存在,separators 的默认值会有两种变化,当 indentNone 时,会使用紧凑的一行表达,这时候的分割符会取值 (', ', ': ') 在每个分割符后都有空格,而相反在不为 None 时会取值 (',', ':')
      小技巧:为了得到最紧凑的 JSON 表达式,在 indent 为None 的情况下,可以手动将 separators 取值为 (',', ':'),也就是不留空格。
  • default: 被指定时,其应该是一个函数,每当某个对象无法被序列化时它会被调用,此函数接受一个参数,这个参数就是无法被序列化的对象。
    它应该返回该对象的一个可以被 JSON 编码的版本或者引发一个 TypeError。如果没有被指定,则会直接引发 TypeError
  • sort_keys:如果为 True(默认为 False),那么字典的输出会以键的顺序排序。

具体讲解:

  • 取消非ASCII编码转义

    在将 JSON 编码的数据存储到文件或是进行编码返回时,很容易发现除了ASCII字符都发生了转义,变成了Unicode的转义字符,最典型的例子就是中文字符。

    # -*- coding: utf-8 -*-
    import json
    from pathlib import Path
    
    BASE_DIR = Path(__file__).parent
    
    happly_new_year = 
        "年份": 2022, "生肖": "虎", 
        "祝福语": "在这美丽的春节之际,祝福各位朋友在新的一年里,天天都有份好心情!", 
        "春节快乐": None
    
    # 文件路径
    file_path = BASE_DIR / "test.json"
    
    # 打印输出到控制台
    print(json.dumps(happly_new_year))
    # 输出存储到文件
    with file_path.open("w", encoding="utf-8") as f_w:
        json.dump(happly_new_year, fp=f_w)
    

    控制台输出 与 输出到文件

    "\\u5e74\\u4efd": 2022, "\\u751f\\u8096": "\\u864e", "\\u795d\\u798f\\u8bed": "\\u5728\\u8fd9\\u7f8e\\u4e3d\\u7684\\u6625\\u8282\\u4e4b\\u9645\\uff0c\\u795d\\u798f\\u5404\\u4f4d\\u670b\\u53cb\\u5728\\u65b0\\u7684\\u4e00\\u5e74\\u91cc\\uff0c\\u5929\\u5929\\u90fd\\u6709\\u4efd\\u597d\\u5fc3\\u60c5!", "\\u6625\\u8282\\u5feb\\u4e50": null
    

    那么对其进行 JSON 编码时如何取消发生的非 ASCII 字符转义,让这些字符会原样输出呢?答案很简单,在每次编码时将 ensure_ascii 参数设置为 Flase 即可。

    上部分的代码保持不变,只是添加ensure_ascii参数

    ......
    
    # 打印输出到控制台
    print(json.dumps(happly_new_year, ensure_ascii=False))
    # 输出存储到文件
    
    with file_path.open("w", encoding="utf-8") as f_w:
        json.dump(happly_new_year, fp=f_w, ensure_ascii=False)
    

    控制台输出 与 输出到文件

    "年份": 2022, "生肖": "虎", "祝福语": "在这美丽的春节之际,祝福各位朋友在新的一年里,天天都有份好心情!", "春节快乐": null
    
  • JSON 数据格式化

    每次输出的 JSON 数据都是一行显示的,在数据量比较少的情况下还能阅读,当数据量一旦多起来后阅读体验上就没那么好了。JSON 本身就是一种注重数据结构化的格式,在维护调试时可以充分发挥其优点,对其进行格式化就是一种常见手段。
    将其进行格式化返回需要使用到 indent 参数,indent 参数的作用在参数讲解部分有将,这边咱们对这个参数作用进行展示。

    以四个空格为缩进

    # -*- coding: utf-8 -*-
    import json
    from pathlib import Path
    
    BASE_DIR = Path(__file__).parent
    
    happly_new_year = 
        "年份": 2022, "生肖": "虎",
        "祝福语": "在这美丽的春节之际,祝福各位朋友在新的一年里,天天都有份好心情!",
        "春节快乐": None
    
    # 文件路径
    file_path = BASE_DIR / "test.json"
    
    # 打印输出到控制台
    print(json.dumps(happly_new_year, indent=4, ensure_ascii=False))
    # 输出存储到文件
    with file_path.open("w", encoding="utf-8") as f_w:
        json.dump(happly_new_year, fp=f_w, indent=4, ensure_ascii=False)
    
    
        "年份": 2022,
        "生肖": "虎",
        "祝福语": "在这美丽的春节之际,祝福各位朋友在新的一年里,天天都有份好心情!",
        "春节快乐": null
    
    

    以两个空格为缩进

    ......
    
    # 打印输出到控制台
    print(json.dumps(happly_new_year, indent=2, ensure_ascii=False))
    # 输出存储到文件
    with file_path.open("w", encoding="utf-8") as f_w:
        json.dump(happly_new_year, fp=f_w, indent=2, ensure_ascii=False)
    
    
      "年份": 2022,
      "生肖": "虎",
      "祝福语": "在这美丽的春节之际,祝福各位朋友在新的一年里,天天都有份好心情!",
      "春节快乐": null
    
    
  • default 参数的妙用

    default 其实是一个很方便的参数,向这个参数传入一个处理自定义对象的函数,就能够将这些自定义对象转义成可以被 JSON 编码的版本,而不需要在将数据序列化前还要手动转义 JSON 编码器无法解析的数据源。

    有一个水果类,为每一种水果都新建一个对象,每个对象中都有一个方法可以将其转义成可以被 JSON 编码版本的方法,需要做的是在每次序列化时都能正确调用这个方法将其转义。这时 default 这个参数就派上用场了,传入 default 的函数要做两件事,如果是水果类的对象那就将其转义,如果不是则抛出异常。

    # -*- coding: utf-8 -*-
    import json
    from pathlib import Path
    
    BASE_DIR = Path(__file__).parent
    
    
    class Fruits:
        """水果类"""
        def __init__(self, name, price):
            self.name = name
            self.price = price
    
        def __str__(self):
            return f"水果类型:self.name,售价:self.price"
    
        def todict(self):
            """将水果信息转换成字典格式输出"""
            return "水果": self.name, "售价": self.price
    
    
    def obj_to_json(obj):
        """对象转为JSON能编码函数
        
        Args:
            obj: 自定义对象
        Returns:
            能够被JSON编码的版本数据
        Raises:
            如果不是目标类别的对象将抛出TypeError异常
        """
        if isinstance(obj, Fruits):
            return obj.todict()
        raise TypeError(f"Object of type obj.__name__ is not JSON serializable")
    
    
    pear = Fruits("梨子", 3.3)
    apple = Fruits("苹果", 5.6)
    banana = Fruits("香蕉", 11.6)
    orange = Fruits("橙子", 6.6)
    
    
    fruits = [pear, apple, banana, orange]
    # 文件路径
    file_path = BASE_DIR / "test.json"
    
    with file_path.open("w", encoding="utf-8") as f_w:
        json.dump(fruits, fp=f_w, default=obj_to_json, ensure_ascii=False)
    

    JSON 数据

    ["水果": "梨子", "售价": 3.3, "水果": "苹果", "售价": 5.6, "水果": "香蕉", "售价": 11.6, "水果": "橙子", "售价": 6.6]
    

2)、json.dumps(obj, *, skipkeys=False, ensure_ascii=True, check_circular=True, allow_nan=True, cls=None, indent=None, separators=None, default=None, sort_keys=False, **kw)

obj 序列化为 JSON 格式的 str 对象并返回,不会进行文件式存储

除了缺少用于存储到文件的 fp 参数外,其它参数的含义与 json.dump() 中的相同,因此用法也是一样的,只不过一个是存储到文件,一个是直接返回 JSON 编码数据,这里不过多讲解

注解: JSON 中的键-值对中的键永远是 str 类型的。当一个对象被转化为 JSON 时,字典中所有的键都会被强制转换为字符串。这所造成的结果是字典被转换为 JSON 然后转换回字典时可能和原来的不相等。换句话说,如果 x 具有非字符串的键,则有 loads(dumps(x)) != x。比如说在Python数据类型中以整数为键,转化为 JSON 时这些整数将会变成字符串类型。

2、反序列化操作

1)、json.load(fp, *, cls=None, object_hook=None, parse_float=None, parse_int=None, parse_constant=None, object_pairs_hook=None, **kw)

fp (一个支持 .read() 并包含一个 JSON 文档的 text file 或者 binary file) 反序列化为一个 Python 对象。

参数如下:

  • fp: 传入一个拥有 .read() 写入方法的文件对象,该文件对象应是 text file(文本对象)或 binary file(二进制对象),对于二进制对象输入编码应当是 UTF-8 , UTF-16 或者 UTF-32。
  • cls:JSON 解码器的类别,默认使用自带的 JSONDecoder。如果想使用自定义的 JSON 解码器,比如说 JSONDecoder 子类,通过此参数实现。额外的关键词参数会通过类的构造函数传递。
  • object_hook: 被指定时,其应该是一个函数。它会被调用于每一个解码出的对象字面量(一个 dict、一个基本类型对象),此函数接受一个参数,这个参数就是每一个解码出的对象字面量。
    该函数的返回值会取代原本的 dict,简单来说就是该函数负责把反序列化后的基本类型对象转换成自定义类型的对象。这一特性能够被用于实现自定义解码器(如 JSON-RPC 的类型提示)。
  • parse_float:解析浮点数。如果指定,将与每个要解码 JSON 浮点数的字符串一同调用。其应该是一个函数,同时接收一个参数,这个参数是每一个 JSON 浮点数的字符串。默认状态下,相当于 float(num_str)
    可以用于对 JSON 浮点数使用其它数据类型和语法分析程序 (比如 decimal.Decimal )。
  • parse_int:解析整数。如果指定,将与每个要解码 JSON 整数的字符串一同调用。其应该是一个函数,同时接收一个参数,这个参数是每一个 JSON 整数的字符串。默认状态下,相当于 int(num_str)
    可以用于对 JSON 整数使用其它数据类型和语法分析程序 (比如 float )。
  • parse_constant:解析常量。如果指定,将要与以下字符串中的一个一同调用:'-Infinity''Infinity''NaN'。其应该是一个函数,同时接收一个参数,这个参数是每一个 JSON 整数的字符串。如果遇到无效的 JSON 数字则可以使用它引发异常。
  • object_pairs_hook: 它会被调用于每一个有序列表对解码出的对象字面量,这个参数与 object_hook 参数功能相同,与 object_hook 参数不同点是给 object_pairs_hook 的解码的键值对列表是有序的,一些依赖键值对顺序的功能可以用 object_pairs_hook 参数,而不用 object_hook
    如果 object_hook 也被定义,object_pairs_hook 优先。

具体讲解:

  • JSON 浮点数处理

    parse_float 参数用于控制 JSON 浮点数转化为 Python 数据类型时的操作,默认是对其转化为浮点数类型 float(num_str),浮点数还能做的处理,比如四舍五入,向上向下取整,转化为decimal.Decimal等,这些都能直接靠 parse_float 参数实现。

    # -*- coding: utf-8 -*-
    import math
    import json
    from pathlib import Path
    from decimal import Decimal
    
    BASE_DIR = Path(__file__).parent
    # 文件路径
    file_path = BASE_DIR / "test.json"
    
    
    def json_ceil(dic):
        """将json浮点数向上取整辅助函数"""
        return math.ceil(float(dic))
    
    
    def json_floor(dic):
        """将json浮点数向下取整辅助函数"""
        return math.floor(float(dic))
    
    
    def json_round(dic):
        """将json浮点数四舍五入取整辅助函数"""
        return round(float(dic))
    
    
    def float_to_decimal(dic):
        """将json浮点数转化Decimal辅助函数"""
        return Decimal.from_float(float(dic))
        
    .......
    

    源 JSON 数据

    ["水果": "梨子", "售价": 3.3, "水果": "苹果", "售价": 5.6, "水果": "香蕉", "售价": 11.6, "水果": "橙子", "售价": 6.6]
    

    向上取整

    ......
    
    with file_path.open("r", encoding="utf-8") as f_r:
        print(json.load(f_r, parse_float=json_ceil))
    
    ['水果': '梨子', '售价': 4, '水果': '苹果', '售价': 6, '水果': '香蕉', '售价': 12, '水果': '橙子', '售价': 7]
    

    向下取整

    ......
    
    with file_path.open("r", encoding="utf-8") as f_r:
        print(json.load(f_r, parse_float=json_floor))
    
    ['水果': '梨子', '售价': 3, '水果': '苹果', '售价': 5, '水果': '香蕉', '售价': 11, '水果': '橙子', '售价': 6]
    

    四舍五入取整

    ......
    
    with file_path.open("r", encoding="utf-8") as f_r:
        print(json.load(f_r, parse_float=json_round))
    
    ['水果': '梨子', '售价': 3, '水果': '苹果', '售价': 6, '水果': '香蕉', '售价': 12, '水果': '橙子', '售价': 7]
    

    转化为Decimal类型

    ......
    
    with file_path.open("r", encoding="utf-8") as f_r:
    	print(json.load(f_r, parse_float=float_to_decimal))
    
    ['水果': '梨子', '售价': Decimal('3.29999999999999982236431605997495353221893310546875'), '水果': '苹果', '售价': Decimal('5.5999999999999996447286321199499070644378662109375'), '水果': '香蕉', '售价': Decimal('11.5999999999999996447286321199499070644378662109375'), '水果': '橙子', '售价': Decimal('6.5999999999999996447286321199499070644378662109375')]
    
  • JSON 整数处理
    在解析 JSON 整数字符串时,默认是将其转化为整数类型 int(num_str),可以将其转化为浮点数类型,直接靠 parse_float 参数实现。

    # -*- coding: utf-8 -*-
    import math
    import json
    from pathlib import Path
    from decimal import Decimal
    
    BASE_DIR = Path(__file__).parent
    file_path = BASE_DIR / "test.json"
    
    
    with file_path.open("r", encoding="utf-8") as f_r:
        print(json.load(f_r, parse_int=float))
    

    源 JSON 数据

    "祝福语": "新春快乐!", "祝你": 1314
    

    程序打印

    '祝福语': '新春快乐!', '祝你': 1314.0
    
  • object_hook 与 object_pairs_hook 的应用
    object_hook 参数与 object_pairs_hook 参数功能都是一致的,但在编写函数时,函数接受的参数不同。对于 object_hook 参数,传入的是每一个解码出来最基本的类型对象,即每次传入都是 dict 字典数据类型。对于 object_pairs_hook 参数,传入的是每一个解码出来的列表嵌套元组类型 [(key, value), ] ,相当于调用了字典的 .items() 方法。

    颁奖排序算是一个典型例子,以数字1、2、3…为颁奖奖次的依据,分别使用 object_hook 参数与 object_pairs_hook 参数进行示例。

    # -*- coding: utf-8 -*-
    import json
    
    s_data = '''
        "3": ["第三名", "王汗"], "1": ["第一名", "王小明"], "2": ["第二名", "小明"],
        "4": ["第四名", "李华"], "5": ["第五名", "李大川"], "6": ["第六名", "xianzhe_"]
    '''
    
    # 使用 object_hook 参数
    json.loads(s_data, object_hook=lambda x: print(以上是关于Python 标准库之 json 编码和解码器『详解』的主要内容,如果未能解决你的问题,请参考以下文章

    Go 每日一库之 jsonrpc:来自标准库

    [Python3]JSON解析

    Python 标准库之 fcntl

    python: json模块 --JSON编码和解码

    python之simplejson,Python版的简单 快速 可扩展 JSON 编码器/解码器

    Python JSON编码器支持日期时间?