Python Json Decoder分析

JSON (JavaScript Object Notation) is a subset of
JavaScript syntax (ECMA-262 3rd edition) used as a lightweight data
interchange format.

JSON 是一种轻量级的数据交换格式。采用完全独立于编程语言的文本格式来存储和表示数据。简洁和清晰的层次结构使得 JSON 成为理想的数据交换语言。 易于人阅读和编写,同时也易于机器解析和生成,并有效地提升网络传输效率。

Python的json库

Python自带了json库,主要由Encoder、Decoder和Scanner三个部分组成。

最简单的例子

import json
s = {
    'a': 'a',
    'b': 'b'
}
print(json.dumps(s))
# {"a": "a", "b": "b"}
s = '{"a": "a", "b": "b"}'
print(json.loads(s))
# {'a': 'a', 'b': 'b'}

def loads

函数定义

def loads(s, *, encoding=None, cls=None, object_hook=None, parse_float=None,
        parse_int=None, parse_constant=None, object_pairs_hook=None, **kw)
    ``object_hook`` is an optional function that will be called with the
    result of any object literal decode (a ``dict``). The return value of
    ``object_hook`` will be used instead of the ``dict``. This feature
    can be used to implement custom decoders (e.g. JSON-RPC class hinting).

    ``object_pairs_hook`` is an optional function that will be called with the
    result of any object literal decoded with an ordered list of pairs.  The
    return value of ``object_pairs_hook`` will be used instead of the ``dict``.
    This feature can be used to implement custom decoders that rely on the
    order that the key and value pairs are decoded (for example,
    collections.OrderedDict will remember the order of insertion). If
    ``object_hook`` is also defined, the ``object_pairs_hook`` takes priority.

    ``parse_float``, if specified, will be called with the string
    of every JSON float to be decoded. By default this is equivalent to
    float(num_str). This can be used to use another datatype or parser
    for JSON floats (e.g. decimal.Decimal).

    ``parse_int``, if specified, will be called with the string
    of every JSON int to be decoded. By default this is equivalent to
    int(num_str). This can be used to use another datatype or parser
    for JSON integers (e.g. float).

    ``parse_constant``, if specified, will be called with one of the
    following strings: -Infinity, Infinity, NaN.
    This can be used to raise an exception if invalid JSON numbers
    are encountered.

    To use a custom ``JSONDecoder`` subclass, specify it with the ``cls``
    kwarg; otherwise ``JSONDecoder`` is used.

    The ``encoding`` argument is ignored and deprecated.

object_hook和object_pairs_hook都可以自定义解码器,但是object_hook返回的是解码后的dict。object_pairs_hook返回的是有序的key-value元祖列表。当两个都给定时,只调用object_pairs_hook。

举个栗子

import json
j = '{"a": 1,"b": 2,"c": 3}'
json.loads(j, object_hook=lambda x: print(type(x), x))
#  {'a': 1, 'b': 2, 'c': 3}
json.loads(j, object_pairs_hook=lambda x: print(type(x), x))
#  [('a', 1), ('b', 2), ('c', 3)]

parse_float、parse_int以及parse_constant可以针对float、int、NaN等值做转化。

再举个栗子

import json
j = '{"a": 1,"b": 2,"c": 3}'
json.loads(j, object_hook=lambda x: print(type(x), x), parse_int=str)
#  {'a': '1', 'b': '2', 'c': '3'}

如果要使用自定义解码器,可以创建一个JSONDecoder的子类,并通过cls参数调用它。
另外encoding已经被废弃了,使用它没有任何用处。

JSONDecoder

loads函数会调用JSONDecoder(**kw).decode()进行解析,JSONDecoder在构造函数中定义了各种类型变量解析函数以及扫描器。decode调用raw_decode从第一个不是空白字符的位置开始进行扫描。

