""" Java JSON standard library APIs implemented in Python. org.json package - JSONObject, JSONArray, JSONTokener, JSONStringer, JSONWriter, etc. """ from .._core import Stub import json as _json __all__ = [ "JSONObject", "JSONArray", "JSONTokener", "JSONStringer", "JSONWriter", "JSONException", "JSONPointer", "JSONPointerBuilder", ] # ============================================================ # JSONException # ============================================================ class JSONException(Exception): def __init__(self, *args): if len(args) == 1 and isinstance(args[0], Exception): super().__init__(str(args[0])) self._cause = args[0] else: super().__init__(*args) self._cause = None def getCause(self): return self._cause # ============================================================ # JSONObject.NULL # ============================================================ class _JSONNull(Stub): def __init__(self): super().__init__() def equals(self, other): return other is None or isinstance(other, _JSONNull) def hashCode(self): return 0 def toString(self): return "null" # ============================================================ # JSONObject # ============================================================ class JSONObject(Stub): NULL = _JSONNull() def __init__(self, *args): super().__init__() self._map = {} if len(args) == 1: arg = args[0] if isinstance(arg, str): tokener = JSONTokener(arg) obj = tokener.nextValue() if isinstance(obj, JSONObject): self._map = obj._map else: raise JSONException("String is not a JSONObject") elif isinstance(arg, dict): for k, v in arg.items(): self._map[str(k)] = JSONObject._wrap(v) elif isinstance(arg, JSONObject): names = None self._map = dict(arg._map) def _wrap(val): if val is None: return JSONObject.NULL if isinstance(val, (bool, int, float)): return val if isinstance(val, str): return val if isinstance(val, (list, tuple)): arr = JSONArray() for item in val: arr.put(JSONObject._wrap(item)) return arr if isinstance(val, dict): obj = JSONObject() for k, v in val.items(): obj.put(str(k), JSONObject._wrap(v)) return obj if isinstance(val, JSONObject) or isinstance(val, JSONArray): return val return val def put(self, key, value): self._map[str(key)] = JSONObject._wrap(value) return self def accumulate(self, key, value): k = str(key) current = self._map.get(k) if current is None: arr = JSONArray() arr.put(value) self._map[k] = arr elif isinstance(current, JSONArray): current.put(value) else: arr = JSONArray() arr.put(current) arr.put(value) self._map[k] = arr return self def get(self, key): k = str(key) if k not in self._map: raise JSONException(f"JSONObject[\"{k}\"] not found") val = self._map[k] if val is JSONObject.NULL: raise JSONException(f"JSONObject[\"{k}\"] is null") return val def opt(self, key, default=None): k = str(key) val = self._map.get(k) if val is None or val is JSONObject.NULL: return default return val def getString(self, key): val = self.get(key) if isinstance(val, str): return val raise JSONException(f"JSONObject[\"{key}\"] is not a string") def getInt(self, key): val = self.get(key) if isinstance(val, int): return val if isinstance(val, float): return int(val) raise JSONException(f"JSONObject[\"{key}\"] is not an int") def getLong(self, key): val = self.get(key) if isinstance(val, int): return val if isinstance(val, float): return int(val) raise JSONException(f"JSONObject[\"{key}\"] is not a long") def getDouble(self, key): val = self.get(key) if isinstance(val, (int, float)): return float(val) raise JSONException(f"JSONObject[\"{key}\"] is not a double") def getBoolean(self, key): val = self.get(key) if isinstance(val, bool): return val if val == "true": return True if val == "false": return False raise JSONException(f"JSONObject[\"{key}\"] is not a boolean") def getJSONArray(self, key): val = self.get(key) if isinstance(val, JSONArray): return val raise JSONException(f"JSONObject[\"{key}\"] is not a JSONArray") def getJSONObject(self, key): val = self.get(key) if isinstance(val, JSONObject): return val raise JSONException(f"JSONObject[\"{key}\"] is not a JSONObject") def optString(self, key, default=""): val = self.opt(key) if val is None or val is JSONObject.NULL: return default return str(val) def optInt(self, key, default=0): val = self.opt(key) if val is None or val is JSONObject.NULL: return default if isinstance(val, int): return val if isinstance(val, float): return int(val) try: return int(val) except (ValueError, TypeError): return default def optLong(self, key, default=0): return self.optInt(key, default) def optDouble(self, key, default=0.0): val = self.opt(key) if val is None or val is JSONObject.NULL: return default if isinstance(val, (int, float)): return float(val) try: return float(val) except (ValueError, TypeError): return default def optBoolean(self, key, default=False): val = self.opt(key) if val is None or val is JSONObject.NULL: return default if isinstance(val, bool): return val if isinstance(val, str): return val.lower() == "true" return default def optJSONArray(self, key): val = self.opt(key) if isinstance(val, JSONArray): return val return None def optJSONObject(self, key): val = self.opt(key) if isinstance(val, JSONObject): return val return None def has(self, key): return str(key) in self._map def remove(self, key): k = str(key) if k in self._map: del self._map[k] def keys(self): return iter(self._map.keys()) def names(self): if not self._map: return None arr = JSONArray() for k in self._map: arr.put(k) return arr def length(self): return len(self._map) def toString(self, indent_factor=None): if indent_factor is None: return self._to_json_string() return self._to_json_pretty(indent_factor) def _to_json_string(self): parts = ["{"] for i, (k, v) in enumerate(self._map.items()): parts.append('"' + k + '": ' + JSONObject._value_to_string(v)) if i < len(self._map) - 1: parts.append(", ") parts.append("}") return "".join(parts) def _to_json_pretty(self, indent): parts = ["{"] for i, (k, v) in enumerate(self._map.items()): parts.append("\n" + " " * indent + '"' + k + '": ' + JSONObject._value_to_string(v)) if i < len(self._map) - 1: parts.append(",") parts.append("\n}") return "".join(parts) def _value_to_string(val): if val is None or val is JSONObject.NULL: return "null" if isinstance(val, bool): return "true" if val else "false" if isinstance(val, (int, float)): return str(val) if isinstance(val, str): return _json.dumps(val) if isinstance(val, JSONObject): return val._to_json_string() if isinstance(val, JSONArray): return val._to_json_string() return str(val) def toMap(self): result = {} for k, v in self._map.items(): if isinstance(v, JSONObject): result[k] = v.toMap() elif isinstance(v, JSONArray): result[k] = v.toList() elif v is JSONObject.NULL: result[k] = None else: result[k] = v return result def write(self, writer): writer._write(self._to_json_string()) def toJSONArray(self, names): if names is None or names.length() == 0: return None arr = JSONArray() for i in range(names.length()): arr.put(self.opt(names.getString(i))) return arr def hashCode(self): return hash(frozenset(self._map.items())) def equals(self, other): if isinstance(other, JSONObject): return self._map == other._map return False # ============================================================ # JSONArray # ============================================================ class JSONArray(Stub): def __init__(self, *args): super().__init__() self._list = [] if len(args) == 1: arg = args[0] if isinstance(arg, str): tokener = JSONTokener(arg) val = tokener.nextValue() if isinstance(val, JSONArray): self._list = val._list else: raise JSONException("String is not a JSONArray") elif isinstance(arg, (list, tuple)): for item in arg: self.put(JSONObject._wrap(item)) def put(self, *args): if len(args) == 1: self._list.append(JSONObject._wrap(args[0])) elif len(args) == 2: index, value = args while len(self._list) <= index: self._list.append(None) self._list[index] = JSONObject._wrap(value) return self def get(self, index): if index < 0 or index >= len(self._list): raise JSONException(f"JSONArray[{index}] not found") val = self._list[index] if val is JSONObject.NULL: raise JSONException(f"JSONArray[{index}] is null") return val def getString(self, index): val = self.get(index) if isinstance(val, str): return val raise JSONException(f"JSONArray[{index}] is not a string") def getInt(self, index): val = self.get(index) if isinstance(val, int): return val if isinstance(val, float): return int(val) raise JSONException(f"JSONArray[{index}] is not an int") def getLong(self, index): val = self.get(index) if isinstance(val, int): return val if isinstance(val, float): return int(val) raise JSONException(f"JSONArray[{index}] is not a long") def getDouble(self, index): val = self.get(index) if isinstance(val, (int, float)): return float(val) raise JSONException(f"JSONArray[{index}] is not a double") def getBoolean(self, index): val = self.get(index) if isinstance(val, bool): return val if val == "true": return True if val == "false": return False raise JSONException(f"JSONArray[{index}] is not a boolean") def getJSONArray(self, index): val = self.get(index) if isinstance(val, JSONArray): return val raise JSONException(f"JSONArray[{index}] is not a JSONArray") def getJSONObject(self, index): val = self.get(index) if isinstance(val, JSONObject): return val raise JSONException(f"JSONArray[{index}] is not a JSONObject") def opt(self, index, default=None): if index < 0 or index >= len(self._list): return default val = self._list[index] if val is JSONObject.NULL: return default return val def optString(self, index, default=""): val = self.opt(index) if val is None: return default return str(val) def optInt(self, index, default=0): val = self.opt(index) if val is None: return default if isinstance(val, int): return val if isinstance(val, float): return int(val) try: return int(val) except (ValueError, TypeError): return default def optLong(self, index, default=0): return self.optInt(index, default) def optDouble(self, index, default=0.0): val = self.opt(index) if val is None: return default if isinstance(val, (int, float)): return float(val) try: return float(val) except (ValueError, TypeError): return default def optBoolean(self, index, default=False): val = self.opt(index) if val is None: return default if isinstance(val, bool): return val if isinstance(val, str): return val.lower() == "true" return default def optJSONArray(self, index): val = self.opt(index) if isinstance(val, JSONArray): return val return None def optJSONObject(self, index): val = self.opt(index) if isinstance(val, JSONObject): return val return None def remove(self, index): if 0 <= index < len(self._list): return self._list.pop(index) return None def length(self): return len(self._list) def isNull(self, index): if 0 <= index < len(self._list): return self._list[index] is JSONObject.NULL or self._list[index] is None return True def toString(self, indent_factor=None): if indent_factor is None: return self._to_json_string() return self._to_json_pretty(indent_factor) def _to_json_string(self): parts = ["["] for i, v in enumerate(self._list): parts.append(JSONObject._value_to_string(v)) if i < len(self._list) - 1: parts.append(", ") parts.append("]") return "".join(parts) def _to_json_pretty(self, indent): parts = ["["] for i, v in enumerate(self._list): parts.append("\n" + " " * indent + JSONObject._value_to_string(v)) if i < len(self._list) - 1: parts.append(",") parts.append("\n]") return "".join(parts) def toList(self): result = [] for v in self._list: if isinstance(v, JSONObject): result.append(v.toMap()) elif isinstance(v, JSONArray): result.append(v.toList()) elif v is JSONObject.NULL: result.append(None) else: result.append(v) return result def join(self, separator): parts = [] for v in self._list: parts.append(JSONObject._value_to_string(v)) return separator.join(parts) # ============================================================ # JSONTokener # ============================================================ class JSONTokener(Stub): def __init__(self, source): super().__init__() if hasattr(source, "read"): self._source = source.read() else: self._source = str(source) self._index = 0 def nextValue(self): self._skipWhitespace() if self._index >= len(self._source): raise JSONException("Unexpected end of input") ch = self._source[self._index] if ch == "{": return self._readObject() if ch == "[": return self._readArray() if ch == '"': return self._readString() if ch == "'": return self._readString("'") if ch == "t" or ch == "f": return self._readLiteral() if ch == "n": return self._readNull() return self._readNumber() def _skipWhitespace(self): while self._index < len(self._source) and self._source[self._index] in " \t\n\r": self._index += 1 def _readObject(self): self._index += 1 # skip { obj = JSONObject() self._skipWhitespace() if self._index < len(self._source) and self._source[self._index] == "}": self._index += 1 return obj while True: self._skipWhitespace() key = self.nextValue() self._skipWhitespace() if self._index < len(self._source) and self._source[self._index] == ":": self._index += 1 self._skipWhitespace() val = self.nextValue() obj.put(str(key), val) self._skipWhitespace() if self._index < len(self._source) and self._source[self._index] == ",": self._index += 1 elif self._index < len(self._source) and self._source[self._index] == "}": self._index += 1 return obj else: raise JSONException("Expected ',' or '}' in object") def _readArray(self): self._index += 1 # skip [ arr = JSONArray() self._skipWhitespace() if self._index < len(self._source) and self._source[self._index] == "]": self._index += 1 return arr while True: self._skipWhitespace() val = self.nextValue() arr.put(val) self._skipWhitespace() if self._index < len(self._source) and self._source[self._index] == ",": self._index += 1 elif self._index < len(self._source) and self._source[self._index] == "]": self._index += 1 return arr else: raise JSONException("Expected ',' or ']' in array") def _readString(self, quote='"'): self._index += 1 # skip quote result = [] while self._index < len(self._source): ch = self._source[self._index] if ch == "\\": self._index += 1 if self._index >= len(self._source): raise JSONException("Unterminated string escape") esc = self._source[self._index] if esc == '"': result.append('"') elif esc == "\\": result.append("\\") elif esc == "/": result.append("/") elif esc == "b": result.append("\b") elif esc == "f": result.append("\f") elif esc == "n": result.append("\n") elif esc == "r": result.append("\r") elif esc == "t": result.append("\t") elif esc == "u": hex_str = self._source[self._index+1:self._index+5] result.append(chr(int(hex_str, 16))) self._index += 4 else: result.append(esc) self._index += 1 elif ch == quote: self._index += 1 return "".join(result) else: result.append(ch) self._index += 1 raise JSONException("Unterminated string") def _readLiteral(self): for lit in ("true", "false"): if self._source[self._index:self._index+len(lit)] == lit: self._index += len(lit) return lit == "true" raise JSONException(f"Unexpected literal at {self._index}") def _readNull(self): if self._source[self._index:self._index+4] == "null": self._index += 4 return JSONObject.NULL raise JSONException(f"Unexpected token at {self._index}") def _readNumber(self): start = self._index while self._index < len(self._source) and self._source[self._index] in "-+0123456789.eE": self._index += 1 num_str = self._source[start:self._index] if "." in num_str or "e" in num_str or "E" in num_str: return float(num_str) return int(num_str) def nextString(self, quote='"'): return self._readString(quote) def nextChar(self, c): self._skipWhitespace() if self._index < len(self._source) and self._source[self._index] == c: self._index += 1 return c raise JSONException(f"Expected '{c}'") def nextTo(self, delimiter): start = self._index while self._index < len(self._source) and self._source[self._index] not in delimiter: self._index += 1 return self._source[start:self._index] def skipPast(self, text): idx = self._source.find(text, self._index) if idx >= 0: self._index = idx + len(text) def skipTo(self, c): idx = self._source.find(c, self._index) if idx >= 0: self._index = idx return idx return -1 def back(self): self._index -= 1 def more(self): self._skipWhitespace() return self._index < len(self._source) def readString(self): return self._readString() # ============================================================ # JSONWriter # ============================================================ class JSONWriter(Stub): def __init__(self, writer): super().__init__() self._writer = writer self._stack = [] self._in_object = False self._need_comma = False def _write(self, s): self._writer.write(s) def array(self): self._beforeValue() self._stack.append(("array", False)) self._write("[") return self def endArray(self): self._write("]") self._stack.pop() return self def object(self): self._beforeValue() self._stack.append(("object", False)) self._write("{") return self def endObject(self): self._write("}") self._stack.pop() return self def key(self, s): if not self._stack or self._stack[-1][0] != "object": raise JSONException("Can't set key outside object") self._beforeKey() self._write('"' + s + '":') return self def value(self, v): self._beforeValue() self._write(JSONObject._value_to_string(v)) return self def _beforeValue(self): if self._stack and self._stack[-1][0] == "object": raise JSONException("Expected key in object") def _beforeKey(self): if self._stack and self._stack[-1][1]: self._write(",") self._stack[-1] = (self._stack[-1][0], True) # ============================================================ # JSONStringer # ============================================================ class JSONStringer(JSONWriter): def __init__(self): from io import StringIO super().__init__(StringIO()) def toString(self): return self._writer.getvalue() # ============================================================ # JSONPointer # ============================================================ class JSONPointer(Stub): def __init__(self, pointer): super().__init__() if not pointer or pointer == "": self._tokens = [] elif pointer.startswith("#"): pointer = pointer[1:] pointer = pointer.replace("~1", "/").replace("~0", "~") import urllib.parse pointer = urllib.parse.unquote(pointer) self._tokens = pointer.split("/")[1:] if len(pointer) > 1 else [] else: self._tokens = pointer.split("/")[1:] if len(pointer) > 0 else [] def query(self, root): current = root for token in self._tokens: token = token.replace("~1", "/").replace("~0", "~") if isinstance(current, JSONObject): current = current.get(token) elif isinstance(current, JSONArray): try: idx = int(token) current = current.get(idx) except (ValueError, IndexError): raise JSONException(f"Invalid index: {token}") else: raise JSONException(f"Cannot index into {type(current)}") return current def queryFrom(self, root): return self.query(root) def toString(self): return "#/" + "/".join(t.replace("~", "~0").replace("/", "~1") for t in self._tokens) def hashCode(self): return hash(tuple(self._tokens)) def equals(self, other): if isinstance(other, JSONPointer): return self._tokens == other._tokens return False # ============================================================ # JSONPointerBuilder # ============================================================ class JSONPointerBuilder(Stub): def __init__(self): super().__init__() self._tokens = [] def append(self, token): self._tokens.append(str(token)) return self def build(self): return JSONPointer("#/" + "/".join(t.replace("~", "~0").replace("/", "~1") for t in self._tokens))