1
|
1 import string
|
|
2 import types
|
|
3
|
|
4 ## json.py implements a JSON (http://json.org) reader and writer.
|
|
5 ## Copyright (C) 2005 Patrick D. Logan
|
|
6 ## Contact mailto:patrickdlogan@stardecisions.com
|
|
7 ##
|
|
8 ## This library is free software; you can redistribute it and/or
|
|
9 ## modify it under the terms of the GNU Lesser General Public
|
|
10 ## License as published by the Free Software Foundation; either
|
|
11 ## version 2.1 of the License, or (at your option) any later version.
|
|
12 ##
|
|
13 ## This library is distributed in the hope that it will be useful,
|
|
14 ## but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
15 ## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
|
16 ## Lesser General Public License for more details.
|
|
17 ##
|
|
18 ## You should have received a copy of the GNU Lesser General Public
|
|
19 ## License along with this library; if not, write to the Free Software
|
|
20 ## Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
|
21
|
|
22
|
|
23 class _StringGenerator(object):
|
|
24 def __init__(self, string):
|
|
25 self.string = string
|
|
26 self.index = -1
|
|
27 def peek(self):
|
|
28 i = self.index + 1
|
|
29 if i < len(self.string):
|
|
30 return self.string[i]
|
|
31 else:
|
|
32 return None
|
|
33 def next(self):
|
|
34 self.index += 1
|
|
35 if self.index < len(self.string):
|
|
36 return self.string[self.index]
|
|
37 else:
|
|
38 raise StopIteration
|
|
39 def all(self):
|
|
40 return self.string
|
|
41
|
|
42 class WriteException(Exception):
|
|
43 pass
|
|
44
|
|
45 class ReadException(Exception):
|
|
46 pass
|
|
47
|
|
48 class JsonReader(object):
|
|
49 hex_digits = {'A': 10,'B': 11,'C': 12,'D': 13,'E': 14,'F':15}
|
|
50 escapes = {'t':'\t','n':'\n','f':'\f','r':'\r','b':'\b'}
|
|
51
|
|
52 def read(self, s):
|
|
53 self._generator = _StringGenerator(s)
|
|
54 result = self._read()
|
|
55 return result
|
|
56
|
|
57 def _read(self):
|
|
58 self._eatWhitespace()
|
|
59 peek = self._peek()
|
|
60 if peek is None:
|
|
61 raise ReadException, "Nothing to read: '%s'" % self._generator.all()
|
|
62 if peek == '{':
|
|
63 return self._readObject()
|
|
64 elif peek == '[':
|
|
65 return self._readArray()
|
|
66 elif peek == '"':
|
|
67 return self._readString()
|
|
68 elif peek == '-' or peek.isdigit():
|
|
69 return self._readNumber()
|
|
70 elif peek == 't':
|
|
71 return self._readTrue()
|
|
72 elif peek == 'f':
|
|
73 return self._readFalse()
|
|
74 elif peek == 'n':
|
|
75 return self._readNull()
|
|
76 elif peek == '/':
|
|
77 self._readComment()
|
|
78 return self._read()
|
|
79 else:
|
|
80 raise ReadException, "Input is not valid JSON: '%s'" % self._generator.all()
|
|
81
|
|
82 def _readTrue(self):
|
|
83 self._assertNext('t', "true")
|
|
84 self._assertNext('r', "true")
|
|
85 self._assertNext('u', "true")
|
|
86 self._assertNext('e', "true")
|
|
87 return True
|
|
88
|
|
89 def _readFalse(self):
|
|
90 self._assertNext('f', "false")
|
|
91 self._assertNext('a', "false")
|
|
92 self._assertNext('l', "false")
|
|
93 self._assertNext('s', "false")
|
|
94 self._assertNext('e', "false")
|
|
95 return False
|
|
96
|
|
97 def _readNull(self):
|
|
98 self._assertNext('n', "null")
|
|
99 self._assertNext('u', "null")
|
|
100 self._assertNext('l', "null")
|
|
101 self._assertNext('l', "null")
|
|
102 return None
|
|
103
|
|
104 def _assertNext(self, ch, target):
|
|
105 if self._next() != ch:
|
|
106 raise ReadException, "Trying to read %s: '%s'" % (target, self._generator.all())
|
|
107
|
|
108 def _readNumber(self):
|
|
109 isfloat = False
|
|
110 result = self._next()
|
|
111 peek = self._peek()
|
|
112 while peek is not None and (peek.isdigit() or peek == "."):
|
|
113 isfloat = isfloat or peek == "."
|
|
114 result = result + self._next()
|
|
115 peek = self._peek()
|
|
116 try:
|
|
117 if isfloat:
|
|
118 return float(result)
|
|
119 else:
|
|
120 return int(result)
|
|
121 except ValueError:
|
|
122 raise ReadException, "Not a valid JSON number: '%s'" % result
|
|
123
|
|
124 def _readString(self):
|
|
125 result = ""
|
|
126 assert self._next() == '"'
|
|
127 try:
|
|
128 while self._peek() != '"':
|
|
129 ch = self._next()
|
|
130 if ch == "\\":
|
|
131 ch = self._next()
|
|
132 if ch in 'brnft':
|
|
133 ch = self.escapes[ch]
|
|
134 elif ch == "u":
|
|
135 ch4096 = self._next()
|
|
136 ch256 = self._next()
|
|
137 ch16 = self._next()
|
|
138 ch1 = self._next()
|
|
139 n = 4096 * self._hexDigitToInt(ch4096)
|
|
140 n += 256 * self._hexDigitToInt(ch256)
|
|
141 n += 16 * self._hexDigitToInt(ch16)
|
|
142 n += self._hexDigitToInt(ch1)
|
|
143 ch = unichr(n)
|
|
144 elif ch not in '"/\\':
|
|
145 raise ReadException, "Not a valid escaped JSON character: '%s' in %s" % (ch, self._generator.all())
|
|
146 result = result + ch
|
|
147 except StopIteration:
|
|
148 raise ReadException, "Not a valid JSON string: '%s'" % self._generator.all()
|
|
149 assert self._next() == '"'
|
|
150 return result
|
|
151
|
|
152 def _hexDigitToInt(self, ch):
|
|
153 try:
|
|
154 result = self.hex_digits[ch.upper()]
|
|
155 except KeyError:
|
|
156 try:
|
|
157 result = int(ch)
|
|
158 except ValueError:
|
|
159 raise ReadException, "The character %s is not a hex digit." % ch
|
|
160 return result
|
|
161
|
|
162 def _readComment(self):
|
|
163 assert self._next() == "/"
|
|
164 second = self._next()
|
|
165 if second == "/":
|
|
166 self._readDoubleSolidusComment()
|
|
167 elif second == '*':
|
|
168 self._readCStyleComment()
|
|
169 else:
|
|
170 raise ReadException, "Not a valid JSON comment: %s" % self._generator.all()
|
|
171
|
|
172 def _readCStyleComment(self):
|
|
173 try:
|
|
174 done = False
|
|
175 while not done:
|
|
176 ch = self._next()
|
|
177 done = (ch == "*" and self._peek() == "/")
|
|
178 if not done and ch == "/" and self._peek() == "*":
|
|
179 raise ReadException, "Not a valid JSON comment: %s, '/*' cannot be embedded in the comment." % self._generator.all()
|
|
180 self._next()
|
|
181 except StopIteration:
|
|
182 raise ReadException, "Not a valid JSON comment: %s, expected */" % self._generator.all()
|
|
183
|
|
184 def _readDoubleSolidusComment(self):
|
|
185 try:
|
|
186 ch = self._next()
|
|
187 while ch != "\r" and ch != "\n":
|
|
188 ch = self._next()
|
|
189 except StopIteration:
|
|
190 pass
|
|
191
|
|
192 def _readArray(self):
|
|
193 result = []
|
|
194 assert self._next() == '['
|
|
195 done = self._peek() == ']'
|
|
196 while not done:
|
|
197 item = self._read()
|
|
198 result.append(item)
|
|
199 self._eatWhitespace()
|
|
200 done = self._peek() == ']'
|
|
201 if not done:
|
|
202 ch = self._next()
|
|
203 if ch != ",":
|
|
204 raise ReadException, "Not a valid JSON array: '%s' due to: '%s'" % (self._generator.all(), ch)
|
|
205 assert ']' == self._next()
|
|
206 return result
|
|
207
|
|
208 def _readObject(self):
|
|
209 result = {}
|
|
210 assert self._next() == '{'
|
|
211 done = self._peek() == '}'
|
|
212 while not done:
|
|
213 key = self._read()
|
|
214 if type(key) is not types.StringType:
|
|
215 raise ReadException, "Not a valid JSON object key (should be a string): %s" % key
|
|
216 self._eatWhitespace()
|
|
217 ch = self._next()
|
|
218 if ch != ":":
|
|
219 raise ReadException, "Not a valid JSON object: '%s' due to: '%s'" % (self._generator.all(), ch)
|
|
220 self._eatWhitespace()
|
|
221 val = self._read()
|
|
222 result[key] = val
|
|
223 self._eatWhitespace()
|
|
224 done = self._peek() == '}'
|
|
225 if not done:
|
|
226 ch = self._next()
|
|
227 if ch != ",":
|
|
228 raise ReadException, "Not a valid JSON array: '%s' due to: '%s'" % (self._generator.all(), ch)
|
|
229 assert self._next() == "}"
|
|
230 return result
|
|
231
|
|
232 def _eatWhitespace(self):
|
|
233 p = self._peek()
|
|
234 while p is not None and p in string.whitespace or p == '/':
|
|
235 if p == '/':
|
|
236 self._readComment()
|
|
237 else:
|
|
238 self._next()
|
|
239 p = self._peek()
|
|
240
|
|
241 def _peek(self):
|
|
242 return self._generator.peek()
|
|
243
|
|
244 def _next(self):
|
|
245 return self._generator.next()
|
|
246
|
|
247 class JsonWriter(object):
|
|
248
|
|
249 def _append(self, s):
|
|
250 self._results.append(s)
|
|
251
|
|
252 def write(self, obj, escaped_forward_slash=False):
|
|
253 self._escaped_forward_slash = escaped_forward_slash
|
|
254 self._results = []
|
|
255 self._write(obj)
|
|
256 return "".join(self._results)
|
|
257
|
|
258 def _write(self, obj):
|
|
259 ty = type(obj)
|
|
260 if ty is types.DictType:
|
|
261 n = len(obj)
|
|
262 self._append("{")
|
|
263 for k, v in obj.items():
|
|
264 self._write(k)
|
|
265 self._append(":")
|
|
266 self._write(v)
|
|
267 n = n - 1
|
|
268 if n > 0:
|
|
269 self._append(",")
|
|
270 self._append("}")
|
|
271 elif ty is types.ListType or ty is types.TupleType:
|
|
272 n = len(obj)
|
|
273 self._append("[")
|
|
274 for item in obj:
|
|
275 self._write(item)
|
|
276 n = n - 1
|
|
277 if n > 0:
|
|
278 self._append(",")
|
|
279 self._append("]")
|
|
280 elif ty is types.StringType or ty is types.UnicodeType:
|
|
281 self._append('"')
|
|
282 obj = obj.replace('\\', r'\\')
|
|
283 if self._escaped_forward_slash:
|
|
284 obj = obj.replace('/', r'\/')
|
|
285 obj = obj.replace('"', r'\"')
|
|
286 obj = obj.replace('\b', r'\b')
|
|
287 obj = obj.replace('\f', r'\f')
|
|
288 obj = obj.replace('\n', r'\n')
|
|
289 obj = obj.replace('\r', r'\r')
|
|
290 obj = obj.replace('\t', r'\t')
|
|
291 self._append(obj)
|
|
292 self._append('"')
|
|
293 elif ty is types.IntType or ty is types.LongType:
|
|
294 self._append(str(obj))
|
|
295 elif ty is types.FloatType:
|
|
296 self._append("%f" % obj)
|
|
297 elif obj is True:
|
|
298 self._append("true")
|
|
299 elif obj is False:
|
|
300 self._append("false")
|
|
301 elif obj is None:
|
|
302 self._append("null")
|
|
303 else:
|
|
304 raise WriteException, "Cannot write in JSON: %s" % repr(obj)
|
|
305
|
|
306 def write(obj, escaped_forward_slash=False):
|
|
307 return JsonWriter().write(obj, escaped_forward_slash)
|
|
308
|
|
309 def read(s):
|
|
310 return JsonReader().read(s)
|