def __init__(self, *, object_hook=None, parse_float=None,
        parse_int=None, parse_constant=None, strict=True,
        object_pairs_hook=None):
    self.object_hook = object_hook
    self.parse_float = parse_float or float
    self.parse_int = parse_int or int
    self.parse_constant = parse_constant or _CONSTANTS.__getitem__
    self.strict = strict
    self.object_pairs_hook = object_pairs_hook
    self.parse_object = JSONObject
    self.parse_array = JSONArray
    self.parse_string = scanstring
    self.memo = {}
    self.scan_once = scanner.make_scanner(self)

def decode(self, s, _w=WHITESPACE.match):
    """Return the Python representation of ``s`` (a ``str`` instance
    containing a JSON document).

    """
    obj, end = self.raw_decode(s, idx=_w(s, 0).end()) 
    # _w(s, start_idx).end() 指获取从光标为start_idx的位置开始到下一个非空字符的光标位置,也就是过滤空格
    # WHITESPACE定义了一个正则,这里不做了解,后文_w均为该意思
    end = _w(s, end).end()
    if end != len(s):
        raise JSONDecodeError("Extra data", s, end)
    return obj

def raw_decode(self, s, idx=0):
    """Decode a JSON document from ``s`` (a ``str`` beginning with
    a JSON document) and return a 2-tuple of the Python
    representation and the index in ``s`` where the document ended.

    This can be used to decode a JSON document from a string that may
    have extraneous data at the end.

    """
    try:
        obj, end = self.scan_once(s, idx)
    except StopIteration as err:
        raise JSONDecodeError("Expecting value", s, err.value) from None
    return obj, end

scanner

scanner的make_scanner会优先使用CPython的scanner,我们这里只看python的scanner,它指向了py_make_scanner。
py_make_scanner接收调用它的对象作为context,raw_decode调用py_make_scanner的scan_once,scan_once又调用了_scan_once,这个函数是最终负责扫描的函数。

_scan_once首先根据idx获取第一个字符,根据字符进行判断属于哪种数据类型,并将字符串分发给相应的处理函数进行解析,如果没有命中任意一种类型或已经扫描完成,抛出停止迭代的异常。

def _scan_once(string, idx):
    try:
        nextchar = string[idx]
    except IndexError:
        raise StopIteration(idx)

    if nextchar == '"':
        return parse_string(string, idx + 1, strict)
    elif nextchar == '{':
        return parse_object((string, idx + 1), strict,
            _scan_once, object_hook, object_pairs_hook, memo)
    elif nextchar == '[':
        return parse_array((string, idx + 1), _scan_once)
    elif nextchar == 'n' and string[idx:idx + 4] == 'null':
        return None, idx + 4
    elif nextchar == 't' and string[idx:idx + 4] == 'true':
        return True, idx + 4
    elif nextchar == 'f' and string[idx:idx + 5] == 'false':
        return False, idx + 5

    m = match_number(string, idx)
    if m is not None:
        integer, frac, exp = m.groups()
        if frac or exp:
            res = parse_float(integer + (frac or '') + (exp or ''))
        else:
            res = parse_int(integer)
        return res, m.end()
    elif nextchar == 'N' and string[idx:idx + 3] == 'NaN':
        return parse_constant('NaN'), idx + 3
    elif nextchar == 'I' and string[idx:idx + 8] == 'Infinity':
        return parse_constant('Infinity'), idx + 8
    elif nextchar == '-' and string[idx:idx + 9] == '-Infinity':
        return parse_constant('-Infinity'), idx + 9
    else:
        raise StopIteration(idx)

parse_object

parse_object首先检查字符是否为}或是"xxxx": 这种形式,如果不是则抛出异常,如果是"xxxx":形式,则将:后面的字符串再次执行scan_once函数进行迭代,并把返回的结果添加到pair的list中,再检查下面的字符是不是 ,” 若是则循环执行scan_once,若是 } 结束解析,并调用object_hook进行自定义处理。

