-
-
Notifications
You must be signed in to change notification settings - Fork 0
/
json.py
163 lines (139 loc) · 3.82 KB
/
json.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
"""Metaloader for JSON documents.
Let's load a simple JSON document:
>>> from metaloaders import load
>>> json = load(\"""
... {
... "test": 123
... }
... \""")
Now you can access the outer object meta-data:
>>> json.start_line == 2
json.end_line == 4
json.start_column == 0
json.end_column == 1
As well as child objects meta-data:
>>> json.inner['test'] == Node(
data=123,
data_type=Type.NUMBER,
end_column=15,
end_line=3,
start_column=12,
start_line=3,
)
Every JSON token contains all possible metadata:
>>> json.data == {key: val}
# Where:
#
# key = Node(
# data='test',
# data_type=Type.STRING,
# end_column=10,
# end_line=3,
# start_column=4,
# start_line=3,
# )
#
# val = Node(
# data=123,
# data_type=Type.NUMBER,
# end_column=15,
# end_line=3,
# start_column=12,
# start_line=3,
# )
"""
# Standard library
import ast
from typing import (
Any,
Optional,
)
# Third party libraries
import lark
# Local libraries
from metaloaders.exceptions import (
MetaloaderError,
)
from metaloaders.model import (
Node,
Type,
)
# Constants
GRAMMAR = r"""
?start: value
?value: array
| object
| number
| string
| "false" -> false
| "null" -> null
| "true" -> true
array : "[" [value ("," value)*] "]"
object : "{" [pair ("," pair)*] "}"
number : SIGNED_NUMBER
pair : string ":" value
string : ESCAPED_STRING
%import common.ESCAPED_STRING
%import common.SIGNED_NUMBER
%import common.WS
%ignore WS
"""
def load(stream: str) -> Node:
"""Loads a string representation of a document.
Raises `metaloaders.exceptions.MetaloaderError` if any parsing error occur.
"""
parser = lark.Lark(
grammar=GRAMMAR,
parser='lalr',
propagate_positions=True,
)
try:
obj = parser.parse(stream)
except lark.exceptions.LarkError as exc:
raise MetaloaderError(f'Unable to parse stream: {exc}')
else:
data: Node = _simplify(obj)
return data
def _simplify(obj: Any) -> Any:
data: Any
data_type: Optional[Type]
if isinstance(obj, lark.Tree):
if obj.data == 'object':
data = dict(map(_simplify, obj.children))
data_type = Type.OBJECT
elif obj.data == 'array':
data = list(map(_simplify, obj.children))
data_type = Type.ARRAY
elif obj.data == 'pair':
data = (
_simplify(obj.children[0]),
_simplify(obj.children[1]),
)
data_type = None
elif obj.data == 'null':
data = None
data_type = Type.NULL
elif obj.data == 'true':
data = True
data_type = Type.BOOLEAN
elif obj.data == 'false':
data = False
data_type = Type.BOOLEAN
elif obj.data == 'string':
data = ast.literal_eval(obj.children[0].value) # type: ignore
data_type = Type.STRING
elif obj.data == 'number':
data = ast.literal_eval(obj.children[0].value) # type: ignore
data_type = Type.NUMBER
else:
raise NotImplementedError(obj)
else:
raise NotImplementedError(obj)
return data if data_type is None else Node(
data=data,
data_type=data_type,
end_column=obj.end_column - 1, # type: ignore
end_line=obj.end_line, # type: ignore
start_column=obj.column - 1, # type: ignore
start_line=obj.line, # type: ignore
)