• Python3标准库:json JavaScript对象记法


    1. json JavaScript对象记法

    json模块提供了一个与pickle类似的API,可以行化表示,被称为JavaScript对象记法(JavaScript Object Notation,JSON)。不同于pickle,JSON有一个优点,它有多种语言的实现(特别是JavaScript)。JSON对于RESTAPI中Web服务器和客户之间的通信使用最广泛,不过也可以用于满足其他应用间的通信需求。

    1.1 编码和解码简单数据类型

    默认的,编码器理解Python的一些内置类型(即str、int、float、list、tuple和dict)。

    import json
    
    data = [{'a': 'A', 'b': (2, 4), 'c': 3.0}]
    print('DATA:', repr(data))
    
    data_string = json.dumps(data)
    print('JSON:', data_string)

    对值编码时,表面上类似于Python的repr()输出。

    编码然后再重新解码时,可能不会得到完全相同的对象类型。

    import json
    
    data = [{'a': 'A', 'b': (2, 4), 'c': 3.0}]
    print('DATA   :', data)
    
    data_string = json.dumps(data)
    print('ENCODED:', data_string)
    
    decoded = json.loads(data_string)
    print('DECODED:', decoded)
    
    print('ORIGINAL:', type(data[0]['b']))
    print('DECODED :', type(decoded[0]['b']))

    具体的,元组会变成列表。

    1.2 人类可读和紧凑输出

    JSON优于pickle的另一个好处是,JSON会生成人类可读的结果。dumps()函数接受多个参数从而使输出更容易理解。例如,sort_keys标志会告诉编码器按有序顺序而不是随机顺序输出字典的键。

    import json
    
    data = [{'a': 'A', 'b': (2, 4), 'c': 3.0}]
    print('DATA:', repr(data))
    
    unsorted = json.dumps(data)
    print('JSON:', json.dumps(data))
    print('SORT:', json.dumps(data, sort_keys=True))
    
    first = json.dumps(data, sort_keys=True)
    second = json.dumps(data, sort_keys=True)
    
    print('UNSORTED MATCH:', unsorted == first)
    print('SORTED MATCH  :', first == second)

    排序后,会让人更容易的查看结果,而且还可以在测试中比较JSON输出。

    对于高度嵌套的数据结构,还可以指定一个缩进(indent)值来得到格式美观的输出。

    import json
    
    data = [{'a': 'A', 'b': (2, 4), 'c': 3.0}]
    print('DATA:', repr(data))
    
    print('NORMAL:', json.dumps(data, sort_keys=True))
    print('INDENT:', json.dumps(data, sort_keys=True, indent=2))

    当缩进是一个非负整数时,输出更类似于pprint的输出,数据结构中每一级的前导空格与缩进级别匹配。

    这种详细输出会增加传输等量数据所需的字节数,所以生产环境中往往不使用这种输出。实际上,可以调整编码输出中分隔数据的设置,从而使其比默认格式更紧凑。

    import json
    
    data = [{'a': 'A', 'b': (2, 4), 'c': 3.0}]
    print('DATA:', repr(data))
    
    print('repr(data)             :', len(repr(data)))
    
    plain_dump = json.dumps(data)
    print('dumps(data)            :', len(plain_dump))
    
    small_indent = json.dumps(data, indent=2)
    print('dumps(data, indent=2)  :', len(small_indent))
    
    with_separators = json.dumps(data, separators=(',', ':'))
    print('dumps(data, separators):', len(with_separators))

    dumps()的separators参数应当是一个元组,其中包含用来分隔列表中各项的字符串,以及分隔字典中键和值的字符串。默认为(',',':')。通过去除空白符,可以生成一个更为紧凑的输出。

    1.3 编码字典

    JSON格式要求字典的键是字符串。如果一个字典以非字符串类型作为键,那么对这个字典编码时,便会生成一个TypeError。要想绕开这个限制,一种办法是使用skipkeys参数告诉编码器跳过非串的键。

    import json
    
    data = [{'a': 'A', 'b': (2, 4), 'c': 3.0, ('d',): 'D tuple'}]
    
    print('First attempt')
    try:
        print(json.dumps(data))
    except TypeError as err:
        print('ERROR:', err)
    
    print()
    print('Second attempt')
    print(json.dumps(data, skipkeys=True))

    这里不会产生一个异常,而是会忽略非串的键。

    1.4 处理定制类型

    目前为止,所有例子都使用Python的内置类型,因为这些类型得到了json的内置支持。通常还需要对定制类编码,有两种办法可以做到。假设以下代码清单中的类需要进行编码。

    import json
    class
    MyObj: def __init__(self, s): self.s = s def __repr__(self): return '<MyObj({})>'.format(self.s)

    要对MyObj实例编码,一个简单的方法是定义一个函数,将未知类型转换为已知类型。这个函数并不需要具体完成编码,它只是将一个类型的对象转换为另一个类型。

    import json
    class MyObj:
    
        def __init__(self, s):
            self.s = s
    
        def __repr__(self):
            return '<MyObj({})>'.format(self.s)
    
    obj = MyObj('instance value goes here')
    
    print('First attempt')
    try:
        print(json.dumps(obj))
    except TypeError as err:
        print('ERROR:', err)
    
    def convert_to_builtin_type(obj):
        print('default(', repr(obj), ')')
        # Convert objects to a dictionary of their representation
        d = {
            '__class__': obj.__class__.__name__,
            '__module__': obj.__module__,
        }
        d.update(obj.__dict__)
        return d
    
    print()
    print('With default')
    print(json.dumps(obj, default=convert_to_builtin_type))

    在convert_to_builtin_ type()中,json无法识别的类实例会被转换为字典,其中包含足够多的信息,如果程序能访问这个处理所需的Python模块,就能利用这些信息重新创建对象。

    要对结果解码并创建一个MyObj()实例,可以使用loads()的objecthook参数关联解码器,从而可以从模块导入这个类,并将该类用来创建实例。对于从到来数据流解码的各个字典,都会调用object_hook,这就提供了一个机会,可以把字典转换为另外一种类型的对象。hook函数要返回调用应用要接收的对象而不是字典。

    import json
    
    def dict_to_object(d):
        if '__class__' in d:
            class_name = d.pop('__class__')
            module_name = d.pop('__module__')
            module = __import__(module_name)
            print('MODULE:', module.__name__)
            class_ = getattr(module, class_name)
            print('CLASS:', class_)
            args = {
                key: value
                for key, value in d.items()
            }
            print('INSTANCE ARGS:', args)
            inst = class_(**args)
        else:
            inst = d
        return inst
    
    encoded_object = '''
        [{"s": "instance value goes here",
          "__module__": "json_myobj", "__class__": "MyObj"}]
        '''
    
    myobj_instance = json.loads(
        encoded_object,
        object_hook=dict_to_object,
    )
    print(myobj_instance)

    由于json将串值转换为Unicode对象,因此,在其被用作类构造函数的关键字参数之前,需要将它们重新编码为ASCII串。

    内置类型也有类似的hook,如整数(parse_int)、浮点数(parse_float)和常量(parse_constant)。

    1.5 编码器和解码器类

    除了之前介绍的便利函数,json模块还提供了一些类来完成编码和解码。直接使用这些类可以访问另外的API来定制其行为。 

    JSONEncoder使用一个iterable接口生成编码数据“块”,从而更容易将其写至文件或网络套接字,而不必在内存中表示完整的数据结构。

    import json
    
    encoder = json.JSONEncoder()
    data = [{'a': 'A', 'b': (2, 4), 'c': 3.0}]
    
    for part in encoder.iterencode(data):
        print('PART:', part)

    输出按逻辑单元输出,而不是根据某个大小值。

    encode()方法基本上等价于''.join(encoder.iterencode()),只不过之前会做一些额外的错误检查。
    要对任意的对象编码,需要用一个实现覆盖default()方法,这个实现类似于convert_to _builtin_type()中的实现。

    import json
    
    class MyObj:
    
        def __init__(self, s):
            self.s = s
    
        def __repr__(self):
            return '<MyObj({})>'.format(self.s)
    
    class MyEncoder(json.JSONEncoder):
    
        def default(self, obj):
            print('default(', repr(obj), ')')
            # Convert objects to a dictionary of their representation
            d = {
                '__class__': obj.__class__.__name__,
                '__module__': obj.__module__,
            }
            d.update(obj.__dict__)
            return d
    
    obj = MyObj('internal data')
    print(obj)
    print(MyEncoder().encode(obj))

    输出与前一个实现的输出相同。

    这里要解码文本,然后将字典转换为一个对象,与前面的实现相比,这需要多做一些工作,不过不算太多。

    import json
    
    class MyDecoder(json.JSONDecoder):
    
        def __init__(self):
            json.JSONDecoder.__init__(
                self,
                object_hook=self.dict_to_object,
            )
    
        def dict_to_object(self, d):
            if '__class__' in d:
                class_name = d.pop('__class__')
                module_name = d.pop('__module__')
                module = __import__(module_name)
                print('MODULE:', module.__name__)
                class_ = getattr(module, class_name)
                print('CLASS:', class_)
                args = {
                    key: value
                    for key, value in d.items()
                }
                print('INSTANCE ARGS:', args)
                inst = class_(**args)
            else:
                inst = d
            return inst
    
    encoded_object = '''
    [{"s": "instance value goes here",
      "__module__": "json_myobj", "__class__": "MyObj"}]
    '''
    
    myobj_instance = MyDecoder().decode(encoded_object)
    print(myobj_instance)

    输出与前面的例子相同。

    1.6 处理流和文件

    目前为止,所有例子都假设整个数据结构的编码版本可以一次完全放在内存中。对于很大的数据结构,更合适的做法可能是将编码直接写至一个类似文件的对象。便利函数load()和dump()会接收一个类似文件对象的引用用于读写。

    import io
    import json
    
    data = [{'a': 'A', 'b': (2, 4), 'c': 3.0}]
    
    f = io.StringIO()
    json.dump(data, f)
    
    print(f.getvalue())

    类似于这个例子中使用的StringIO缓冲区,也可以使用套接字或常规的文件句柄。

    尽管没有优化,即一次只读取数据的一部分,但load()函数还提供了一个好处,它封装了从流输入生成对象的逻辑。

    import io
    import json
    
    f = io.StringIO('[{"a": "A", "c": 3.0, "b": [2, 4]}]')
    print(json.load(f))

    类似于dump(),任何类似文件对象都可以被传递到load()。

    1.7 混合数据流

    JS0NDecoder包含一个raw_decode()方法,如果一个数据结构后面跟有更多数据,如带尾部文本的JSON数据,则可以用这个方法完成解码。返回值是对输入数据解码创建的对象,以及该数据的一个索引(指示在哪里结束解码)。

    import json
    
    decoder = json.JSONDecoder()
    
    def get_decoded_and_remainder(input_data):
        obj, end = decoder.raw_decode(input_data)
        remaining = input_data[end:]
        return (obj, end, remaining)
    
    encoded_object = '[{"a": "A", "c": 3.0, "b": [2, 4]}]'
    extra_text = 'This text is not JSON.'
    
    print('JSON first:')
    data = ' '.join([encoded_object, extra_text])
    obj, end, remaining = get_decoded_and_remainder(data)
    
    print('Object              :', obj)
    print('End of parsed input :', end)
    print('Remaining text      :', repr(remaining))
    
    print()
    print('JSON embedded:')
    try:
        data = ' '.join([extra_text, encoded_object, extra_text])
        obj, end, remaining = get_decoded_and_remainder(data)
    except ValueError as err:
        print('ERROR:', err)

    遗憾的是,这种做法只适用于对象出现在输入起始位置的情况。

     

    1.8 命令行上处理JSON

    json.tool模块实现了一个命令行程序来重新格式化JSON数据,使数据更易读。

    [{"a": "A", "c": 3.0, "b": [2, 4]}]

    输入文件example.json包含一个映射,其中键采用字母表顺序。第一个例子显示了按顺序重新格式化的数据,第二个例子使用了--sort-keys在打印输出之前先对映射键排序。

    [
        {
            "a": "A",
            "c": 3.0,
            "b": [
                2,
                4
            ]
        }
    ]
    [
        {
            "a": "A",
            "b": [
                2,
                4
            ],
            "c": 3.0
        }
    ]
  • 相关阅读:
    Kaldi的data目录解析
    Kaldi的nnet3
    Kaldi中的Chain模型
    Karel版本的nnet1
    Dan版本的nnet2
    MFCC/Filter Bank的提取流程
    【算法专题】工欲善其事必先利其器—— C++ STL中vector(向量/不定长数组)的常用方法总结
    App 设计技巧
    js判断是否在微信浏览器中打开
    WebApi 跨域
  • 原文地址:https://www.cnblogs.com/liuhui0308/p/12670409.html
Copyright © 2020-2023  润新知