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
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.
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.
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
23
class _StringGenerator(object):
24
def __init__(self, string):
29
if i < len(self.string):
35
if self.index < len(self.string):
36
return self.string[self.index]
42
class WriteException(Exception):
45
class ReadException(Exception):
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'}
53
self._generator = _StringGenerator(s)
61
raise ReadException, "Nothing to read: '%s'" % self._generator.all()
63
return self._readObject()
65
return self._readArray()
67
return self._readString()
68
elif peek == '-' or peek.isdigit():
69
return self._readNumber()
71
return self._readTrue()
73
return self._readFalse()
75
return self._readNull()
80
raise ReadException, "Input is not valid JSON: '%s'" % self._generator.all()
83
self._assertNext('t', "true")
84
self._assertNext('r', "true")
85
self._assertNext('u', "true")
86
self._assertNext('e', "true")
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")
98
self._assertNext('n', "null")
99
self._assertNext('u', "null")
100
self._assertNext('l', "null")
101
self._assertNext('l', "null")
104
def _assertNext(self, ch, target):
105
if self._next() != ch:
106
raise ReadException, "Trying to read %s: '%s'" % (target, self._generator.all())
108
def _readNumber(self):
110
result = self._next()
112
while peek is not None and (peek.isdigit() or peek == "."):
113
isfloat = isfloat or peek == "."
114
result = result + self._next()
122
raise ReadException, "Not a valid JSON number: '%s'" % result
124
def _readString(self):
126
assert self._next() == '"'
128
while self._peek() != '"':
133
ch = self.escapes[ch]
135
ch4096 = 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)
144
elif ch not in '"/\\':
145
raise ReadException, "Not a valid escaped JSON character: '%s' in %s" % (ch, self._generator.all())
147
except StopIteration:
148
raise ReadException, "Not a valid JSON string: '%s'" % self._generator.all()
149
assert self._next() == '"'
152
def _hexDigitToInt(self, ch):
154
result = self.hex_digits[ch.upper()]
159
raise ReadException, "The character %s is not a hex digit." % ch
162
def _readComment(self):
163
assert self._next() == "/"
164
second = self._next()
166
self._readDoubleSolidusComment()
168
self._readCStyleComment()
170
raise ReadException, "Not a valid JSON comment: %s" % self._generator.all()
172
def _readCStyleComment(self):
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()
181
except StopIteration:
182
raise ReadException, "Not a valid JSON comment: %s, expected */" % self._generator.all()
184
def _readDoubleSolidusComment(self):
187
while ch != "\r" and ch != "\n":
189
except StopIteration:
192
def _readArray(self):
194
assert self._next() == '['
195
done = self._peek() == ']'
199
self._eatWhitespace()
200
done = self._peek() == ']'
204
raise ReadException, "Not a valid JSON array: '%s' due to: '%s'" % (self._generator.all(), ch)
205
assert ']' == self._next()
208
def _readObject(self):
210
assert self._next() == '{'
211
done = self._peek() == '}'
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()
219
raise ReadException, "Not a valid JSON object: '%s' due to: '%s'" % (self._generator.all(), ch)
220
self._eatWhitespace()
223
self._eatWhitespace()
224
done = self._peek() == '}'
228
raise ReadException, "Not a valid JSON array: '%s' due to: '%s'" % (self._generator.all(), ch)
229
assert self._next() == "}"
232
def _eatWhitespace(self):
234
while p is not None and p in string.whitespace or p == '/':
242
return self._generator.peek()
245
return self._generator.next()
247
class JsonWriter(object):
249
def _append(self, s):
250
self._results.append(s)
252
def write(self, obj, escaped_forward_slash=False):
253
self._escaped_forward_slash = escaped_forward_slash
256
return "".join(self._results)
258
def _write(self, obj):
260
if ty is types.DictType:
263
for k, v in obj.items():
271
elif ty is types.ListType or ty is types.TupleType:
280
elif ty is types.StringType or ty is types.UnicodeType:
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')
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)
300
self._append("false")
304
raise WriteException, "Cannot write in JSON: %s" % repr(obj)
306
def write(obj, escaped_forward_slash=False):
307
return JsonWriter().write(obj, escaped_forward_slash)
310
return JsonReader().read(s)