import json
import re
from typing import Tuple, Optional, Any
JSON_FENCE = re.compile(r"```(?:json)?\s*(\{.*?\})\s*```", re.DOTALL)
JSON_INLINE = re.compile(r"\{.*\}", re.DOTALL)
_TRAILING_COMMAS = re.compile(r",\s*([}\]])")
def _clean_json(blob: str) -> str:
"""
Given a JSON blob remove any invalid trailing commans
Args:
blob: string with json object
Return:
Corrected string with no trailing commas
"""
return _TRAILING_COMMAS.sub(r"\1", blob)
[docs]
def parse_json_body(text: str) -> Tuple[Optional[dict], str]:
if not text:
return None, text
last_fence = None
for last_fence in JSON_FENCE.finditer(text):
pass
if last_fence:
blob = last_fence.group(1)
try:
obj: dict = json.loads(_clean_json(blob))
obj = strip_keys(obj)
m = last_fence
remainder = text[: m.start()] + text[m.end() :]
return obj, remainder.strip()
except Exception:
pass
last_inline = None
for last_inline in JSON_INLINE.finditer(text):
pass
if last_inline:
blob = last_inline.group(0)
try:
obj: dict = json.loads(_clean_json(blob))
obj = strip_keys(obj)
start, end = last_inline.start(), last_inline.end()
remainder = text[:start] + text[end:]
return obj, remainder.strip()
except Exception:
pass
return None, text
[docs]
def strip_keys(obj) -> Any:
"""
Recursively remove leading and trailing whitespace from dictionary keys.
Args:
obj (dict): The dictionary object to process.
Returns:
object with all dictionary keys stripped of surrounding whitespace.
"""
if isinstance(obj, dict):
new_dict = {}
for key, value in obj.items():
# Strip whitespace from string keys only
new_key = key.strip() if isinstance(key, str) else key
new_dict[new_key] = strip_keys(value)
return new_dict
elif isinstance(obj, list):
# Apply recursively to elements inside lists
return [strip_keys(item) for item in obj]
else:
return obj