JSON 是一个轻量的数据交换格式
关于JSON的使用,参考
https://www.runoob.com/python/python-json.html
本文浅析了Python的Json库实现,其分析代码依照CPython的Lib库
分析源码位于:
github
Load 中涉及的函数
Load 时序图
解析JSON字符串默认流程
Load函数
json.load 用于解码 存有JSON数据的文件
源代码位于
github
def load(fp, *, cls=None, object_hook=None, parse_float=None,
parse_int=None, parse_constant=None, object_pairs_hook=None, **kw):
return loads(fp.read(),
cls=cls, object_hook=object_hook,
parse_float=parse_float, parse_int=parse_int,
parse_constant=parse_constant, object_pairs_hook=object_pairs_hook, **kw)
这个函数读取文件fp,然后将解析fp.read()的任务交给了loads
Loads函数
json.loads 用于解码 JSON 数据。该函数返回 Python 字段的数据类型。
源代码位于
github
Loads 要接受一个需要解析的对象s,这个对象的类型可以是str,也可以是bytes或者bytearray
如果就提供了s一个参数,使用默认的解析器_default_decoder,调用_default_decoder.decode(s)
否则构造解析器:
如果没有提供解析器类,则使用JSONDecoder作为解析器类,否则使用自定义的解析器, 自定义解析器要是JSONDecoder的子类。
消耗参数cls,其余参数当作解析器类的构造参数提供, 其余参数在py_make_scanner会进行介绍
特别注释: The
encoding
argument is ignored and deprecated.
def loads(s, *, cls=None, object_hook=None, parse_float=None,
parse_int=None, parse_constant=None, object_pairs_hook=None, **kw):
if isinstance(s, str):
if s.startswith('\ufeff'):
raise JSONDecodeError("Unexpected UTF-8 BOM (decode using utf-8-sig)",
s, 0)
else:
if not isinstance(s, (bytes, bytearray)):
raise TypeError(f'the JSON object must be str, bytes or bytearray, '
f'not {s.__class__.__name__}')
s = s.decode(detect_encoding(s), 'surrogatepass')
if (cls is None and object_hook is None and
parse_int is None and parse_float is None and
parse_constant is None and object_pairs_hook is None and not kw):
return _default_decoder.decode(s)
if cls is None:
cls = JSONDecoder
if object_hook is not None:
kw['object_hook'] = object_hook
if object_pairs_hook is not None:
kw['object_pairs_hook'] = object_pairs_hook
if parse_float is not None:
kw['parse_float'] = parse_float
if parse_int is not None:
kw['parse_int'] = parse_int
if parse_constant is not None:
kw['parse_constant'] = parse_constant
return cls(**kw).decode(s)
decode函数
decode是JSONDecoder类的成员方法
源代码位于
github
class JSONDecoder(object):
def decode(self, s, _w=WHITESPACE.match):
obj, end = self.raw_decode(s, idx=_w(s, 0).end())
end = _w(s, end).end()
if end != len(s):
raise JSONDecodeError("Extra data", s, end)
return obj
decode把解析任务交给了raw_decode方法
raw_decode函数
raw_decode是JSONDecoder类的成员方法
源代码位于
github
class JSONDecoder(object):
def raw_decode(self, s, idx=0):
try:
obj, end = self.scan_once(s, idx)
except StopIteration as err:
raise JSONDecodeError("Expecting value", s, err.value) from None
return obj, end
raw_decode把解析任务交给了scan_once函数
scan_once是scanner.make_scanner(self)
make_scanner方法
make_scanner是scanner文件中的函数
源代码位于
github
make_scanner = c_make_scanner or py_make_scanner
make_scanner实际指向c_make_scanner 或者 py_make_scanner
c_make_scanner采用的c语言编写,源代码
github
py_make_scanner采用python语言编写,源代码
github
这两俩才是真正进行对JSON字符串解析的,为了分析这两个,需要进一步了解JSON格式文法
JSON 格式文法
JSON 文法
JSON
->
true
|
false
|
null
|
number
|
str
|
JSONArray
|
JSONObject
JSONObject
->
{
ObjectMembers
}
ObjectMembers
->
Members
|
ε
Members
->
Member
A
A
->
,
Member
|
ε
Member
->
Key
:
Value
Key
->
str
Value
->
JSON
JSONArray
->
[
ArrayElements
]
ArrayElements
->
Elements
|
ε
Elements
->
Element
B
B
->
,
Element
|
ε
Element
->
JSON
编写JSON解析器
_scan_once 函数
这是一个闭包, 其自由变量位于py_make_scanner中
parse_object = context.parse_object = JSONDecoder中的parse_object = JSONObject,
JSONObject位于
github
parse_array = context.parse_array = JSONDecoder中的parse_array = JSONArray,
JSONArray位于
github
parse_string = context.parse_string = JSONDecoder中的parse_string = scanstring,
scanstring位于
github
match_number = NUMBER_RE.match = re.compile(r’(-?(?:0|[1-9]\d*))(.\d+)?([eE][-+]?\d+)?', (re.VERBOSE | re.MULTILINE | re.DOTALL))
strict = context.strict = JSONDecoder中的strict = True or loads中kw存有strict参数
parse_float = context.parse_float = JSONDecoder中的parse_float = float or (parse_float = loads 中的 parse_float)
parse_int = context.parse_int = JSONDecoder中的parse_int = int or (parse_int = loads 中的 parse_int)
parse_constant = context.parse_constant = JSONDecoder中的parse_constant = _CONSTANTS.__getitem__ or (parse_constant = loads 中的 parse_constant)
object_hook = context.object_hook= JSONDecoder中的object_hook = object_hook = loads 中的 object_hook
object_pairs_hook = context.object_pairs_hook= JSONDecoder中的object_pairs_hook = object_pairs_hook = loads 中的 object_pairs_hook
memo = context.memo= JSONDecoder中的memo = {}
采用了递归下降分析法
def _scan_once(string, idx):
try:
nextchar = string[idx]
except IndexError:
raise StopIteration(idx) from None
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_string 函数
parse_string函数默认是c_scanstring 或者 py_scanstring 函数
这两个都是用来解析字符串的,c_scanstring采用c语言编写,py_scanstring采用python语言编写。
下面讨论py_scanstring函数, 源代码位于:
github
py_scanstring使用了正则表达式 ,其中:
_m = re.compile(r'(.*?)(["\\\x00-\x1f])', re.VERBOSE | re.MULTILINE | re.DOTALL)
代表匹配以" \ \x00-\x1f结尾的最小字符串。groups会返回捕获的分组,这里设置了两个分组, 因此terminator会获得第二个分组的信息
JSONObject 函数
parse_object函数默认是 JSONObject 函数
下面讨论JSONObject函数, 源代码位于:
github
在JSONObject中 跳过 空格、制表、回车、换行等空白字符是有必要的, 采用正则表达式来完成上述操作。
JSONObject的key 必须是string, 其原因可以参考
stackoverflow
JSONObject
->
{
ObjectMembers
}
ObjectMembers
->
Members
|
ε
处理JSONObject = {} 的情况
Member
->
Key
:
Value
Key
->
str
Value
->
JSON
|
null
如果 object_pairs_hook 不为空, 利用 object_pairs_hook 对于 pairs 进行处理
object_pairs_hook 接受一个 list 对象, 其中list的每一个元素是一个tuple
以下给一个demo:
class Test(object):
def __init__(self, li):
self.li = li
def __str__(self):
return "[Test object_pairs_hook] : " + self.li.__str__()
def test():
t = json.loads('{"123": 234}', object_pairs_hook=Test)
print(t)
print(type(t))
如果 object_hook 不为空, 利用 object_hook 对于dict(pairs) 进行处理
class Test(object):
def __init__(self, di):
self.di = di
def __str__(self):
return "[Test object_pairs_hook] : " + self.di.__str__()
def test():
t = json.loads('{"123": 234}', object_hook=Test)
print(t)
print(type(t))
JSONArray 函数
parse_array函数默认是 JSONArray 函数
下面讨论 JSONArray 函数, 源代码位于:
github
JSONArray
->
[
ArrayElements
]
ArrayElements
->
Elements
|
ε
Elements
->
Element
B
B
->
,
Element
|
ε
Element
->
JSON
先判断是不是空的list, 利用正则表达式跳过空白字符
不断调用scan_once获取
JSON
判断null、true、false
def _scan_once(string, idx):
if 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
利用正则表达式进行匹配
NUMBER_RE = re.compile(
r'(-?(?:0|[1-9]\d*))(\.\d+)?([eE][-+]?\d+)?',
(re.VERBOSE | re.MULTILINE | re.DOTALL))
然后利用groups()获取分组,根据分组情况转成int或者float, 如果无法转换,检查是否是NaN、Infinity、-Infinity