def JSONObject(s_and_end, strict, scan_once, object_hook, object_pairs_hook,
               memo=None, _w=WHITESPACE.match, _ws=WHITESPACE_STR):
    s, end = s_and_end
    pairs = []
    pairs_append = pairs.append
    # Backwards compatibility
    if memo is None:
        memo = {}
    memo_get = memo.setdefault
    # Use a slice to prevent IndexError from being raised, the following
    # check will raise a more specific ValueError if the string is empty
    nextchar = s[end:end + 1]
    # Normally we expect nextchar == '"'
    # 为了避免频繁调用正则提高效率,用if过滤出小于2个空格的情况,超过才调用正则搜索空格结尾,后文均为这个作用
    if nextchar != '"':  
        if nextchar in _ws:
            end = _w(s, end).end()
            nextchar = s[end:end + 1]
        # Trivial empty object
        if nextchar == '}':
            if object_pairs_hook is not None:
                result = object_pairs_hook(pairs)
                return result, end + 1
            pairs = {}
            if object_hook is not None:
                pairs = object_hook(pairs)
            return pairs, end + 1
        elif nextchar != '"':
            raise JSONDecodeError(
                "Expecting property name enclosed in double quotes", s, end)
    end += 1
    while True:
        key, end = scanstring(s, end, strict)
        key = memo_get(key, key)
        # To skip some function call overhead we optimize the fast paths where
        # the JSON key separator is ": " or just ":".
        if s[end:end + 1] != ':':
            end = _w(s, end).end()
            if s[end:end + 1] != ':':
                raise JSONDecodeError("Expecting ':' delimiter", s, end)
        end += 1

        try:
            if s[end] in _ws:
                end += 1
                if s[end] in _ws:
                    end = _w(s, end + 1).end()
        except IndexError:
            pass

        try:
            value, end = scan_once(s, end)
        except StopIteration as err:
            raise JSONDecodeError("Expecting value", s, err.value) from None
        pairs_append((key, value))
        try:
            nextchar = s[end]
            if nextchar in _ws:
                end = _w(s, end + 1).end()
                nextchar = s[end]
        except IndexError:
            nextchar = ''
        end += 1

        if nextchar == '}':
            break
        elif nextchar != ',':
            raise JSONDecodeError("Expecting ',' delimiter", s, end - 1)
        end = _w(s, end).end()
        nextchar = s[end:end + 1]
        end += 1
        if nextchar != '"':
            raise JSONDecodeError(
                "Expecting property name enclosed in double quotes", s, end - 1)
    if object_pairs_hook is not None:
        result = object_pairs_hook(pairs)
        return result, end
    pairs = dict(pairs)
    if object_hook is not None:
        pairs = object_hook(pairs)
    return pairs, end

parse_array

parse_array首先检查字符是否为],为]则返回[],否则将[后面的字符串再次执行scan_once函数进行迭代,并把返回的结果添加到list中,再检查下面的字符若是,则循环执行scan_once,若是]结束解析。

def JSONArray(s_and_end, scan_once, _w=WHITESPACE.match, _ws=WHITESPACE_STR):
    s, end = s_and_end
    values = []
    nextchar = s[end:end + 1]
    if nextchar in _ws:
        end = _w(s, end + 1).end()
        nextchar = s[end:end + 1]
    # Look-ahead for trivial empty array
    if nextchar == ']':
        return values, end + 1
    _append = values.append
    while True:
        try:
            value, end = scan_once(s, end)
        except StopIteration as err:
            raise JSONDecodeError("Expecting value", s, err.value) from None
        _append(value)
        nextchar = s[end:end + 1]
        if nextchar in _ws:
            end = _w(s, end + 1).end()
            nextchar = s[end:end + 1]
        end += 1
        if nextchar == ']':
            break
        elif nextchar != ',':
            raise JSONDecodeError("Expecting ',' delimiter", s, end - 1)
        try:
            if s[end] in _ws:
                end += 1
                if s[end] in _ws:
                    end = _w(s, end + 1).end()
        except IndexError:
            pass

    return values, end

你可能感兴趣的:(Python Json Decoder分析)