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